@adonis-agora/authkit-client 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -0
  3. package/build/commands/configure.d.ts +2 -0
  4. package/build/commands/configure.js +17 -0
  5. package/build/index.d.ts +28 -0
  6. package/build/index.js +15 -0
  7. package/build/providers/authkit_client_provider.d.ts +61 -0
  8. package/build/providers/authkit_client_provider.js +187 -0
  9. package/build/src/authenticator.d.ts +41 -0
  10. package/build/src/authenticator.js +105 -0
  11. package/build/src/backchannel_logout.d.ts +77 -0
  12. package/build/src/backchannel_logout.js +114 -0
  13. package/build/src/define_config.d.ts +80 -0
  14. package/build/src/define_config.js +41 -0
  15. package/build/src/discovery.d.ts +55 -0
  16. package/build/src/discovery.js +90 -0
  17. package/build/src/dpop.d.ts +50 -0
  18. package/build/src/dpop.js +48 -0
  19. package/build/src/http/resilient_fetch.d.ts +30 -0
  20. package/build/src/http/resilient_fetch.js +26 -0
  21. package/build/src/lucid_mirror.d.ts +38 -0
  22. package/build/src/lucid_mirror.js +32 -0
  23. package/build/src/middleware/auth_middleware.d.ts +29 -0
  24. package/build/src/middleware/auth_middleware.js +36 -0
  25. package/build/src/middleware/authkit_middleware.d.ts +5 -0
  26. package/build/src/middleware/authkit_middleware.js +9 -0
  27. package/build/src/middleware/backchannel_revocation_middleware.d.ts +18 -0
  28. package/build/src/middleware/backchannel_revocation_middleware.js +50 -0
  29. package/build/src/middleware/silent_auth_middleware.d.ts +13 -0
  30. package/build/src/middleware/silent_auth_middleware.js +14 -0
  31. package/build/src/observability/client_metrics.d.ts +10 -0
  32. package/build/src/observability/client_metrics.js +59 -0
  33. package/build/src/observability/context_bridge.d.ts +7 -0
  34. package/build/src/observability/context_bridge.js +49 -0
  35. package/build/src/oidc_login.d.ts +113 -0
  36. package/build/src/oidc_login.js +123 -0
  37. package/build/src/register_oidc_client.d.ts +50 -0
  38. package/build/src/register_oidc_client.js +125 -0
  39. package/build/src/resolvers/factory.d.ts +41 -0
  40. package/build/src/resolvers/factory.js +51 -0
  41. package/build/src/resolvers/identity.d.ts +30 -0
  42. package/build/src/resolvers/identity.js +49 -0
  43. package/build/src/resolvers/jwt_resolver.d.ts +20 -0
  44. package/build/src/resolvers/jwt_resolver.js +34 -0
  45. package/build/src/resolvers/opaque_resolver.d.ts +43 -0
  46. package/build/src/resolvers/opaque_resolver.js +51 -0
  47. package/build/src/resolvers/pat_resolver.d.ts +18 -0
  48. package/build/src/resolvers/pat_resolver.js +24 -0
  49. package/build/src/revocation/revocation_store.d.ts +68 -0
  50. package/build/src/revocation/revocation_store.js +0 -0
  51. package/build/src/token_source.d.ts +3 -0
  52. package/build/src/token_source.js +12 -0
  53. package/build/src/types.d.ts +6 -0
  54. package/build/src/types.js +1 -0
  55. package/build/src/user_resolvers.d.ts +41 -0
  56. package/build/src/user_resolvers.js +42 -0
  57. package/build/src/verify_access_token.d.ts +37 -0
  58. package/build/src/verify_access_token.js +43 -0
  59. package/build/stubs/.gitkeep +0 -0
  60. package/build/stubs/config/authkit_client.stub +17 -0
  61. package/build/stubs/controllers/oidc_session_controller.stub +42 -0
  62. package/build/stubs/main.d.ts +1 -0
  63. package/build/stubs/main.js +2 -0
  64. package/build/types.d.ts +13 -0
  65. package/build/types.js +1 -0
  66. package/package.json +92 -0
  67. package/stubs/.gitkeep +0 -0
  68. package/stubs/config/authkit_client.stub +17 -0
  69. package/stubs/controllers/oidc_session_controller.stub +42 -0
  70. package/stubs/main.ts +2 -0
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Converte uma `Identity` (claims OIDC validadas) num objeto de usuário simples.
3
+ * Pode ser usada diretamente como `resolveUser: identityToUser`.
4
+ */
5
+ export function identityToUser(identity) {
6
+ return {
7
+ id: identity.userId,
8
+ email: identity.email,
9
+ name: identity.profile?.name,
10
+ avatarUrl: identity.profile?.avatarUrl,
11
+ globalRoles: identity.globalRoles,
12
+ };
13
+ }
14
+ /**
15
+ * Fábrica de um `resolveUser` que busca o usuário no endpoint userinfo do IdP
16
+ * usando o access token. Caminho recomendado quando o app precisa de dados
17
+ * além do que o token carrega (topologia de bancos separados).
18
+ *
19
+ * Sem access token (ex.: tokenSource bearer só com ID token), faz fallback
20
+ * para `identityToUser(identity)`.
21
+ */
22
+ export function createUserinfoResolver(options = {}) {
23
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
24
+ const endpoint = options.userinfoEndpoint ??
25
+ (options.issuer ? `${options.issuer.replace(/\/$/, '')}/me` : undefined);
26
+ return async function resolveUser(identity, context = {}) {
27
+ const accessToken = context.accessToken;
28
+ if (!accessToken || !endpoint) {
29
+ return identityToUser(identity);
30
+ }
31
+ const res = await fetchImpl(endpoint, {
32
+ method: 'GET',
33
+ headers: { authorization: `Bearer ${accessToken}`, accept: 'application/json' },
34
+ });
35
+ if (!res.ok) {
36
+ throw new Error(`userinfo falhou: HTTP ${res.status}`);
37
+ }
38
+ const claims = (await res.json());
39
+ // mescla identity (base) com o que veio do userinfo (sobrescreve)
40
+ return { ...identityToUser(identity), ...claims };
41
+ };
42
+ }
@@ -0,0 +1,37 @@
1
+ import { type JWTPayload } from 'jose';
2
+ /**
3
+ * Verificação LOCAL de um JWT Access Token (RFC 9068) emitido pelo AuthKit server
4
+ * com `accessTokens: { format: 'jwt' }`. Valida a assinatura contra o JWKS remoto
5
+ * (cacheado) do IdP — SEM round-trip de introspecção — e impõe as regras do perfil
6
+ * RFC 9068: header `typ: at+jwt`, `iss`/`aud` esperados e claims obrigatórias.
7
+ *
8
+ * Barato e stateless: o `createRemoteJWKSet` busca o jwks_uri uma vez e cacheia as
9
+ * chaves (rotação é tolerada — o jose recarrega ao ver um `kid` novo). Útil para um
10
+ * resource server que recebe o AT no header Authorization e quer validar sem chamar
11
+ * o /introspect a cada request.
12
+ */
13
+ export interface VerifyJwtAccessTokenOptions {
14
+ /** Issuer esperado (a claim `iss`). */
15
+ issuer: string;
16
+ /** jwks_uri do IdP (do discovery). */
17
+ jwksUri: string;
18
+ /** Audience esperada (a claim `aud`) — a URI/identificador desta API. */
19
+ audience: string | string[];
20
+ /** Algoritmos de assinatura aceitos (defesa contra alg-confusion). Default: asimétricos. */
21
+ algorithms?: string[];
22
+ /** Aceita JWS com qualquer `typ` (desliga a checagem RFC 9068 `at+jwt`). Default: false. */
23
+ allowAnyTyp?: boolean;
24
+ }
25
+ /** Claims de um JWT AT RFC 9068 validado. */
26
+ export interface JwtAccessTokenClaims extends JWTPayload {
27
+ client_id?: string;
28
+ scope?: string;
29
+ }
30
+ /**
31
+ * Verifica um JWT AT (RFC 9068). Lança (via jose) se a assinatura/claims forem
32
+ * inválidas ou se o `typ` não for `at+jwt` (a menos que `allowAnyTyp`). Retorna as
33
+ * claims validadas no caminho feliz.
34
+ */
35
+ export declare function verifyJwtAccessToken(token: string, options: VerifyJwtAccessTokenOptions): Promise<JwtAccessTokenClaims>;
36
+ /** Limpa o cache de JWKS remotos (útil em testes). */
37
+ export declare function clearJwksCache(): void;
@@ -0,0 +1,43 @@
1
+ import { createRemoteJWKSet, jwtVerify } from 'jose';
2
+ const DEFAULT_ALGS = [
3
+ 'RS256',
4
+ 'RS384',
5
+ 'RS512',
6
+ 'PS256',
7
+ 'PS384',
8
+ 'PS512',
9
+ 'ES256',
10
+ 'ES384',
11
+ 'ES512',
12
+ 'EdDSA',
13
+ ];
14
+ // Cache de keysets remotos por jwks_uri — evita refazer fetch/parse por request.
15
+ const jwksCache = new Map();
16
+ function getJwks(uri) {
17
+ let set = jwksCache.get(uri);
18
+ if (!set) {
19
+ set = createRemoteJWKSet(new URL(uri));
20
+ jwksCache.set(uri, set);
21
+ }
22
+ return set;
23
+ }
24
+ /**
25
+ * Verifica um JWT AT (RFC 9068). Lança (via jose) se a assinatura/claims forem
26
+ * inválidas ou se o `typ` não for `at+jwt` (a menos que `allowAnyTyp`). Retorna as
27
+ * claims validadas no caminho feliz.
28
+ */
29
+ export async function verifyJwtAccessToken(token, options) {
30
+ const jwks = getJwks(options.jwksUri);
31
+ const { payload } = await jwtVerify(token, jwks, {
32
+ issuer: options.issuer,
33
+ audience: options.audience,
34
+ algorithms: options.algorithms ?? DEFAULT_ALGS,
35
+ // RFC 9068 §2.1: o header DEVE ser `typ: "at+jwt"`. jose valida quando passamos `typ`.
36
+ ...(options.allowAnyTyp ? {} : { typ: 'at+jwt' }),
37
+ });
38
+ return payload;
39
+ }
40
+ /** Limpa o cache de JWKS remotos (útil em testes). */
41
+ export function clearJwksCache() {
42
+ jwksCache.clear();
43
+ }
File without changes
@@ -0,0 +1,17 @@
1
+ {{{
2
+ exports({ to: app.configPath('authkit_client.ts') })
3
+ }}}
4
+ import env from '#start/env'
5
+ import { defineConfig, resolvers } from '@adonis-agora/authkit-client'
6
+
7
+ const authkitClientConfig = defineConfig({
8
+ issuer: env.get('AUTHKIT_ISSUER'),
9
+ clientId: env.get('AUTHKIT_CLIENT_ID'),
10
+ clientSecret: env.get('AUTHKIT_CLIENT_SECRET'),
11
+ redirectUri: env.get('AUTHKIT_REDIRECT_URI'),
12
+ resolver: resolvers.jwt({ tokenSource: 'session' }),
13
+ // resolveUser: async (identity) => User.find(identity.userId),
14
+ // resolveAppRoles: async (identity) => RoleService.appRolesFor(identity.userId),
15
+ })
16
+
17
+ export default authkitClientConfig
@@ -0,0 +1,42 @@
1
+ {{{
2
+ exports({ to: app.makePath('app/controllers/oidc_session_controller.ts') })
3
+ }}}
4
+ import type { HttpContext } from '@adonisjs/core/http'
5
+ import { buildAuthorizeUrl, exchangeCode, generatePkce } from '@adonis-agora/authkit-client'
6
+ import { randomUUID } from 'node:crypto'
7
+
8
+ export default class OidcSessionController {
9
+ async login(ctx: HttpContext) {
10
+ const manager = await ctx.containerResolver.make('authkit.client')
11
+ const cfg = manager.clientConfig
12
+ const { verifier, challenge } = await generatePkce()
13
+ const state = randomUUID()
14
+ ctx.session.put('authkit_pkce', { verifier, state })
15
+ const url = buildAuthorizeUrl({
16
+ issuer: cfg.issuer, clientId: cfg.clientId, redirectUri: cfg.redirectUri,
17
+ scopes: cfg.scopes, state, codeChallenge: challenge,
18
+ })
19
+ return ctx.response.redirect(url)
20
+ }
21
+
22
+ async callback(ctx: HttpContext) {
23
+ const manager = await ctx.containerResolver.make('authkit.client')
24
+ const cfg = manager.clientConfig
25
+ const { code, state } = ctx.request.qs()
26
+ const pkce = ctx.session.get('authkit_pkce')
27
+ if (!pkce || pkce.state !== state) return ctx.response.badRequest({ error: 'state inválido' })
28
+ const tokenSet = await exchangeCode({
29
+ issuer: cfg.issuer, clientId: cfg.clientId, clientSecret: cfg.clientSecret,
30
+ redirectUri: cfg.redirectUri, code, codeVerifier: pkce.verifier,
31
+ })
32
+ ctx.session.put(cfg.sessionKey, tokenSet)
33
+ ctx.session.forget('authkit_pkce')
34
+ return ctx.response.redirect('/')
35
+ }
36
+
37
+ async logout(ctx: HttpContext) {
38
+ const manager = await ctx.containerResolver.make('authkit.client')
39
+ ctx.session.forget(manager.clientConfig.sessionKey)
40
+ return ctx.response.redirect('/')
41
+ }
42
+ }
@@ -0,0 +1 @@
1
+ export declare const stubsRoot: string;
@@ -0,0 +1,2 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ export const stubsRoot = fileURLToPath(new URL('./', import.meta.url));
@@ -0,0 +1,13 @@
1
+ import type { Authenticator } from './src/authenticator.js';
2
+ import type { AuthkitClientManager } from './providers/authkit_client_provider.js';
3
+ declare module '@adonisjs/core/http' {
4
+ interface HttpContext {
5
+ auth: Authenticator;
6
+ }
7
+ }
8
+ declare module '@adonisjs/core/types' {
9
+ interface ContainerBindings {
10
+ 'authkit.client': AuthkitClientManager;
11
+ }
12
+ }
13
+ export type { Authenticator, AuthkitClientManager };
package/build/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@adonis-agora/authkit-client",
3
+ "version": "0.9.0",
4
+ "description": "AdonisJS OIDC/OAuth2 relying-party (client) toolkit: session-based authentication against an OpenID Connect identity provider, with JWT/PAT user resolvers and OpenTelemetry metrics.",
5
+ "license": "MIT",
6
+ "author": "dudousxd",
7
+ "homepage": "https://github.com/DavideCarvalho/adonis-authkit/tree/main/packages/authkit-client#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/DavideCarvalho/adonis-authkit.git",
11
+ "directory": "packages/authkit-client"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/DavideCarvalho/adonis-authkit/issues"
15
+ },
16
+ "keywords": [
17
+ "adonisjs",
18
+ "oidc",
19
+ "oauth2",
20
+ "openid-connect",
21
+ "authentication",
22
+ "authkit",
23
+ "relying-party",
24
+ "jwt"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "type": "module",
30
+ "main": "./build/index.js",
31
+ "types": "./build/index.d.ts",
32
+ "files": [
33
+ "build",
34
+ "stubs"
35
+ ],
36
+ "exports": {
37
+ ".": "./build/index.js",
38
+ "./types": "./build/index.js",
39
+ "./authkit_client_provider": "./build/providers/authkit_client_provider.js",
40
+ "./authkit_middleware": "./build/src/middleware/authkit_middleware.js",
41
+ "./backchannel_revocation_middleware": "./build/src/middleware/backchannel_revocation_middleware.js",
42
+ "./auth_middleware": "./build/src/middleware/auth_middleware.js",
43
+ "./silent_auth_middleware": "./build/src/middleware/silent_auth_middleware.js",
44
+ "./commands": "./build/commands/configure.js"
45
+ },
46
+ "adonisjs": {
47
+ "types": "@adonis-agora/authkit-client",
48
+ "providers": [
49
+ "@adonis-agora/authkit-client/authkit_client_provider"
50
+ ],
51
+ "commands": [
52
+ "@adonis-agora/authkit-client/commands"
53
+ ]
54
+ },
55
+ "peerDependencies": {
56
+ "@adonisjs/core": "7.3.3",
57
+ "@adonisjs/session": "8.1.0",
58
+ "@adonisjs/lucid": "22.4.2",
59
+ "@adonis-agora/resilience": "^0.1.0"
60
+ },
61
+ "peerDependenciesMeta": {
62
+ "@adonisjs/lucid": {
63
+ "optional": true
64
+ },
65
+ "@adonis-agora/resilience": {
66
+ "optional": true
67
+ }
68
+ },
69
+ "dependencies": {
70
+ "jose": "^5.9.0",
71
+ "@adonis-agora/authkit-core": "0.7.0"
72
+ },
73
+ "devDependencies": {
74
+ "@adonisjs/core": "7.3.3",
75
+ "@adonis-agora/resilience": "^0.1.0",
76
+ "@adonisjs/session": "8.1.0",
77
+ "@adonisjs/lucid": "22.4.2",
78
+ "@adonisjs/tsconfig": "2.0.0",
79
+ "@japa/assert": "4.2.0",
80
+ "@japa/runner": "5.3.0",
81
+ "@poppinss/ts-exec": "1.4.4",
82
+ "ioredis-mock": "^8.9.0",
83
+ "@types/node": "25.9.1",
84
+ "typescript": "6.0.3",
85
+ "@adonis-agora/authkit-server": "0.34.0"
86
+ },
87
+ "scripts": {
88
+ "build": "tsc && node -e \"require('node:fs').cpSync('stubs','build/stubs',{recursive:true,filter:(s)=>!s.endsWith('.ts')})\"",
89
+ "typecheck": "tsc --noEmit",
90
+ "test": "node --import=@poppinss/ts-exec bin/test.ts"
91
+ }
92
+ }
package/stubs/.gitkeep ADDED
File without changes
@@ -0,0 +1,17 @@
1
+ {{{
2
+ exports({ to: app.configPath('authkit_client.ts') })
3
+ }}}
4
+ import env from '#start/env'
5
+ import { defineConfig, resolvers } from '@adonis-agora/authkit-client'
6
+
7
+ const authkitClientConfig = defineConfig({
8
+ issuer: env.get('AUTHKIT_ISSUER'),
9
+ clientId: env.get('AUTHKIT_CLIENT_ID'),
10
+ clientSecret: env.get('AUTHKIT_CLIENT_SECRET'),
11
+ redirectUri: env.get('AUTHKIT_REDIRECT_URI'),
12
+ resolver: resolvers.jwt({ tokenSource: 'session' }),
13
+ // resolveUser: async (identity) => User.find(identity.userId),
14
+ // resolveAppRoles: async (identity) => RoleService.appRolesFor(identity.userId),
15
+ })
16
+
17
+ export default authkitClientConfig
@@ -0,0 +1,42 @@
1
+ {{{
2
+ exports({ to: app.makePath('app/controllers/oidc_session_controller.ts') })
3
+ }}}
4
+ import type { HttpContext } from '@adonisjs/core/http'
5
+ import { buildAuthorizeUrl, exchangeCode, generatePkce } from '@adonis-agora/authkit-client'
6
+ import { randomUUID } from 'node:crypto'
7
+
8
+ export default class OidcSessionController {
9
+ async login(ctx: HttpContext) {
10
+ const manager = await ctx.containerResolver.make('authkit.client')
11
+ const cfg = manager.clientConfig
12
+ const { verifier, challenge } = await generatePkce()
13
+ const state = randomUUID()
14
+ ctx.session.put('authkit_pkce', { verifier, state })
15
+ const url = buildAuthorizeUrl({
16
+ issuer: cfg.issuer, clientId: cfg.clientId, redirectUri: cfg.redirectUri,
17
+ scopes: cfg.scopes, state, codeChallenge: challenge,
18
+ })
19
+ return ctx.response.redirect(url)
20
+ }
21
+
22
+ async callback(ctx: HttpContext) {
23
+ const manager = await ctx.containerResolver.make('authkit.client')
24
+ const cfg = manager.clientConfig
25
+ const { code, state } = ctx.request.qs()
26
+ const pkce = ctx.session.get('authkit_pkce')
27
+ if (!pkce || pkce.state !== state) return ctx.response.badRequest({ error: 'state inválido' })
28
+ const tokenSet = await exchangeCode({
29
+ issuer: cfg.issuer, clientId: cfg.clientId, clientSecret: cfg.clientSecret,
30
+ redirectUri: cfg.redirectUri, code, codeVerifier: pkce.verifier,
31
+ })
32
+ ctx.session.put(cfg.sessionKey, tokenSet)
33
+ ctx.session.forget('authkit_pkce')
34
+ return ctx.response.redirect('/')
35
+ }
36
+
37
+ async logout(ctx: HttpContext) {
38
+ const manager = await ctx.containerResolver.make('authkit.client')
39
+ ctx.session.forget(manager.clientConfig.sessionKey)
40
+ return ctx.response.redirect('/')
41
+ }
42
+ }
package/stubs/main.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { fileURLToPath } from 'node:url'
2
+ export const stubsRoot = fileURLToPath(new URL('./', import.meta.url))