@adonis-agora/authkit-server 0.59.0 → 0.61.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 (27) hide show
  1. package/build/index.d.ts +4 -2
  2. package/build/index.js +3 -1
  3. package/build/providers/authkit_server_provider.js +7 -0
  4. package/build/src/accounts/account_store.d.ts +20 -1
  5. package/build/src/accounts/account_store.js +4 -0
  6. package/build/src/accounts/lucid_account_store.js +4 -0
  7. package/build/src/accounts/lucid_store/login_methods.d.ts +11 -0
  8. package/build/src/accounts/lucid_store/login_methods.js +32 -0
  9. package/build/src/define_config.d.ts +23 -0
  10. package/build/src/define_config.js +6 -0
  11. package/build/src/host/account_api/account_api_controller.d.ts +38 -0
  12. package/build/src/host/account_api/account_api_controller.js +84 -3
  13. package/build/src/host/auth_host_config.d.ts +9 -0
  14. package/build/src/host/controllers/headless_login_methods_controller.d.ts +31 -0
  15. package/build/src/host/controllers/headless_login_methods_controller.js +79 -0
  16. package/build/src/host/controllers/interaction_controller.js +159 -12
  17. package/build/src/host/controllers/social_controller.js +29 -1
  18. package/build/src/host/login_methods_state.d.ts +39 -0
  19. package/build/src/host/login_methods_state.js +52 -0
  20. package/build/src/host/register_auth_host.js +18 -0
  21. package/build/src/host/ui-dist/assets/{index-6cE5JMyP.js → index-Cb38N16r.js} +1 -1
  22. package/build/src/host/ui-dist/index.html +1 -1
  23. package/build/src/host/user_login_methods.d.ts +94 -0
  24. package/build/src/host/user_login_methods.js +132 -0
  25. package/package.json +7 -4
  26. package/skills/authkit-idp-setup/SKILL.md +222 -0
  27. package/skills/authkit-interactions/SKILL.md +236 -0
@@ -8,7 +8,7 @@
8
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
10
  <link href="https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700&family=JetBrains+Mono:ital,wght@0,400;0,500;0,600;1,400&display=swap" rel="stylesheet">
11
- <script type="module" crossorigin src="/__AUTHKIT_BASE__/assets/index-6cE5JMyP.js"></script>
11
+ <script type="module" crossorigin src="/__AUTHKIT_BASE__/assets/index-Cb38N16r.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/__AUTHKIT_BASE__/assets/index-DTSmD4RU.css">
13
13
  </head>
14
14
  <body>
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Tipos de login POR USUÁRIO — preferência self-service do dono da conta.
3
+ *
4
+ * Diferente do runtime setting global `auth_methods` (que o operador controla via
5
+ * console admin), aqui é o PRÓPRIO usuário quem decide, no console de conta,
6
+ * quais métodos de login ficam habilitados para a SUA conta. A preferência é
7
+ * persistida na coluna `login_methods` (JSONB) de `auth.users` e SEMPRE se
8
+ * intersecta com os métodos globais efetivos: o usuário nunca pode LIGAR um
9
+ * método que o host desligou (config pin ou setting), só restringir.
10
+ *
11
+ * Contratos:
12
+ * - Coluna ausente no model → capacidade AUSENTE → feature no-op (mesmo padrão
13
+ * das demais colunas opcionais; hosts adotam por migração própria).
14
+ * - `NULL` na coluna = sem preferência = herda os globais (comportamento atual).
15
+ * - Fail-safe all-off: uma preferência que zere TODOS os métodos disponíveis
16
+ * deixa de restringir (nunca trancamos o usuário fora da própria conta).
17
+ */
18
+ /** Chaves de método que o usuário pode ligar/desligar para a própria conta. */
19
+ export type UserLoginMethodKey = 'password' | 'magicLink' | 'passkey' | 'social';
20
+ export declare const USER_LOGIN_METHOD_KEYS: readonly UserLoginMethodKey[];
21
+ /**
22
+ * Shape persistido na coluna `login_methods` (JSONB) de `auth.users`.
23
+ * Campos ausentes = herda o global. Campos presentes = ON/OFF explícito.
24
+ */
25
+ export interface UserLoginMethods {
26
+ password?: boolean;
27
+ magicLink?: boolean;
28
+ passkey?: boolean;
29
+ /**
30
+ * O método social como um todo (não por provider — a interseção com os
31
+ * providers configurados já acontece no resolver global).
32
+ */
33
+ social?: boolean;
34
+ }
35
+ /**
36
+ * Preferência NORMALIZADA pronta para consumo: cada campo presente indica o que
37
+ * o usuário escolheu; ausente = herda o global. É o que o store devolve/gravа.
38
+ */
39
+ export type NormalizedUserLoginMethods = UserLoginMethods;
40
+ /**
41
+ * Resultado da interseção global × por-usuário, pronto para as telas/POSTs.
42
+ * Espelha {@link ResolvedAuthMethods} (global) mas já filtrado pela preferência.
43
+ */
44
+ export interface ResolvedUserLoginMethods {
45
+ password: boolean;
46
+ magicLink: boolean;
47
+ passkey: boolean;
48
+ /** Providers sociais finais (global ∩ preferência). */
49
+ social: string[];
50
+ forgotPassword: boolean;
51
+ passkeyAutofill: boolean;
52
+ }
53
+ /**
54
+ * Normaliza um valor cru vindo do DB (JSONB) ou do request body para o shape
55
+ * canônico. Campos inválidos são descartados silenciosamente (fail-safe).
56
+ * Objetos vazios viram null (sem preferência).
57
+ */
58
+ export declare function normalizeUserLoginMethods(raw: unknown): NormalizedUserLoginMethods | null;
59
+ /**
60
+ * Valida e normaliza o payload de update vindo do PUT da account API.
61
+ * Espera `{ methods: {...} }`; só aceita chaves conhecidas com valores booleanos.
62
+ * `{ methods: {} }` = limpar a preferência (voltar a herdar os globais).
63
+ */
64
+ export declare function parseUserLoginMethodsPayload(body: unknown): ParsedUserLoginMethodsPayload;
65
+ export type ParsedUserLoginMethodsPayload = {
66
+ ok: true;
67
+ value: UserLoginMethods;
68
+ } | {
69
+ ok: false;
70
+ error: string;
71
+ };
72
+ /**
73
+ * Interseção dos métodos globais efetivos com a preferência do usuário.
74
+ *
75
+ * Regras:
76
+ * - `pref` null/vazio → globais inalterados (herança).
77
+ * - Campo `false` na preferência DESLIGA o método (só pode restringir).
78
+ * - Campo `true` NUNCA liga método globalmente indisponível (interseção).
79
+ * - FAIL-SAFE all-off: se a preferência zerar todos os métodos disponíveis,
80
+ * volta aos globais (nunca deixar a conta sem nenhum método de entrada).
81
+ * Loga um aviso (console.warn) — sinaliza preferência órfã após o host ter
82
+ * desligado métodos globalmente.
83
+ * - Qualquer erro → globais (a chamada nunca deve derrubar o login).
84
+ */
85
+ export declare function resolveEffectiveUserLoginMethods(global: ResolvedAuthMethodsLike, pref: UserLoginMethods | null | undefined): ResolvedUserLoginMethods;
86
+ /** Mínimo que o resolver precisa dos métodos globais (evita import circular). */
87
+ export interface ResolvedAuthMethodsLike {
88
+ password: boolean;
89
+ magicLink: boolean;
90
+ passkey: boolean;
91
+ social: string[];
92
+ forgotPassword: boolean;
93
+ passkeyAutofill: boolean;
94
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Tipos de login POR USUÁRIO — preferência self-service do dono da conta.
3
+ *
4
+ * Diferente do runtime setting global `auth_methods` (que o operador controla via
5
+ * console admin), aqui é o PRÓPRIO usuário quem decide, no console de conta,
6
+ * quais métodos de login ficam habilitados para a SUA conta. A preferência é
7
+ * persistida na coluna `login_methods` (JSONB) de `auth.users` e SEMPRE se
8
+ * intersecta com os métodos globais efetivos: o usuário nunca pode LIGAR um
9
+ * método que o host desligou (config pin ou setting), só restringir.
10
+ *
11
+ * Contratos:
12
+ * - Coluna ausente no model → capacidade AUSENTE → feature no-op (mesmo padrão
13
+ * das demais colunas opcionais; hosts adotam por migração própria).
14
+ * - `NULL` na coluna = sem preferência = herda os globais (comportamento atual).
15
+ * - Fail-safe all-off: uma preferência que zere TODOS os métodos disponíveis
16
+ * deixa de restringir (nunca trancamos o usuário fora da própria conta).
17
+ */
18
+ export const USER_LOGIN_METHOD_KEYS = [
19
+ 'password',
20
+ 'magicLink',
21
+ 'passkey',
22
+ 'social',
23
+ ];
24
+ // ---------------------------------------------------------------------------
25
+ // Normalização / validação
26
+ // ---------------------------------------------------------------------------
27
+ function boolOrUndefined(v) {
28
+ return typeof v === 'boolean' ? v : undefined;
29
+ }
30
+ /**
31
+ * Normaliza um valor cru vindo do DB (JSONB) ou do request body para o shape
32
+ * canônico. Campos inválidos são descartados silenciosamente (fail-safe).
33
+ * Objetos vazios viram null (sem preferência).
34
+ */
35
+ export function normalizeUserLoginMethods(raw) {
36
+ if (raw === null || raw === undefined)
37
+ return null;
38
+ if (typeof raw !== 'object' || Array.isArray(raw))
39
+ return null;
40
+ const r = raw;
41
+ const out = {};
42
+ for (const key of USER_LOGIN_METHOD_KEYS) {
43
+ const v = boolOrUndefined(r[key]);
44
+ if (v !== undefined)
45
+ out[key] = v;
46
+ }
47
+ return Object.keys(out).length > 0 ? out : null;
48
+ }
49
+ /**
50
+ * Valida e normaliza o payload de update vindo do PUT da account API.
51
+ * Espera `{ methods: {...} }`; só aceita chaves conhecidas com valores booleanos.
52
+ * `{ methods: {} }` = limpar a preferência (voltar a herdar os globais).
53
+ */
54
+ export function parseUserLoginMethodsPayload(body) {
55
+ if (typeof body !== 'object' || body === null || Array.isArray(body)) {
56
+ return { ok: false, error: 'invalid_body' };
57
+ }
58
+ const b = body;
59
+ if (!('methods' in b) || typeof b.methods !== 'object' || b.methods === null) {
60
+ return { ok: false, error: 'invalid_methods' };
61
+ }
62
+ const methods = normalizeUserLoginMethods(b.methods);
63
+ // Objeto com só campos inválidos → trata como reset ({}).
64
+ return { ok: true, value: methods ?? {} };
65
+ }
66
+ // ---------------------------------------------------------------------------
67
+ // Resolver puro — interseção global × por-usuário
68
+ // ---------------------------------------------------------------------------
69
+ /**
70
+ * Interseção dos métodos globais efetivos com a preferência do usuário.
71
+ *
72
+ * Regras:
73
+ * - `pref` null/vazio → globais inalterados (herança).
74
+ * - Campo `false` na preferência DESLIGA o método (só pode restringir).
75
+ * - Campo `true` NUNCA liga método globalmente indisponível (interseção).
76
+ * - FAIL-SAFE all-off: se a preferência zerar todos os métodos disponíveis,
77
+ * volta aos globais (nunca deixar a conta sem nenhum método de entrada).
78
+ * Loga um aviso (console.warn) — sinaliza preferência órfã após o host ter
79
+ * desligado métodos globalmente.
80
+ * - Qualquer erro → globais (a chamada nunca deve derrubar o login).
81
+ */
82
+ export function resolveEffectiveUserLoginMethods(global, pref) {
83
+ try {
84
+ const p = normalizeUserLoginMethods(pref);
85
+ if (!p) {
86
+ return passthrough(global);
87
+ }
88
+ const password = applyPref(global.password, p.password);
89
+ const magicLink = applyPref(global.magicLink, p.magicLink);
90
+ const passkey = applyPref(global.passkey, p.passkey);
91
+ const social = p.social === false ? [] : global.social;
92
+ const resolved = {
93
+ password,
94
+ magicLink,
95
+ passkey,
96
+ social,
97
+ // forgotPassword é derivado do password (mesma derivação do global).
98
+ forgotPassword: password && global.forgotPassword,
99
+ passkeyAutofill: passkey && global.passkeyAutofill,
100
+ };
101
+ const allOff = !resolved.password &&
102
+ !resolved.magicLink &&
103
+ !resolved.passkey &&
104
+ resolved.social.length === 0;
105
+ if (allOff) {
106
+ console.warn('[authkit] user login_methods zerou todos os métodos — ignorando a preferência (fail-safe).');
107
+ return passthrough(global);
108
+ }
109
+ return resolved;
110
+ }
111
+ catch {
112
+ // Nunca derruba o login por causa da preferência.
113
+ return passthrough(global);
114
+ }
115
+ }
116
+ /** Interseção: false do usuário desliga; true não liga o que o global não tem. */
117
+ function applyPref(globalValue, prefValue) {
118
+ if (prefValue === false)
119
+ return false;
120
+ return globalValue;
121
+ }
122
+ /** Cópia dos métodos globais como resultado por-usuário (sem preferência). */
123
+ function passthrough(global) {
124
+ return {
125
+ password: global.password,
126
+ magicLink: global.magicLink,
127
+ passkey: global.passkey,
128
+ social: [...(global.social ?? [])],
129
+ forgotPassword: global.forgotPassword,
130
+ passkeyAutofill: global.passkeyAutofill,
131
+ };
132
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/authkit-server",
3
- "version": "0.59.0",
3
+ "version": "0.61.0",
4
4
  "description": "AdonisJS OIDC/OAuth2 provider (Identity Provider) toolkit: ejectable auth server with sessions, rate-limiting, MFA/TOTP, audit log, federated logout and OpenTelemetry metrics.",
5
5
  "license": "MIT",
6
6
  "author": "dudousxd",
@@ -23,7 +23,8 @@
23
23
  "identity-provider",
24
24
  "rate-limiting",
25
25
  "mfa",
26
- "totp"
26
+ "totp",
27
+ "tanstack-intent"
27
28
  ],
28
29
  "publishConfig": {
29
30
  "access": "public"
@@ -33,7 +34,8 @@
33
34
  "types": "./build/index.d.ts",
34
35
  "files": [
35
36
  "build",
36
- "stubs"
37
+ "stubs",
38
+ "skills"
37
39
  ],
38
40
  "exports": {
39
41
  ".": "./build/index.js",
@@ -113,6 +115,7 @@
113
115
  "devDependencies": {
114
116
  "@adonis-agora/durable": "0.22.0",
115
117
  "@adonis-agora/telescope": "0.6.0",
118
+ "@tanstack/intent": "^0.3.2",
116
119
  "@adonisjs/ally": "6.3.0",
117
120
  "@adonisjs/auth": "10.1.0",
118
121
  "@adonisjs/core": "7.4.0",
@@ -150,7 +153,7 @@
150
153
  "react-error-boundary": "6.1.2",
151
154
  "nuqs": "2.9.5",
152
155
  "recharts": "3.10.1",
153
- "@adonis-agora/authkit-react": "0.19.1"
156
+ "@adonis-agora/authkit-react": "0.20.1"
154
157
  },
155
158
  "scripts": {
156
159
  "build": "node scripts/build_host_css.mjs && node scripts/build_webauthn.mjs && node scripts/build_ui.mjs && node -e \"const fs=require('node:fs');for(const d of ['build/stubs','build/host/views'])fs.rmSync(d,{recursive:true,force:true})\" && tsc && node -e \"require('node:fs').cpSync('src/host/assets','build/src/host/assets',{recursive:true})\" && node -e \"require('node:fs').cpSync('stubs','build/stubs',{recursive:true,filter:(s)=>!s.endsWith('.ts')})\" && node -e \"const fs=require('node:fs');if(fs.existsSync('assets'))fs.cpSync('assets','build/assets',{recursive:true})\" && node -e \"require('node:fs').cpSync('src/host/views','build/host/views',{recursive:true})\" && node -e \"const fs=require('node:fs');fs.mkdirSync('build/host/ui',{recursive:true});fs.readdirSync('src/host/ui').filter(f=>f.endsWith('.html')).forEach(f=>fs.copyFileSync('src/host/ui/'+f,'build/host/ui/'+f))\" && node -e \"require('node:fs').copyFileSync('commands/commands.json','build/commands/commands.json')\" && node -e \"const fs=require('node:fs');fs.mkdirSync('build/src/password',{recursive:true});fs.copyFileSync('src/password/common_passwords.txt','build/src/password/common_passwords.txt')\"",
@@ -0,0 +1,222 @@
1
+ ---
2
+ name: authkit-idp-setup
3
+ description: >-
4
+ Set up the @adonis-agora/authkit-server OIDC/OAuth2 Authorization Server (Identity
5
+ Provider) in an AdonisJS app. Covers `node ace add @adonis-agora/authkit-server`,
6
+ defineConfig({ issuer, adapter, jwks, ttl, globalRolesClaim, accountStore }) with
7
+ adapters.redis({ connection }) vs adapters.database({ connection? }),
8
+ lucidAccountStore(AuthUser), JWKS management ({ source: 'managed' | 'jwks' } or
9
+ 'auto', keystore store file/drive/lucid/redis/hashicorp vaults plus
10
+ @adonis-agora/authkit-vault-aws/azure/gcp createKeystoreVault add-ons),
11
+ registerOidcRoutes(router, { mountPath?, metrics?, dashboard? }), mounting consoles
12
+ via registerAuthHost(router, { admin, adminApi, account... }) or config.routes,
13
+ and AUTHKIT_ISSUER / observability env. Use when booting an IdP app, wiring
14
+ config/authkit.ts, choosing a persistence adapter, managing signing keys, or
15
+ debugging 404s on /oidc, /account/*, /admin.
16
+ license: MIT
17
+ metadata:
18
+ type: core
19
+ library: "@adonis-agora/authkit-server"
20
+ library_version: "0.60.0"
21
+ framework: adonisjs
22
+ sources:
23
+ - "DavideCarvalho/adonis-authkit:README.md"
24
+ - "DavideCarvalho/adonis-authkit:packages/authkit-server/README.md"
25
+ - "DavideCarvalho/adonis-authkit:packages/authkit-server/src/define_config.ts"
26
+ - "DavideCarvalho/adonis-authkit:packages/authkit-server/src/register_routes.ts"
27
+ - "DavideCarvalho/adonis-authkit:packages/authkit-server/stubs/config/authkit.stub"
28
+ ---
29
+
30
+ # Setting up the AuthKit Authorization Server
31
+
32
+ `@adonis-agora/authkit-server` turns an AdonisJS app into an OpenID Connect /
33
+ OAuth2 Authorization Server (IdP) on top of `oidc-provider`. Setup has three
34
+ independent pieces that all must exist before anything works: **config**
35
+ (`config/authkit.ts` via `defineConfig`), **OIDC routes** (`registerOidcRoutes`),
36
+ and the host routes/consoles (`registerAuthHost` or `config.routes`). The login
37
+ screens are a separate concern — see `authkit-interactions`.
38
+
39
+ ## Setup
40
+
41
+ Install and run the configurator:
42
+
43
+ ```bash
44
+ node ace add @adonis-agora/authkit-server
45
+ ```
46
+
47
+ `configure` publishes `config/authkit.ts`, the model `app/models/auth_user.ts`, the
48
+ interaction controller stub and registers the provider. Then wire the routes:
49
+
50
+ ```ts
51
+ // start/routes.ts
52
+ import router from '@adonisjs/core/services/router'
53
+ import { registerOidcRoutes } from '@adonis-agora/authkit-server'
54
+
55
+ registerOidcRoutes(router) // mounts the oidc-provider catch-all at /oidc
56
+ ```
57
+
58
+ Point `AUTHKIT_ISSUER` at `<host>/oidc` — the issuer must end with the mount path.
59
+
60
+ Source: `packages/authkit-server/README.md`, `src/register_routes.ts`.
61
+
62
+ ## Core patterns
63
+
64
+ ### Pattern 1 — `defineConfig`: issuer, adapter, accountStore
65
+
66
+ ```ts
67
+ // config/authkit.ts
68
+ import env from '#start/env'
69
+ import AuthUser from '#models/auth_user'
70
+ import { defineConfig, adapters, lucidAccountStore } from '@adonis-agora/authkit-server'
71
+
72
+ const authServerConfig = defineConfig({
73
+ issuer: env.get('AUTHKIT_ISSUER'),
74
+ adapter: adapters.redis({ connection: 'main' }), // or adapters.database()
75
+ jwks: { source: 'managed', algorithm: 'RS256' },
76
+ ttl: { accessToken: '15m', refreshToken: '30d' },
77
+ globalRolesClaim: 'roles',
78
+ accountStore: lucidAccountStore(AuthUser),
79
+ })
80
+
81
+ export default authServerConfig
82
+ ```
83
+
84
+ `adapter` is where oidc-provider persists grants/tokens/codes: `adapters.redis({
85
+ connection })` requires `@adonisjs/redis` configured; `adapters.database({
86
+ connection? })` uses Lucid and requires running the `authkit_oidc_payloads`
87
+ migration. `accountStore` is the identity contract — `lucidAccountStore(AuthUser)`
88
+ derives `findAccount`/`verifyCredentials` from your user model.
89
+
90
+ Source: `stubs/config/authkit.stub`, `src/define_config.ts` (`AuthServerConfigInput`),
91
+ `src/adapters/factory.ts`, README § Persistência.
92
+
93
+ ### Pattern 2 — signing keys: managed JWKS, `'auto'`, and inline keys
94
+
95
+ ```ts
96
+ defineConfig({
97
+ issuer: env.get('AUTHKIT_ISSUER'),
98
+ adapter: adapters.redis({ connection: 'main' }),
99
+ // Ephemeral-friendly: AUTHKIT_JWKS inline JSON wins, else file-managed keystore.
100
+ jwks: 'auto',
101
+ accountStore: lucidAccountStore(AuthUser),
102
+ })
103
+ ```
104
+
105
+ Explicit forms: `{ source: 'managed' }` generates/rotates/persists the private
106
+ keystore (`rotationDays`, `algorithm`, `encrypt`, and `store` choosing where the
107
+ blob lives); `{ source: 'jwks', keys }` supplies keys inline.
108
+
109
+ The managed keystore's `store` accepts a path shortcut (`store: 'tmp/keystore.json'`)
110
+ or `{ driver: ... }`: built-in vaults are `file`, `drive` (a `@adonisjs/drive` disk),
111
+ `lucid` (table created on first write), `redis`, and `hashicorp`; for cloud secret
112
+ managers install the add-ons `@adonis-agora/authkit-vault-aws` / `-azure` / `-gcp`
113
+ and pass their `createKeystoreVault(...)` result as `store` (any object with a
114
+ `read()` method is accepted). With no `store` at all, keys are ephemeral per boot.
115
+
116
+ Rotation tooling ships as `node ace authkit:rotate-keys`; health checks via
117
+ `node ace authkit:doctor`.
118
+
119
+ Source: `src/keys/keystore_manager.ts` (`resolveKeystoreVault`),
120
+ `packages/authkit-core/src/types/server_config.ts` (`JwksConfig`), index.ts exports.
121
+
122
+ ### Pattern 3 — mounting consoles with `registerAuthHost` (or `config.routes`)
123
+
124
+ ```ts
125
+ // start/routes.ts
126
+ import router from '@adonisjs/core/services/router'
127
+ import { registerOidcRoutes, registerAuthHost } from '@adonis-agora/authkit-server'
128
+
129
+ registerOidcRoutes(router)
130
+ const authkitRoutes = registerAuthHost(router, {
131
+ mountPath: '/oidc',
132
+ admin: true, // admin console under /admin
133
+ adminApi: true, // Admin REST API under /api/authkit/v1
134
+ })
135
+ // Hand authkitRoutes to your frontend instead of hardcoding hrefs.
136
+ router.get('/', async ({ inertia }) => inertia.render('home', { authkitRoutes }))
137
+ ```
138
+
139
+ Alternatively set `routes: true` in `config/authkit.ts` to auto-mount in the
140
+ provider's `boot()` (before `start/routes.ts`) — then do NOT also call
141
+ `registerAuthHost` manually (double registration crashes the boot). Policy flags
142
+ (`admin`, `adminApi`, `social`, `rateLimit`) declared in `defineConfig` LOCK the
143
+ on/off switch: `registerAuthHost` may only adjust structural prefixes.
144
+
145
+ Observability routes are opt-in on the OIDC mount itself:
146
+ `registerOidcRoutes(router, { metrics: true, dashboard: true })` mounts
147
+ `GET /authkit/metrics` (JSON snapshot) and `GET /authkit/dashboard` (embedded HTML).
148
+
149
+ Source: `src/host/register_auth_host.ts` (`AuthHostOptions`, policy-vs-structural),
150
+ `src/define_config.ts` (`routes`, `admin`, `adminApi` JSDoc), server README
151
+ § Observabilidade.
152
+
153
+ ## Common mistakes
154
+
155
+ ### CRITICAL — Issuer not ending with the mount path
156
+
157
+ ```ts
158
+ // Wrong — issuer says /oauth but routes mount at default /oidc
159
+ defineConfig({ issuer: 'https://idp.example.com/oauth' })
160
+ registerOidcRoutes(router) // mounts /oidc/*
161
+ ```
162
+
163
+ ```ts
164
+ // Correct — issuer ends exactly at the mount path
165
+ defineConfig({ issuer: 'https://idp.example.com/oidc' })
166
+ registerOidcRoutes(router) // default mountPath '/oidc' matches the issuer
167
+ ```
168
+
169
+ oidc-provider routes internally UNDER the issuer URL, so a trailing mismatch makes
170
+ discovery and every protocol endpoint unreachable while the app otherwise boots fine.
171
+
172
+ Source: `packages/authkit-server/README.md` § Montar as rotas OIDC;
173
+ `src/host/register_auth_host.ts` (`mountPath` doc: "Deve casar com o final do issuer").
174
+
175
+ ### HIGH — Consoles/account screens 404 because no host routes were mounted
176
+
177
+ ```ts
178
+ // Wrong — only the protocol endpoints exist; /account/* and /admin are dead
179
+ registerOidcRoutes(router)
180
+ ```
181
+
182
+ ```ts
183
+ // Correct — mount the host surface too (or set config.routes: true)
184
+ registerOidcRoutes(router)
185
+ registerAuthHost(router, { mountPath: '/oidc', admin: true })
186
+ ```
187
+
188
+ `registerOidcRoutes` only registers the oidc-provider catch-all; everything else
189
+ (account console `/account/*`, admin console, Admin API, sudo routes) exists solely
190
+ after `registerAuthHost` runs or `config.routes` auto-mounts them. Conversely,
191
+ calling `registerAuthHost` while `config.routes: true` double-registers named routes
192
+ and throws at boot — pick one.
193
+
194
+ Source: `src/define_config.ts` (`routes` JSDoc: "duplo registro ... derrubam o boot"),
195
+ `src/register_routes.ts`.
196
+
197
+ ### HIGH — `jwks: 'auto'` in production without AUTHKIT_JWKS
198
+
199
+ ```ts
200
+ // Wrong — prod deploy with no AUTHKIT_JWKS env: falls back to a file keystore in tmp/
201
+ jwks: 'auto'
202
+ ```
203
+
204
+ ```ts
205
+ // Correct — explicit managed keystore persisted in a durable vault for prod
206
+ jwks: {
207
+ source: 'managed',
208
+ algorithm: 'RS256',
209
+ store: { driver: 'lucid' }, // or redis/drive/hashicorp/vault add-on
210
+ }
211
+ ```
212
+
213
+ `'auto'` prefers the `AUTHKIT_JWKS` env JSON but silently degrades to
214
+ `tmp/authkit_jwks.json` on disk — on ephemeral filesystems every restart mints new
215
+ signing keys and every issued token fails validation after redeploy.
216
+
217
+ Source: `src/define_config.ts` (`jwks` JSDoc: "'auto' (recomendado p/ deploys
218
+ efêmeros)... senão cai no managed persistido em arquivo"),
219
+ `packages/authkit-core/src/types/server_config.ts`.
220
+
221
+ See also: `authkit-interactions/SKILL.md` — after setup, the authorization flow ends
222
+ at the interaction screens, which the host must register and render itself.