@venizia/ignis-docs 0.2.0 → 0.2.1-1
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.
- package/README.md +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +24 -13
- package/content/best-practices/architecture-decisions.md +29 -16
- package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
- package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
- package/content/best-practices/code-style-standards/control-flow.md +33 -1
- package/content/best-practices/code-style-standards/documentation.md +28 -8
- package/content/best-practices/code-style-standards/function-patterns.md +15 -4
- package/content/best-practices/code-style-standards/index.md +7 -2
- package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
- package/content/best-practices/code-style-standards/route-definitions.md +9 -5
- package/content/best-practices/code-style-standards/tooling.md +14 -1
- package/content/best-practices/code-style-standards/type-safety.md +39 -6
- package/content/best-practices/common-pitfalls.md +59 -35
- package/content/best-practices/contribution-workflow.md +8 -4
- package/content/best-practices/data-modeling.md +78 -62
- package/content/best-practices/deployment-strategies.md +56 -19
- package/content/best-practices/error-handling.md +247 -153
- package/content/best-practices/index.md +6 -0
- package/content/best-practices/performance-optimization.md +26 -13
- package/content/best-practices/security-guidelines.md +35 -9
- package/content/best-practices/testing-strategies.md +7 -2
- package/content/best-practices/troubleshooting-tips.md +27 -17
- package/content/extensions/components/api-reference.md +109 -323
- package/content/extensions/components/authentication/api.md +489 -602
- package/content/extensions/components/authentication/errors.md +135 -498
- package/content/extensions/components/authentication/index.md +89 -801
- package/content/extensions/components/authentication/usage.md +231 -955
- package/content/extensions/components/authorization/api.md +991 -644
- package/content/extensions/components/authorization/errors.md +204 -208
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +88 -795
- package/content/extensions/components/authorization/usage.md +219 -528
- package/content/extensions/components/health-check.md +77 -243
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +553 -285
- package/content/extensions/components/mail/errors.md +76 -62
- package/content/extensions/components/mail/index.md +111 -463
- package/content/extensions/components/mail/usage.md +139 -173
- package/content/extensions/components/request-tracker.md +70 -173
- package/content/extensions/components/socket-io/api.md +462 -785
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +69 -372
- package/content/extensions/components/socket-io/usage.md +212 -105
- package/content/extensions/components/static-asset/api.md +461 -141
- package/content/extensions/components/static-asset/errors.md +121 -53
- package/content/extensions/components/static-asset/index.md +84 -606
- package/content/extensions/components/static-asset/usage.md +184 -299
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +311 -404
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +75 -407
- package/content/extensions/components/websocket/usage.md +120 -338
- package/content/extensions/helpers/cron/index.md +52 -160
- package/content/extensions/helpers/crypto/index.md +67 -480
- package/content/extensions/helpers/crypto/reference.md +528 -0
- package/content/extensions/helpers/env/index.md +64 -178
- package/content/extensions/helpers/error/index.md +286 -196
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +76 -550
- package/content/extensions/helpers/inversion/reference.md +530 -0
- package/content/extensions/helpers/kafka/admin.md +23 -3
- package/content/extensions/helpers/kafka/compile-binary.md +83 -52
- package/content/extensions/helpers/kafka/consumer.md +65 -28
- package/content/extensions/helpers/kafka/examples.md +46 -253
- package/content/extensions/helpers/kafka/index.md +55 -617
- package/content/extensions/helpers/kafka/producer.md +156 -22
- package/content/extensions/helpers/kafka/schema-registry.md +59 -79
- package/content/extensions/helpers/logger/hf-logger.md +220 -0
- package/content/extensions/helpers/logger/index.md +78 -552
- package/content/extensions/helpers/logger/pino.md +105 -0
- package/content/extensions/helpers/logger/reference.md +937 -0
- package/content/extensions/helpers/network/api.md +276 -195
- package/content/extensions/helpers/network/index.md +87 -524
- package/content/extensions/helpers/queue/index.md +80 -897
- package/content/extensions/helpers/queue/reference.md +494 -0
- package/content/extensions/helpers/redis/index.md +86 -640
- package/content/extensions/helpers/redis/reference.md +784 -0
- package/content/extensions/helpers/secrets/index.md +136 -0
- package/content/extensions/helpers/socket-io/api.md +325 -204
- package/content/extensions/helpers/socket-io/index.md +79 -429
- package/content/extensions/helpers/storage/api.md +584 -464
- package/content/extensions/helpers/storage/index.md +80 -573
- package/content/extensions/helpers/types/index.md +75 -495
- package/content/extensions/helpers/types/reference.md +689 -0
- package/content/extensions/helpers/uid/index.md +176 -189
- package/content/extensions/helpers/websocket/api.md +366 -214
- package/content/extensions/helpers/websocket/index.md +68 -503
- package/content/extensions/helpers/worker-thread/index.md +62 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/extensions/index.md +38 -39
- package/content/extensions/src-details/mcp-server.md +96 -548
- package/content/guides/core-concepts/persistent/datasources.md +2 -2
- package/content/guides/core-concepts/persistent/index.md +5 -1
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/pglite.md +218 -0
- package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
- package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
- package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/get-started/5-minute-quickstart.md +59 -206
- package/content/guides/get-started/philosophy.md +135 -670
- package/content/guides/get-started/setup.md +53 -74
- package/content/guides/migrations/redis-helpers-migration.md +5 -4
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +8 -7
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +64 -22
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +600 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +25 -46
- package/content/references/base/filter-system/application-usage.md +95 -123
- package/content/references/base/filter-system/array-operators.md +33 -43
- package/content/references/base/filter-system/comparison-operators.md +55 -63
- package/content/references/base/filter-system/default-filter.md +144 -350
- package/content/references/base/filter-system/fields-order-pagination.md +116 -148
- package/content/references/base/filter-system/index.md +114 -253
- package/content/references/base/filter-system/json-filtering.md +52 -181
- package/content/references/base/filter-system/list-operators.md +28 -44
- package/content/references/base/filter-system/logical-operators.md +69 -114
- package/content/references/base/filter-system/null-operators.md +41 -98
- package/content/references/base/filter-system/pattern-matching.md +48 -51
- package/content/references/base/filter-system/quick-reference.md +93 -196
- package/content/references/base/filter-system/range-operators.md +22 -38
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +173 -232
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +43 -28
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +81 -1452
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +281 -419
- package/content/references/base/repositories/index.md +84 -644
- package/content/references/base/repositories/mixins.md +25 -21
- package/content/references/base/repositories/relations.md +194 -375
- package/content/references/base/repositories/soft-deletable.md +67 -55
- package/content/references/base/secrets.md +267 -0
- package/content/references/base/services.md +8 -6
- package/content/references/configuration/environment-variables.md +73 -21
- package/content/references/configuration/index.md +51 -31
- package/content/references/index.md +1 -1
- package/content/references/quick-reference.md +3 -16
- package/content/references/utilities/crypto.md +35 -76
- package/content/references/utilities/date.md +33 -73
- package/content/references/utilities/duration.md +85 -0
- package/content/references/utilities/index.md +6 -2
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +81 -61
- package/content/references/utilities/parse.md +34 -64
- package/content/references/utilities/performance.md +33 -58
- package/content/references/utilities/promise.md +28 -62
- package/content/references/utilities/request.md +58 -218
- package/content/references/utilities/retry.md +139 -0
- package/content/references/utilities/schema.md +43 -137
- package/content/references/utilities/statuses-reference.md +361 -0
- package/content/references/utilities/statuses.md +63 -667
- package/dist/mcp-server/index.js +0 -0
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
- package/package.json +24 -23
|
@@ -1,552 +1,553 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Authentication Reference
|
|
3
|
+
description: Full option tables, binding keys, service class hierarchy, strategy registry, and controller factory for the Authentication component
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
# Authentication Reference
|
|
8
|
+
|
|
9
|
+
Every option, binding key, class, and method the Authentication component exposes. See the [Overview](./) for the guided introduction.
|
|
10
|
+
|
|
11
|
+
**Files:**
|
|
12
|
+
|
|
13
|
+
- [`packages/core-server/src/components/auth/authenticate/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate) - component, services, strategies, controllers
|
|
14
|
+
- [`packages/core-server/src/components/auth/models/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/models) - entity column helpers + request schemas
|
|
15
|
+
- [`packages/core-server/src/components/auth/base/abstract-auth-registry.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/base/abstract-auth-registry.ts) - `AbstractAuthRegistry`
|
|
16
|
+
|
|
17
|
+
## Find what you need
|
|
18
|
+
|
|
19
|
+
| You want to | Go to |
|
|
20
|
+
|---|---|
|
|
21
|
+
| See how the pieces fit together | [Architecture](#architecture) |
|
|
22
|
+
| Look up a binding key or option field | [Binding keys](#binding-keys), [Option interfaces](#option-interfaces) |
|
|
23
|
+
| Check what `IAuthUser` / `IAuthService` require | [IAuthUser / IJWTTokenPayload](#iauthuser-ijwttokenpayload), [IAuthService](#iauthservice) |
|
|
24
|
+
| Understand the JWS/JWKS/Basic class hierarchy | [Service class hierarchy](#service-class-hierarchy) |
|
|
25
|
+
| Register or resolve a strategy programmatically | [Strategy registry](#strategy-registry) |
|
|
26
|
+
| See exactly what the built-in `/auth` controller builds | [Controller factory](#controller-factory) |
|
|
27
|
+
| Find a file on disk | [File structure](#file-structure) |
|
|
28
|
+
|
|
29
|
+
## Import paths
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import {
|
|
33
|
+
// Component + registry
|
|
34
|
+
AuthenticateComponent,
|
|
35
|
+
AuthenticateBindingKeys,
|
|
36
|
+
Authentication,
|
|
37
|
+
AuthenticationFieldCodecs,
|
|
38
|
+
AuthenticationModes,
|
|
39
|
+
AuthenticationTokenTypes,
|
|
40
|
+
AuthenticationStrategyRegistry,
|
|
41
|
+
ServiceAssertion,
|
|
42
|
+
|
|
43
|
+
// JOSE standards + constants
|
|
44
|
+
JOSEStandards,
|
|
45
|
+
JWKSModes,
|
|
46
|
+
JWKSKeyDrivers,
|
|
47
|
+
JWKSKeyFormats,
|
|
48
|
+
|
|
49
|
+
// Strategies
|
|
50
|
+
JWSAuthenticationStrategy,
|
|
51
|
+
JWKSIssuerAuthenticationStrategy,
|
|
52
|
+
JWKSVerifierAuthenticationStrategy,
|
|
53
|
+
BasicAuthenticationStrategy,
|
|
54
|
+
ServiceAuthenticationStrategy,
|
|
55
|
+
|
|
56
|
+
// Services
|
|
57
|
+
AbstractBearerTokenService,
|
|
58
|
+
JWSTokenService,
|
|
59
|
+
JWKSIssuerTokenService,
|
|
60
|
+
JWKSVerifierTokenService,
|
|
61
|
+
BasicTokenService,
|
|
62
|
+
|
|
63
|
+
// Controllers
|
|
64
|
+
defineAuthController,
|
|
65
|
+
JWKSController,
|
|
66
|
+
authenticate,
|
|
67
|
+
|
|
68
|
+
// Entity column helpers
|
|
69
|
+
extraUserColumns,
|
|
70
|
+
extraRoleColumns,
|
|
71
|
+
extraPermissionColumns,
|
|
72
|
+
extraPolicyDefinitionColumns,
|
|
73
|
+
UserStatuses,
|
|
74
|
+
UserTypes,
|
|
75
|
+
RoleStatuses,
|
|
76
|
+
} from '@venizia/ignis';
|
|
77
|
+
|
|
78
|
+
import type {
|
|
79
|
+
TAuthenticationRestOptions,
|
|
80
|
+
TJWTTokenServiceOptions,
|
|
81
|
+
IJWSTokenServiceOptions,
|
|
82
|
+
IJWKSIssuerOptions,
|
|
83
|
+
IJWKSVerifierOptions,
|
|
84
|
+
TJWKSTokenServiceOptions,
|
|
85
|
+
TBasicTokenServiceOptions,
|
|
86
|
+
IServiceAuthOptions,
|
|
87
|
+
IServiceAssertionClaims,
|
|
88
|
+
TServiceCallerEntry,
|
|
89
|
+
IAuthenticateOptions,
|
|
90
|
+
IAuthUser,
|
|
91
|
+
IJWTTokenPayload,
|
|
92
|
+
IPayloadFieldCodec,
|
|
93
|
+
IAuthService,
|
|
94
|
+
IAuthenticationStrategy,
|
|
95
|
+
TDefineAuthControllerOpts,
|
|
96
|
+
TAuthStrategy,
|
|
97
|
+
TAuthMode,
|
|
98
|
+
TGetTokenExpiresFn,
|
|
99
|
+
TJWKSAlgorithm,
|
|
100
|
+
TJWKSKeyDriver,
|
|
101
|
+
TJWKSKeyFormat,
|
|
102
|
+
TJOSEStandard,
|
|
103
|
+
TJWKSMode,
|
|
104
|
+
TPermissionOptions,
|
|
105
|
+
TPermissionCommonColumns,
|
|
106
|
+
TPolicyDefinitionOptions,
|
|
107
|
+
TPolicyDefinitionCommonColumns,
|
|
108
|
+
} from '@venizia/ignis';
|
|
109
|
+
```
|
|
4
110
|
|
|
5
111
|
## Architecture
|
|
6
112
|
|
|
7
113
|
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
│
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
│ 3. defineBasicAuth() → BasicTokenService │
|
|
31
|
-
│ 4. defineControllers() → AuthController (factory-built) │
|
|
32
|
-
│ 5. defineOAuth2() → stub (not yet implemented) │
|
|
33
|
-
└────────────────────────────┬─────────────────────────────────────┘
|
|
34
|
-
│
|
|
35
|
-
┌─────────────────────┼──────────────────────┐
|
|
36
|
-
▼ ▼ ▼
|
|
37
|
-
┌──────────────┐ ┌────────────────┐ ┌──────────────────┐
|
|
38
|
-
│ Bearer Token │ │ BasicToken │ │ AuthController │
|
|
39
|
-
│ Services │ │ Service │ │ (factory-built) │
|
|
40
|
-
│ (see below) │ └───────┬────────┘ └────────┬─────────┘
|
|
41
|
-
└──────┬───────┘ │ │
|
|
42
|
-
│ ▼ ▼
|
|
43
|
-
│ ┌──────────────┐ ┌──────────────────┐
|
|
44
|
-
│ │ Basic │ │ /sign-in │
|
|
45
|
-
│ │ Strategy │ │ /sign-up │
|
|
46
|
-
│ └──────────────┘ │ /change-password │
|
|
47
|
-
│ │ /who-am-i │
|
|
48
|
-
▼ └──────────────────┘
|
|
49
|
-
┌──────────────────────────────────────────────────────────┐
|
|
50
|
-
│ Bearer Token Service Hierarchy │
|
|
51
|
-
│ │
|
|
52
|
-
│ AbstractBearerTokenService (base) │
|
|
53
|
-
│ ├── extractCredentials() │
|
|
54
|
-
│ ├── verify() → doVerify() │
|
|
55
|
-
│ ├── generate() → getSigner() + getSigningKey() │
|
|
56
|
-
│ ├── encryptPayload() / decryptPayload() (optional) │
|
|
57
|
-
│ │ │
|
|
58
|
-
│ ├── JWSTokenService (symmetric HS256) │
|
|
59
|
-
│ │ ├── JWSAuthenticationStrategy │
|
|
60
|
-
│ │ └── sign with shared secret │
|
|
61
|
-
│ │ │
|
|
62
|
-
│ └── AbstractJWKSTokenService (lazy-init + retry) │
|
|
63
|
-
│ ├── ensureInitialized() / initialize() │
|
|
64
|
-
│ │ │
|
|
65
|
-
│ ├── JWKSIssuerTokenService (asymmetric) │
|
|
66
|
-
│ │ ├── JWKSIssuerAuthenticationStrategy │
|
|
67
|
-
│ │ ├── sign with private key │
|
|
68
|
-
│ │ ├── verify with public key │
|
|
69
|
-
│ │ └── getJWKS() / getJWKSAsync() │
|
|
70
|
-
│ │ │
|
|
71
|
-
│ └── JWKSVerifierTokenService (remote verify) │
|
|
72
|
-
│ ├── JWKSVerifierAuthenticationStrategy │
|
|
73
|
-
│ └── verify via createRemoteJWKSet() │
|
|
74
|
-
└──────────────────────────────────────────────────────────┘
|
|
114
|
+
Application.preConfigure()
|
|
115
|
+
├── bind JWT_OPTIONS (TJWTTokenServiceOptions, discriminated on `standard`)
|
|
116
|
+
├── bind BASIC_OPTIONS / REST_OPTIONS
|
|
117
|
+
├── this.component(AuthenticateComponent)
|
|
118
|
+
└── AuthenticationStrategyRegistry.register() -- manual, after the component
|
|
119
|
+
|
|
120
|
+
AuthenticateComponent.binding()
|
|
121
|
+
├── switch on jwtOptions.standard
|
|
122
|
+
│ ├── JWS -> defineJWSAuth() -> registers JWSTokenService
|
|
123
|
+
│ └── JWKS -> defineJWKSAuth() -> switch on mode
|
|
124
|
+
│ ├── issuer -> JWKSIssuerTokenService + JWKSController (/certs)
|
|
125
|
+
│ └── verifier -> JWKSVerifierTokenService
|
|
126
|
+
├── defineBasicAuth() -> registers BasicTokenService (if basicOptions bound)
|
|
127
|
+
├── defineControllers() -> registers AuthController (if useAuthController: true)
|
|
128
|
+
└── defineOAuth2() -> stub, not implemented
|
|
129
|
+
|
|
130
|
+
Bearer token service hierarchy:
|
|
131
|
+
AbstractBearerTokenService (extractCredentials, verify, generate, encryptPayload/decryptPayload)
|
|
132
|
+
├── JWSTokenService (symmetric HS256)
|
|
133
|
+
└── AbstractJWKSTokenService (lazy ensureInitialized() + retry-on-failure)
|
|
134
|
+
├── JWKSIssuerTokenService (sign + verify + getJWKS/getJWKSAsync)
|
|
135
|
+
└── JWKSVerifierTokenService (verify only, via createRemoteJWKSet())
|
|
75
136
|
```
|
|
76
137
|
|
|
77
|
-
|
|
138
|
+
**Tech stack**
|
|
78
139
|
|
|
79
140
|
| Technology | Purpose |
|
|
80
141
|
|------------|---------|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
|
85
|
-
| **Drizzle ORM** | Database access for user lookup (in your implementation) |
|
|
142
|
+
| `jose` | JWT signing (`SignJWT`), verification (`jwtVerify`), JWKS (`createRemoteJWKSet`, `exportJWK`, `importPKCS8`, `importSPKI`, `importJWK`) |
|
|
143
|
+
| `@venizia/ignis-helpers` | `AES` payload encryption, `BaseHelper`/`BaseService`, `getError`, `HTTP` result codes |
|
|
144
|
+
| Hono middleware | Route-level integration via `createMiddleware` from `hono/factory` |
|
|
145
|
+
| `node:fs/promises` | Async key file reads for JWKS |
|
|
86
146
|
|
|
87
|
-
## Component
|
|
147
|
+
## Component methods
|
|
88
148
|
|
|
89
|
-
|
|
149
|
+
`AuthenticateComponent.binding()` runs four private configuration methods and one public stub:
|
|
90
150
|
|
|
91
151
|
| Method | Purpose |
|
|
92
152
|
|--------|---------|
|
|
93
|
-
| `defineJWSAuth(opts)` | Validates
|
|
94
|
-
| `defineJWKSAuth(opts)` | Switches on `mode
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
153
|
+
| `defineJWSAuth(opts)` | Validates `jwtSecret` and `getTokenExpiresFn`, binds `IJWSTokenServiceOptions` to `JWT_OPTIONS`, registers `JWSTokenService` |
|
|
154
|
+
| `defineJWKSAuth(opts)` | Switches on `mode`.<br>**Issuer:** validates keys/format/kid/getTokenExpiresFn, binds to `JWKS_OPTIONS`, registers `JWKSIssuerTokenService` + `JWKSController`.<br>**Verifier:** validates `jwksUrl`, binds to `JWKS_OPTIONS`, registers `JWKSVerifierTokenService` |
|
|
155
|
+
| `defineServiceAuth(opts)` | Validates `name` and `resolvePrincipal`, registers the verifier and the `service` strategy. With `keys` present it also registers the signer and mounts the certs route. Absent options: does nothing |
|
|
156
|
+
| `defineBasicAuth(opts)` | Validates `verifyCredentials` presence, registers `BasicTokenService`. Skips (debug log) if `basicOptions` not bound |
|
|
157
|
+
| `defineControllers(opts)` | Requires `jwtOptions` when `useAuthController: true`. Calls `defineAuthController()` and registers the generated controller |
|
|
158
|
+
| `defineOAuth2()` | Public stub, called during `binding()`, performs no action - not yet implemented |
|
|
98
159
|
|
|
99
160
|
> [!NOTE]
|
|
100
|
-
> The component reads
|
|
161
|
+
> The component reads the discriminated union from `JWT_OPTIONS`. It then re-binds only the inner options object - `IJWSTokenServiceOptions`, or `IJWKSIssuerOptions`/`IJWKSVerifierOptions` - to `JWKS_OPTIONS` or back to `JWT_OPTIONS`. The token service resolves it with a plain `@inject`.
|
|
101
162
|
|
|
102
|
-
##
|
|
163
|
+
## Binding keys
|
|
103
164
|
|
|
104
|
-
|
|
165
|
+
| Constant | Key string | Type | Required | Default |
|
|
166
|
+
|----------|-----------|------|----------|---------|
|
|
167
|
+
| `AuthenticateBindingKeys.REST_OPTIONS` | `@app/authenticate/rest-options` | `TAuthenticationRestOptions` | No | <code v-pre>{ useAuthController: false }</code> |
|
|
168
|
+
| `AuthenticateBindingKeys.JWT_OPTIONS` | `@app/authenticate/jwt-options` | `TJWTTokenServiceOptions` | Conditional | -- |
|
|
169
|
+
| `AuthenticateBindingKeys.JWKS_OPTIONS` | `@app/authenticate/jwks-options` | `IJWKSIssuerOptions \| IJWKSVerifierOptions` | Internal | Bound by the component from `JWT_OPTIONS` |
|
|
170
|
+
| `AuthenticateBindingKeys.BASIC_OPTIONS` | `@app/authenticate/basic-options` | `TBasicTokenServiceOptions` | Conditional | -- |
|
|
171
|
+
| `AuthenticateBindingKeys.SERVICE_OPTIONS` | `@app/authenticate/service-options` | `IServiceAuthOptions` | Conditional | -- |
|
|
172
|
+
| `AuthenticateBindingKeys.SERVICE_CERTS_PATH` | `@app/authenticate/service-certs-path` | `string` | Internal | Bound by the component from `serviceOptions.rest.path` |
|
|
105
173
|
|
|
106
|
-
|
|
174
|
+
> [!IMPORTANT]
|
|
175
|
+
> At least one of `JWT_OPTIONS`, `BASIC_OPTIONS` or `SERVICE_OPTIONS` must be bound, or `AuthenticateComponent.binding()` throws. A service that only VERIFIES assertions needs neither jwt nor basic.
|
|
107
176
|
|
|
108
|
-
|
|
109
|
-
|--------|-----------|---------|-------------|
|
|
110
|
-
| `getInstance()` | `static` | `AuthenticationStrategyRegistry` | Returns the singleton instance (creates if not exists) |
|
|
111
|
-
| `register` | <code v-pre>(opts: { container: Container; strategies: Array<{ name: string; strategy: TClass<IAuthenticationStrategy> }> }) => this</code> | `this` | Registers strategies as singletons in the container. Returns `this` for chaining. |
|
|
112
|
-
| `resolveStrategy` | `(opts: { name: string }) => IAuthenticationStrategy` | `IAuthenticationStrategy` | Resolves a strategy instance from the container by name |
|
|
177
|
+
## Option interfaces
|
|
113
178
|
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
AuthenticationStrategyRegistry.getInstance().register({
|
|
117
|
-
container: this,
|
|
118
|
-
strategies: [
|
|
119
|
-
{ name: Authentication.STRATEGY_JWT, strategy: JWKSIssuerAuthenticationStrategy },
|
|
120
|
-
{ name: Authentication.STRATEGY_BASIC, strategy: BasicAuthenticationStrategy },
|
|
121
|
-
],
|
|
122
|
-
});
|
|
123
|
-
```
|
|
179
|
+
### TJWTTokenServiceOptions
|
|
124
180
|
|
|
125
|
-
|
|
126
|
-
> `register()` returns `this`, enabling method chaining if needed.
|
|
127
|
-
|
|
128
|
-
**How it works:**
|
|
129
|
-
- Strategies are stored in an internal map and bound to the DI container as singletons
|
|
130
|
-
- Binding keys follow the pattern `authentication.strategy.{name}` (e.g., `authentication.strategy.jwt`, `authentication.strategy.basic`)
|
|
131
|
-
- The standalone `authenticate()` function creates middleware via `AuthenticationProvider`, which uses the registry to resolve strategies
|
|
132
|
-
|
|
133
|
-
**Middleware creation:**
|
|
134
|
-
|
|
135
|
-
The `authenticate()` function returns a Hono middleware that:
|
|
136
|
-
1. Checks if `Authentication.SKIP_AUTHENTICATION` is set on context - if true, skips entirely (logs debug)
|
|
137
|
-
2. Checks if `Authentication.CURRENT_USER` is already set on context - if true, skips (already authenticated)
|
|
138
|
-
3. Reads `strategies` and `mode` from the provided options
|
|
139
|
-
4. Executes strategies based on mode (`any` or `all`)
|
|
140
|
-
5. On success, sets `Authentication.CURRENT_USER` and `Authentication.AUDIT_USER_ID` on context
|
|
141
|
-
6. On failure, throws 401 with list of tried strategies
|
|
142
|
-
|
|
143
|
-
```mermaid
|
|
144
|
-
sequenceDiagram
|
|
145
|
-
participant C as Client
|
|
146
|
-
participant MW as Auth Middleware
|
|
147
|
-
participant P as AuthenticationProvider
|
|
148
|
-
participant R as StrategyRegistry
|
|
149
|
-
participant S as Strategy
|
|
150
|
-
participant SVC as TokenService
|
|
151
|
-
|
|
152
|
-
C->>MW: Request with Authorization header
|
|
153
|
-
MW->>P: authenticateFn({ strategies, mode })
|
|
154
|
-
P->>P: Check SKIP_AUTHENTICATION
|
|
155
|
-
P->>P: Check CURRENT_USER already set
|
|
156
|
-
P->>R: resolveStrategy({ name })
|
|
157
|
-
R-->>P: strategy instance
|
|
158
|
-
P->>S: authenticate(context)
|
|
159
|
-
S->>SVC: extractCredentials(context)
|
|
160
|
-
SVC-->>S: { type, token }
|
|
161
|
-
S->>SVC: verify({ type, token })
|
|
162
|
-
SVC-->>S: IJWTTokenPayload
|
|
163
|
-
S-->>P: IAuthUser
|
|
164
|
-
P->>MW: Set CURRENT_USER + AUDIT_USER_ID
|
|
165
|
-
MW->>C: Continue to handler
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Standalone `authenticate()` Function
|
|
181
|
+
Discriminated union on `standard`:
|
|
169
182
|
|
|
170
183
|
```typescript
|
|
171
|
-
|
|
172
|
-
|
|
184
|
+
type TJWTTokenServiceOptions =
|
|
185
|
+
| { standard: typeof JOSEStandards.JWS; options: IJWSTokenServiceOptions }
|
|
186
|
+
| { standard: typeof JOSEStandards.JWKS; options: TJWKSTokenServiceOptions };
|
|
173
187
|
|
|
174
|
-
|
|
175
|
-
return authenticateFn(opts);
|
|
176
|
-
};
|
|
188
|
+
type TJWKSTokenServiceOptions = IJWKSIssuerOptions | IJWKSVerifierOptions; // discriminated on `mode`
|
|
177
189
|
```
|
|
178
190
|
|
|
179
|
-
|
|
191
|
+
### IJWSTokenServiceOptions
|
|
192
|
+
|
|
193
|
+
| Option | Type | Default | Required | Description |
|
|
194
|
+
|--------|------|---------|----------|-------------|
|
|
195
|
+
| `jwtSecret` | `string` | -- | Yes | Secret for signing and verifying the JWT signature |
|
|
196
|
+
| `getTokenExpiresFn` | `TGetTokenExpiresFn` | -- | Yes | Returns token expiration in seconds |
|
|
197
|
+
| `applicationSecret` | `string` | -- | No | Enables AES payload field encryption when set |
|
|
198
|
+
| `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload encryption |
|
|
199
|
+
| `headerAlgorithm` | `string` | `'HS256'` | No | JWT signing algorithm |
|
|
200
|
+
| `fieldCodecs` | `IPayloadFieldCodec[]` | `[]` | No | Custom serialize/deserialize per field name |
|
|
201
|
+
| `verify` | `IJWTVerifyOptions` | -- | No | Claim checks on verify: `audience`, `issuer`, `subject`, `algorithms`, `clockTolerance`, `maxTokenAge`, `typ`, `requiredClaims`. Unset means signature and time claims only |
|
|
202
|
+
| `sign` | `{ issuer?, audience? }` | -- | No | Claims stamped on every issued token. A configured value WINS over one supplied in the payload |
|
|
203
|
+
|
|
204
|
+
> [!WARNING]
|
|
205
|
+
> `jwtSecret` is mandatory - the component throws if it's missing or equals the placeholder `'unknown_secret'`. `applicationSecret` is optional. When you omit it, the JWT payload stays standard plaintext. Standard fields (`iss`, `sub`, `aud`, `jti`, `nbf`, `exp`, `iat`) are never encrypted either way.
|
|
206
|
+
|
|
207
|
+
### IJWKSIssuerOptions
|
|
208
|
+
|
|
209
|
+
| Option | Type | Default | Required | Description |
|
|
210
|
+
|--------|------|---------|----------|-------------|
|
|
211
|
+
| `mode` | `typeof JWKSModes.ISSUER` | -- | Yes | Must be `'issuer'` |
|
|
212
|
+
| `algorithm` | `TJWKSAlgorithm` | -- | Yes | `'ES256'`, `'RS256'`, or `'EdDSA'` |
|
|
213
|
+
| `keys.driver` | `TJWKSKeyDriver` | -- | Yes | `'text'` (inline) or `'file'` (path) |
|
|
214
|
+
| `keys.format` | `TJWKSKeyFormat` | -- | Yes | `'pem'` or `'jwk'` |
|
|
215
|
+
| `keys.private` | `string` | -- | Yes | Private key content or file path |
|
|
216
|
+
| `keys.public` | `string` | -- | Yes | Public key content or file path |
|
|
217
|
+
| `kid` | `string` | -- | Yes | Key ID exposed in the JWKS endpoint and JWT header |
|
|
218
|
+
| `getTokenExpiresFn` | `TGetTokenExpiresFn` | -- | Yes | Returns token expiration in seconds |
|
|
219
|
+
| `rest.path` | `string` | `'/certs'` | No | Path of the generated `JWKSController` |
|
|
220
|
+
| `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload encryption |
|
|
221
|
+
| `applicationSecret` | `string` | -- | No | Enables AES payload field encryption when set |
|
|
222
|
+
| `fieldCodecs` | `IPayloadFieldCodec[]` | `[]` | No | Custom serialize/deserialize per field name |
|
|
223
|
+
| `verify` | `IJWTVerifyOptions` | -- | No | Claim checks on verify: `audience`, `issuer`, `subject`, `algorithms`, `clockTolerance`, `maxTokenAge`, `typ`, `requiredClaims`. Unset means signature and time claims only |
|
|
224
|
+
| `sign` | `{ issuer?, audience? }` | -- | No | Claims stamped on every issued token. A configured value WINS over one supplied in the payload |
|
|
225
|
+
|
|
226
|
+
### IJWKSVerifierOptions
|
|
227
|
+
|
|
228
|
+
| Option | Type | Default | Required | Description |
|
|
229
|
+
|--------|------|---------|----------|-------------|
|
|
230
|
+
| `mode` | `typeof JWKSModes.VERIFIER` | -- | Yes | Must be `'verifier'` |
|
|
231
|
+
| `jwksUrl` | `string` | -- | Yes | URL of the issuer's JWKS endpoint |
|
|
232
|
+
| `cacheTtlMs` | `number` | `43_200_000` (12h) | No | `createRemoteJWKSet` `cacheMaxAge` |
|
|
233
|
+
| `cooldownMs` | `number` | `30_000` (30s) | No | `createRemoteJWKSet` `cooldownDuration` |
|
|
234
|
+
| `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload decryption |
|
|
235
|
+
| `applicationSecret` | `string` | -- | No | Must match the issuer's secret to decrypt payloads |
|
|
236
|
+
| `fieldCodecs` | `IPayloadFieldCodec[]` | `[]` | No | Must match the issuer's codecs to decrypt custom fields |
|
|
237
|
+
| `verify` | `IJWTVerifyOptions` | -- | No | Claim checks on verify: `audience`, `issuer`, `subject`, `algorithms`, `clockTolerance`, `maxTokenAge`, `typ`, `requiredClaims`. Unset means signature and time claims only |
|
|
238
|
+
|
|
239
|
+
> [!IMPORTANT]
|
|
240
|
+
> `JWKSVerifierTokenService` cannot sign tokens - `getSigner()`, `getSigningKey()`, and `getDefaultTokenExpiresFn()` all throw. Only `verify()` and `extractCredentials()` are functional.
|
|
241
|
+
|
|
242
|
+
### TBasicTokenServiceOptions
|
|
243
|
+
|
|
244
|
+
| Option | Type | Description |
|
|
245
|
+
|--------|------|-------------|
|
|
246
|
+
| `verifyCredentials` | <code v-pre>(opts: { credentials: { username: string; password: string }; context: TContext }) => Promise<IAuthUser \| null></code> | Callback that validates Basic credentials and returns the authenticated user, or `null` |
|
|
247
|
+
|
|
248
|
+
### TAuthenticationRestOptions
|
|
249
|
+
|
|
250
|
+
Discriminated union on `useAuthController` - when `true`, `controllerOpts` becomes required:
|
|
180
251
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
```mermaid
|
|
187
|
-
classDiagram
|
|
188
|
-
class AbstractBearerTokenService {
|
|
189
|
-
<<abstract>>
|
|
190
|
-
#aes: AES | null
|
|
191
|
-
#applicationSecret: string | null
|
|
192
|
-
+extractCredentials(context) credentials
|
|
193
|
-
+verify(opts) IJWTTokenPayload
|
|
194
|
-
+generate(opts) string
|
|
195
|
-
+encryptPayload(payload) Record
|
|
196
|
-
+decryptPayload(opts) IJWTTokenPayload
|
|
197
|
-
#doVerify(token)* IJWTTokenPayload
|
|
198
|
-
+getSigner(opts)* SignJWT
|
|
199
|
-
#getSigningKey()* Uint8Array | CryptoKey
|
|
200
|
-
#getDefaultTokenExpiresFn()* TGetTokenExpiresFn
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
class JWSTokenService {
|
|
204
|
-
#jwtSecret: Uint8Array
|
|
205
|
-
#options: IJWSTokenServiceOptions
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
class AbstractJWKSTokenService {
|
|
209
|
-
<<abstract>>
|
|
210
|
-
#initialized: boolean
|
|
211
|
-
#initPromise: Promise | null
|
|
212
|
-
+ensureInitialized()
|
|
213
|
-
#initialize()* void
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
class JWKSIssuerTokenService {
|
|
217
|
-
#privateKey: CryptoKey | null
|
|
218
|
-
#publicKey: CryptoKey | null
|
|
219
|
-
#jwks: object | null
|
|
220
|
-
+getJWKS() keys
|
|
221
|
-
+getJWKSAsync() keys
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
class JWKSVerifierTokenService {
|
|
225
|
-
#jwksVerifier: Function | null
|
|
226
|
-
+getSigner() never
|
|
227
|
-
+getSigningKey() never
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
AbstractBearerTokenService <|-- JWSTokenService
|
|
231
|
-
AbstractBearerTokenService <|-- AbstractJWKSTokenService
|
|
232
|
-
AbstractJWKSTokenService <|-- JWKSIssuerTokenService
|
|
233
|
-
AbstractJWKSTokenService <|-- JWKSVerifierTokenService
|
|
252
|
+
```typescript
|
|
253
|
+
type TAuthenticationRestOptions = {} & (
|
|
254
|
+
| { useAuthController?: false | undefined }
|
|
255
|
+
| { useAuthController: true; controllerOpts: TDefineAuthControllerOpts }
|
|
256
|
+
);
|
|
234
257
|
```
|
|
235
258
|
|
|
236
|
-
|
|
259
|
+
### TDefineAuthControllerOpts
|
|
237
260
|
|
|
238
|
-
|
|
261
|
+
| Option | Type | Default | Description |
|
|
262
|
+
|--------|------|---------|-------------|
|
|
263
|
+
| `restPath` | `string` | `'/auth'` | Base path for the generated controller |
|
|
264
|
+
| `serviceKey` | `string` | -- | DI key for the `IAuthService` implementation (required) |
|
|
265
|
+
| `requireAuthenticatedSignUp` | `boolean` | `false` | Whether `POST /sign-up` requires a valid JWT |
|
|
266
|
+
| `payload.signIn` | `{ request: { schema }, response: { schema } }` | Built-in schemas | Custom Zod schemas for `/sign-in` |
|
|
267
|
+
| `payload.signUp` | `{ request: { schema }, response: { schema } }` | Built-in schemas | Custom Zod schemas for `/sign-up` |
|
|
268
|
+
| `payload.changePassword` | `{ request: { schema? }, response: { schema } }` | Built-in schemas | Custom Zod schemas for `/change-password` |
|
|
269
|
+
| `payload.refreshToken` | `{ response: { schema } }` | `AnyObjectSchema` | Custom response schema for `/token/refresh` |
|
|
270
|
+
| `payload.getUserInformation` | `{ response: { schema } }` | `AnyObjectSchema` | Custom response schema for `/me` and the `who-am-i` `userInformation` field |
|
|
239
271
|
|
|
240
|
-
|
|
272
|
+
### Route authenticate config
|
|
241
273
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
### Protected Fields
|
|
274
|
+
```typescript
|
|
275
|
+
type TRouteAuthenticateConfig =
|
|
276
|
+
| { skip: true }
|
|
277
|
+
| { skip?: false; strategies?: TAuthStrategy[]; mode?: TAuthMode };
|
|
278
|
+
```
|
|
249
279
|
|
|
250
280
|
| Field | Type | Default | Description |
|
|
251
281
|
|-------|------|---------|-------------|
|
|
252
|
-
| `
|
|
253
|
-
| `
|
|
254
|
-
| `
|
|
255
|
-
|
|
256
|
-
### Methods
|
|
257
|
-
|
|
258
|
-
| Method | Signature | Description |
|
|
259
|
-
|--------|-----------|-------------|
|
|
260
|
-
| `configurePayloadEncryption` | <code v-pre>(opts: { aesAlgorithm?: AESAlgorithmType; applicationSecret?: string; fieldCodecs?: IPayloadFieldCodec[] }) => void</code> | Configures optional AES encryption and field codecs. Codecs are converted to a Map keyed by `codec.key` for O(1) lookup. |
|
|
261
|
-
| `extractCredentials` | <code v-pre>(context: TContext<E, string>) => { type: string; token: string }</code> | Extracts Bearer token from Authorization header |
|
|
262
|
-
| `verify` | <code v-pre>(opts: { type: string; token: string }) => Promise<IJWTTokenPayload></code> | Template method - calls `doVerify()` |
|
|
263
|
-
| `generate` | <code v-pre>(opts: { payload: IJWTTokenPayload; getTokenExpiresFn?: TGetTokenExpiresFn }) => Promise<string></code> | Template method - calls `getSigner()` + `getSigningKey()` |
|
|
264
|
-
| `serializeField` | <code v-pre>(opts: { key: string; value: any }) => string</code> | Serializes a single field: codec → `JSON.stringify` fallback |
|
|
265
|
-
| `deserializeField` | <code v-pre>(opts: { key: string; value: string }) => any</code> | Deserializes a single field: codec → `JSON.parse` fallback |
|
|
266
|
-
| `encryptPayload` | <code v-pre>(payload: IJWTTokenPayload) => Record<string, any></code> | AES-encrypts non-standard JWT fields using `serializeField`. Returns payload unchanged if AES not configured. |
|
|
267
|
-
| `decryptPayload` | <code v-pre>(opts: { result: JWTVerifyResult<IJWTTokenPayload> }) => IJWTTokenPayload</code> | Decrypts AES-encrypted fields using `deserializeField`. Returns payload unchanged if AES not configured. |
|
|
268
|
-
|
|
269
|
-
### Abstract Methods (implemented by subclasses)
|
|
282
|
+
| `authenticate.strategies` | `TAuthStrategy[]` | -- | Strategy names to try - for example, `['jwt']` or `['jwt', 'basic']`. Any name you register works too; `'jwt'` and `'basic'` are the ones the framework ships |
|
|
283
|
+
| `authenticate.mode` | `'any' \| 'all'` | `'any'` | `'any'`: first success wins. `'all'`: every strategy must pass |
|
|
284
|
+
| `authenticate.skip` | `true` | -- | Skips authentication for this route entirely |
|
|
270
285
|
|
|
271
|
-
|
|
272
|
-
|--------|------------|-----------|-------------|
|
|
273
|
-
| `doVerify` | `protected` | `(token: string) => Promise<IJWTTokenPayload>` | Verify the raw JWT token and return the payload |
|
|
274
|
-
| `getSigner` | **`public`** | <code v-pre>(opts: { payload: IJWTTokenPayload; getTokenExpiresFn: TGetTokenExpiresFn }) => Promise<SignJWT></code> | Create a `jose.SignJWT` instance with the payload |
|
|
275
|
-
| `getSigningKey` | `protected` | `() => ValueOrPromise<Uint8Array \| CryptoKey>` | Return the signing key |
|
|
276
|
-
| `getDefaultTokenExpiresFn` | `protected` | `() => TGetTokenExpiresFn` | Return the default token expiry function |
|
|
286
|
+
### IAuthUser / IJWTTokenPayload
|
|
277
287
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
288
|
+
```typescript
|
|
289
|
+
interface IAuthUser {
|
|
290
|
+
userId: IdType;
|
|
291
|
+
[extra: string | symbol]: any;
|
|
292
|
+
}
|
|
281
293
|
|
|
282
|
-
|
|
294
|
+
interface IJWTTokenPayload extends JWTPayload, IAuthUser {
|
|
295
|
+
userId: IdType;
|
|
296
|
+
roles: { id: IdType; identifier: string; priority: number }[];
|
|
297
|
+
clientId?: string;
|
|
298
|
+
provider?: string;
|
|
299
|
+
email?: string;
|
|
300
|
+
name?: string;
|
|
301
|
+
[extra: string | symbol]: any;
|
|
302
|
+
}
|
|
303
|
+
```
|
|
283
304
|
|
|
284
|
-
|
|
305
|
+
> [!TIP]
|
|
306
|
+
> `IAuthUser` is intentionally minimal. Your `IAuthService` can return extra fields (roles, email, provider) - they pass through JWT generation and are available on `Authentication.CURRENT_USER` after authentication.
|
|
285
307
|
|
|
286
|
-
|
|
308
|
+
### IAuthService
|
|
287
309
|
|
|
288
310
|
```typescript
|
|
289
|
-
|
|
290
|
-
extends
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
})
|
|
303
|
-
private service: JWSTokenService<E>,
|
|
304
|
-
) { ... }
|
|
305
|
-
|
|
306
|
-
authenticate(context: TContext<E, string>): Promise<IAuthUser> {
|
|
307
|
-
const token = this.service.extractCredentials(context);
|
|
308
|
-
return this.service.verify(token);
|
|
309
|
-
}
|
|
311
|
+
interface IAuthService<
|
|
312
|
+
E extends Env = Env,
|
|
313
|
+
SIRQ extends TSignInRequest = TSignInRequest, SIRS = AnyObject,
|
|
314
|
+
SURQ extends TSignUpRequest = TSignUpRequest, SURS = AnyObject,
|
|
315
|
+
CPRQ extends TChangePasswordRequest = TChangePasswordRequest, CPRS = AnyObject,
|
|
316
|
+
UIRQ = AnyObject, UIRS = AnyObject,
|
|
317
|
+
RTRS = AnyObject,
|
|
318
|
+
> {
|
|
319
|
+
signIn(context: TContext<E>, opts: SIRQ): Promise<SIRS>;
|
|
320
|
+
signUp(context: TContext<E>, opts: SURQ): Promise<SURS>;
|
|
321
|
+
changePassword(context: TContext<E>, opts: CPRQ): Promise<CPRS>;
|
|
322
|
+
getUserInformation?(context: TContext<E>, opts: UIRQ): Promise<UIRS>;
|
|
323
|
+
refreshToken?(context: TContext<E>): Promise<RTRS>;
|
|
310
324
|
}
|
|
311
325
|
```
|
|
312
326
|
|
|
313
|
-
|
|
327
|
+
> [!NOTE]
|
|
328
|
+
> `getUserInformation` and `refreshToken` are optional. The generated auth controller returns `501` for `/me`, `/token/refresh`, or `?withUserInformation=true` on `/who-am-i` if the bound service doesn't implement the corresponding method.
|
|
314
329
|
|
|
315
|
-
|
|
316
|
-
|-------|------|-------------|
|
|
317
|
-
| `jwtSecret` | `Uint8Array` | Encoded JWT secret for `jose` signing/verification |
|
|
318
|
-
| `options` | `IJWSTokenServiceOptions` | Injected options |
|
|
330
|
+
### Field codecs
|
|
319
331
|
|
|
320
|
-
|
|
332
|
+
```typescript
|
|
333
|
+
interface IPayloadFieldCodec<T = unknown> {
|
|
334
|
+
key: string;
|
|
335
|
+
serialize(opts: { value: T }): string;
|
|
336
|
+
deserialize(opts: { raw: string }): T;
|
|
337
|
+
}
|
|
338
|
+
```
|
|
321
339
|
|
|
322
|
-
|
|
340
|
+
`AuthenticationFieldCodecs.ROLES_CODEC` is a ready-made codec for the `roles` field (pipe-separated `id|identifier|priority` strings). It is **not applied automatically** - pass it explicitly via `fieldCodecs: [AuthenticationFieldCodecs.ROLES_CODEC]` in the JWS/JWKS options. Without it, `roles` (and every other non-standard field) is serialized with plain `JSON.stringify` before AES encryption.
|
|
323
341
|
|
|
324
342
|
```typescript
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
) {
|
|
329
|
-
// Throws '[JWSTokenService] Invalid jwtSecret' if !jwtSecret
|
|
330
|
-
// Throws '[JWSTokenService] Invalid getTokenExpiresFn' if !getTokenExpiresFn
|
|
331
|
-
// Calls configurePayloadEncryption({ aesAlgorithm, applicationSecret })
|
|
332
|
-
// Encodes jwtSecret to Uint8Array for jose
|
|
343
|
+
class AuthenticationFieldCodecs {
|
|
344
|
+
static readonly ROLES_CODEC: IPayloadFieldCodec<IJWTTokenPayload['roles']>;
|
|
345
|
+
static build<T>(opts: { key: string; serialize; deserialize }): IPayloadFieldCodec<T>;
|
|
333
346
|
}
|
|
334
347
|
```
|
|
335
348
|
|
|
336
|
-
|
|
337
|
-
> `applicationSecret` is no longer validated in the constructor. If not provided, AES encryption is simply not configured, and payloads pass through in plaintext.
|
|
349
|
+
## Context variables
|
|
338
350
|
|
|
339
|
-
|
|
351
|
+
Set on the Hono `Context` during authentication, readable via `context.get()`:
|
|
340
352
|
|
|
341
|
-
|
|
|
342
|
-
|
|
343
|
-
| `
|
|
344
|
-
| `
|
|
345
|
-
| `
|
|
346
|
-
| `getDefaultTokenExpiresFn()` | Returns `this.options.getTokenExpiresFn` |
|
|
353
|
+
| Constant | Key string | Type | Description |
|
|
354
|
+
|----------|-----------|------|-------------|
|
|
355
|
+
| `Authentication.CURRENT_USER` | `auth.current.user` | `IAuthUser` | Authenticated user payload |
|
|
356
|
+
| `Authentication.AUDIT_USER_ID` | `audit.user.id` | `IdType` | Authenticated user's ID |
|
|
357
|
+
| `Authentication.SKIP_AUTHENTICATION` | `authentication.skip` | `boolean` | Set `true` in a preceding middleware to bypass auth |
|
|
347
358
|
|
|
348
|
-
##
|
|
359
|
+
## Constants
|
|
349
360
|
|
|
350
|
-
|
|
361
|
+
**Authentication**
|
|
351
362
|
|
|
352
|
-
|
|
363
|
+
| Constant | Value | Description |
|
|
364
|
+
|----------|-------|-------------|
|
|
365
|
+
| `Authentication.STRATEGY_JWT` | `'jwt'` | JWT strategy name |
|
|
366
|
+
| `Authentication.STRATEGY_BASIC` | `'basic'` | Basic strategy name |
|
|
367
|
+
| `Authentication.TYPE_BEARER` | `'Bearer'` | Bearer token type prefix |
|
|
368
|
+
| `Authentication.TYPE_BASIC` | `'Basic'` | Basic token type prefix |
|
|
369
|
+
| `Authentication.AUTHENTICATION_STRATEGY` | `'authentication.strategy'` | Binding key prefix for registered strategies |
|
|
353
370
|
|
|
354
|
-
|
|
371
|
+
**AuthenticationTokenTypes**
|
|
355
372
|
|
|
356
|
-
|
|
373
|
+
| Constant | Value |
|
|
374
|
+
|----------|-------|
|
|
375
|
+
| `TYPE_AUTHORIZATION_CODE` | `'000_AUTHORIZATION_CODE'` |
|
|
376
|
+
| `TYPE_ACCESS_TOKEN` | `'100_ACCESS_TOKEN'` |
|
|
377
|
+
| `TYPE_REFRESH_TOKEN` | `'200_REFRESH_TOKEN'` |
|
|
357
378
|
|
|
358
|
-
|
|
359
|
-
|-------|------|---------|-------------|
|
|
360
|
-
| `initialized` | `boolean` | `false` | Whether the service has been initialized |
|
|
361
|
-
| `initPromise` | `Promise<void> \| null` | `null` | Pending initialization promise (for concurrent callers) |
|
|
379
|
+
**JOSE / JWKS constants** - each class also exposes `SCHEME_SET: Set<string>` and `isValid(input): boolean`
|
|
362
380
|
|
|
363
|
-
|
|
381
|
+
| Class | Members |
|
|
382
|
+
|-------|---------|
|
|
383
|
+
| `JOSEStandards` | `JWS` (`'JWS'`), `JWKS` (`'JWKS'`) |
|
|
384
|
+
| `JWKSModes` | `ISSUER` (`'issuer'`), `VERIFIER` (`'verifier'`) |
|
|
385
|
+
| `JWKSKeyDrivers` | `TEXT` (`'text'`), `FILE` (`'file'`) |
|
|
386
|
+
| `JWKSKeyFormats` | `PEM` (`'pem'`), `JWK` (`'jwk'`) |
|
|
387
|
+
| `AuthenticateStrategy` | `BASIC` (`'basic'`), `JWT` (`'jwt'`) - same values as `Authentication.STRATEGY_*` |
|
|
388
|
+
| `AuthenticationModes` | `ANY` (`'any'`), `ALL` (`'all'`) |
|
|
364
389
|
|
|
365
|
-
|
|
366
|
-
|--------|-----------|-------------|
|
|
367
|
-
| `ensureInitialized` | `() => Promise<void>` | Lazily initializes the service on first call. Concurrent callers share the same promise. On failure, resets `initPromise` so the next call retries. |
|
|
390
|
+
## Strategy registry
|
|
368
391
|
|
|
369
|
-
|
|
392
|
+
`AuthenticationStrategyRegistry` is a singleton extending `AbstractAuthRegistry<IAuthenticationStrategy>`.
|
|
370
393
|
|
|
371
394
|
| Method | Signature | Description |
|
|
372
|
-
|
|
373
|
-
| `
|
|
374
|
-
|
|
375
|
-
|
|
395
|
+
|--------|-----------|--------------|
|
|
396
|
+
| `getInstance()` | `static (): AuthenticationStrategyRegistry` | Returns (creating if needed) the singleton |
|
|
397
|
+
| `register` | <code v-pre>(opts: { container: Container; strategies: { name: string; strategy: TClass<IAuthenticationStrategy> }[] }) => this</code> | Binds each strategy into the container as a singleton under `authentication.strategy.<name>`. Returns `this` |
|
|
398
|
+
| `resolveStrategy` | `(opts: { name: string }) => IAuthenticationStrategy` | Resolves a registered strategy instance by name |
|
|
376
399
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
400
|
+
```typescript
|
|
401
|
+
AuthenticationStrategyRegistry.getInstance().register({
|
|
402
|
+
container: this,
|
|
403
|
+
strategies: [
|
|
404
|
+
{ name: Authentication.STRATEGY_JWT, strategy: JWKSIssuerAuthenticationStrategy },
|
|
405
|
+
{ name: Authentication.STRATEGY_BASIC, strategy: BasicAuthenticationStrategy },
|
|
406
|
+
],
|
|
407
|
+
});
|
|
408
|
+
```
|
|
380
409
|
|
|
381
|
-
|
|
410
|
+
**Standalone `authenticate()` function** - the primary export for creating middleware outside the route-config `authenticate` field:
|
|
382
411
|
|
|
383
412
|
```typescript
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
{
|
|
388
|
-
name = Authentication.STRATEGY_JWT;
|
|
389
|
-
standard = JOSEStandards.JWKS;
|
|
390
|
-
|
|
391
|
-
constructor(
|
|
392
|
-
@inject({
|
|
393
|
-
key: BindingKeys.build({
|
|
394
|
-
namespace: BindingNamespaces.SERVICE,
|
|
395
|
-
key: JWKSIssuerTokenService.name,
|
|
396
|
-
}),
|
|
397
|
-
})
|
|
398
|
-
private service: JWKSIssuerTokenService<E>,
|
|
399
|
-
) { ... }
|
|
400
|
-
|
|
401
|
-
authenticate(context: TContext<E, string>): Promise<IAuthUser> {
|
|
402
|
-
const token = this.service.extractCredentials(context);
|
|
403
|
-
return this.service.verify(token);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
413
|
+
const authenticationProvider = new AuthenticationProvider();
|
|
414
|
+
const authenticateFn = authenticationProvider.value();
|
|
415
|
+
|
|
416
|
+
export const authenticate = (opts: { strategies: string[]; mode?: TAuthMode }) => authenticateFn(opts);
|
|
406
417
|
```
|
|
407
418
|
|
|
408
|
-
|
|
419
|
+
**`AuthenticationProvider` middleware behavior:**
|
|
409
420
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
421
|
+
1. If `Authentication.SKIP_AUTHENTICATION` is set on context, skips entirely (debug log)
|
|
422
|
+
2. If `Authentication.CURRENT_USER` is already set, skips (already authenticated)
|
|
423
|
+
3. Runs strategies per `mode` (`'any'` or `'all'`)
|
|
424
|
+
4. On success, sets `Authentication.CURRENT_USER` and `Authentication.AUDIT_USER_ID`
|
|
425
|
+
5. On failure, throws `401`
|
|
415
426
|
|
|
416
|
-
|
|
427
|
+
> [!NOTE]
|
|
428
|
+
> `'any'` mode logs each failing strategy at debug and discards the error. It only throws once every strategy is exhausted. `'all'` mode uses the **first** strategy's user payload as the identity source. If that payload has no `userId`, it throws `401` even though every strategy technically passed.
|
|
417
429
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
430
|
+
## Service class hierarchy
|
|
431
|
+
|
|
432
|
+
```
|
|
433
|
+
AbstractBearerTokenService<E> (extends BaseService)
|
|
434
|
+
#aes, #applicationSecret, #fieldCodecs
|
|
435
|
+
+extractCredentials(context) Extract Bearer token from Authorization header
|
|
436
|
+
+verify(opts) Template method -> doVerify()
|
|
437
|
+
+generate(opts) Template method -> getSigner() + getSigningKey()
|
|
438
|
+
+encryptPayload(payload) / decryptPayload(opts)
|
|
439
|
+
#doVerify(token)* +getSigner(opts)* #getSigningKey()* #getDefaultTokenExpiresFn()*
|
|
440
|
+
|
|
441
|
+
JWSTokenService Symmetric HS256, shared secret
|
|
442
|
+
used by JWSAuthenticationStrategy
|
|
443
|
+
|
|
444
|
+
AbstractJWKSTokenService Lazy ensureInitialized() + retry-on-failure
|
|
445
|
+
#initialized, #initPromise
|
|
446
|
+
+ensureInitialized() #initialize()*
|
|
447
|
+
|
|
448
|
+
JWKSIssuerTokenService Sign with private key, verify with public key
|
|
449
|
+
+getJWKS() (sync, throws if uninitialized) +getJWKSAsync()
|
|
450
|
+
used by JWKSIssuerAuthenticationStrategy
|
|
451
|
+
|
|
452
|
+
JWKSVerifierTokenService Verify only, via remote JWKS
|
|
453
|
+
used by JWKSVerifierAuthenticationStrategy
|
|
426
454
|
```
|
|
427
455
|
|
|
428
|
-
###
|
|
456
|
+
### AbstractBearerTokenService
|
|
429
457
|
|
|
430
|
-
|
|
431
|
-
1. **Resolves key content** - reads from file (`readFile` from `node:fs/promises`) or uses inline text, based on `keys.driver`
|
|
432
|
-
2. **Parses key material** - imports keys using `importPKCS8`/`importSPKI` (PEM format) or `importJWK` (JWK format), based on `keys.format`
|
|
433
|
-
3. **Exports public JWK** - calls `exportJWK()` and adds `kid`, `alg`, `use: 'sig'` metadata
|
|
434
|
-
4. **Caches JWKS** - stores `{ keys: [publicJWK] }` for the `/certs` endpoint
|
|
435
|
-
5. **Sets `initialized = true`**
|
|
458
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/services/bearer/abstract.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/bearer/abstract.service.ts)
|
|
436
459
|
|
|
437
|
-
|
|
460
|
+
| Method | Signature | Description |
|
|
461
|
+
|--------|-----------|-------------|
|
|
462
|
+
| `configurePayloadEncryption` | <code v-pre>(opts: { aesAlgorithm?; applicationSecret?; fieldCodecs?: IPayloadFieldCodec[] }) => void</code> | Sets up AES + field codecs. AES only activates if `applicationSecret` is provided |
|
|
463
|
+
| `extractCredentials` | `(context) => { type: string; token: string }` | Parses `Authorization: Bearer <token>` |
|
|
464
|
+
| `verify` | `(opts: { type, token }) => Promise<IJWTTokenPayload>` | Calls `doVerify()`, wraps errors as sanitized `401` |
|
|
465
|
+
| `generate` | `(opts: { payload, getTokenExpiresFn? }) => Promise<string>` | Calls `getSigner()` then signs with `getSigningKey()` |
|
|
466
|
+
| `serializeField` / `deserializeField` | `(opts) => string` / `any` | Per-field codec lookup, `JSON.stringify`/`JSON.parse` fallback |
|
|
467
|
+
| `encryptPayload` | `(payload) => Record<string, any>` | AES-encrypts non-standard fields (keys and values). No-op if AES not configured |
|
|
468
|
+
| `decryptPayload` | `(opts: { result }) => IJWTTokenPayload` | Reverses `encryptPayload`. No-op if AES not configured |
|
|
438
469
|
|
|
439
|
-
|
|
440
|
-
|--------|----------|
|
|
441
|
-
| `doVerify(token)` | Calls `ensureInitialized()`, then `jwtVerify(token, this.publicKey!)`, then `this.decryptPayload()` |
|
|
442
|
-
| `getSigner(opts)` | Calls `ensureInitialized()`, then `this.encryptPayload()`, then creates `SignJWT` with algorithm + kid header |
|
|
443
|
-
| `getSigningKey()` | Returns `this.privateKey` |
|
|
444
|
-
| `getDefaultTokenExpiresFn()` | Returns `this.options.getTokenExpiresFn` |
|
|
470
|
+
Static: `JWT_COMMON_FIELDS: Set<'iss'|'sub'|'aud'|'jti'|'nbf'|'exp'|'iat'>` - never encrypted or touched by field codecs.
|
|
445
471
|
|
|
446
|
-
###
|
|
472
|
+
### JWSTokenService
|
|
447
473
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
474
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/services/bearer/jws.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/bearer/jws.service.ts)
|
|
475
|
+
|
|
476
|
+
The constructor validates `jwtSecret` and `getTokenExpiresFn`, throwing `500` if either is missing. It encodes the secret to `Uint8Array` and calls `configurePayloadEncryption()`. `doVerify` calls `jose.jwtVerify()` with the shared secret. `getSigner` signs with header `HS256`, or `headerAlgorithm` if you set one.
|
|
477
|
+
|
|
478
|
+
### AbstractJWKSTokenService
|
|
479
|
+
|
|
480
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/services/bearer/jwks/abstract.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/bearer/jwks/abstract.service.ts)
|
|
481
|
+
|
|
482
|
+
`ensureInitialized()` lazily runs `initialize()` on the first call. Concurrent callers share the pending promise. If `initialize()` rejects, the promise resets - the next call retries instead of caching the failure.
|
|
452
483
|
|
|
453
|
-
###
|
|
484
|
+
### JWKSIssuerTokenService
|
|
485
|
+
|
|
486
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/services/bearer/jwks/issuer.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/bearer/jwks/issuer.service.ts)
|
|
487
|
+
|
|
488
|
+
`initialize()` runs in order:
|
|
489
|
+
|
|
490
|
+
1. Reads the key content - `readFile` for `keys.driver: 'file'`, inline text for `'text'`.
|
|
491
|
+
2. Imports it - `importPKCS8`/`importSPKI` for PEM, `importJWK` for JWK.
|
|
492
|
+
3. Exports the public JWK with `kid`, `alg`, and `use: 'sig'` set.
|
|
493
|
+
4. Caches the result as `{ keys: [publicJWK] }`.
|
|
454
494
|
|
|
455
495
|
| Method | Signature | Description |
|
|
456
496
|
|--------|-----------|-------------|
|
|
457
|
-
| `
|
|
458
|
-
| `
|
|
497
|
+
| `getJWKS` | `() => { keys: JWK[] }` | Synchronous, returns the cached JWKS. Throws if called before `initialize()` completes |
|
|
498
|
+
| `getJWKSAsync` | `() => Promise<{ keys: JWK[] }>` | Calls `ensureInitialized()` first |
|
|
459
499
|
|
|
460
|
-
|
|
500
|
+
### JWKSVerifierTokenService
|
|
461
501
|
|
|
462
|
-
|
|
502
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/services/bearer/jwks/verifier.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/bearer/jwks/verifier.service.ts)
|
|
463
503
|
|
|
464
|
-
|
|
504
|
+
`initialize()` calls `createRemoteJWKSet(jwksUrl, { cacheMaxAge: cacheTtlMs ?? 43_200_000, cooldownDuration: cooldownMs ?? 30_000 })`. `getSigner`/`getSigningKey`/`getDefaultTokenExpiresFn` all throw - this service is verify-only.
|
|
465
505
|
|
|
466
|
-
###
|
|
506
|
+
### BasicTokenService
|
|
467
507
|
|
|
468
|
-
|
|
469
|
-
class JWKSVerifierAuthenticationStrategy<E extends Env = Env>
|
|
470
|
-
extends BaseHelper
|
|
471
|
-
implements IAuthenticationStrategy<E>
|
|
472
|
-
{
|
|
473
|
-
name = Authentication.STRATEGY_JWT;
|
|
474
|
-
standard = JOSEStandards.JWKS;
|
|
475
|
-
|
|
476
|
-
constructor(
|
|
477
|
-
@inject({
|
|
478
|
-
key: BindingKeys.build({
|
|
479
|
-
namespace: BindingNamespaces.SERVICE,
|
|
480
|
-
key: JWKSVerifierTokenService.name,
|
|
481
|
-
}),
|
|
482
|
-
})
|
|
483
|
-
private service: JWKSVerifierTokenService<E>,
|
|
484
|
-
) { ... }
|
|
485
|
-
|
|
486
|
-
authenticate(context: TContext<E, string>): Promise<IAuthUser> {
|
|
487
|
-
const token = this.service.extractCredentials(context);
|
|
488
|
-
return this.service.verify(token);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
```
|
|
508
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/services/basic/service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/basic/service.ts)
|
|
492
509
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
|
496
|
-
|
|
497
|
-
| `jwksVerifier` | `ReturnType<typeof createRemoteJWKSet> \| null` | `null` | Remote JWKS verifier function |
|
|
510
|
+
| Method | Signature | Description |
|
|
511
|
+
|--------|-----------|-------------|
|
|
512
|
+
| `extractCredentials` | `(context) => { username: string; password: string }` | Decodes `Authorization: Basic <base64>` |
|
|
513
|
+
| `verify` | `(opts: { credentials, context }) => Promise<IAuthUser>` | Calls the user-provided `verifyCredentials` |
|
|
498
514
|
|
|
499
|
-
|
|
515
|
+
Constructor throws `500` if `verifyCredentials` is missing from the injected options.
|
|
500
516
|
|
|
501
|
-
|
|
502
|
-
constructor(
|
|
503
|
-
@inject({ key: AuthenticateBindingKeys.JWKS_OPTIONS })
|
|
504
|
-
protected options: IJWKSVerifierOptions,
|
|
505
|
-
) {
|
|
506
|
-
// Calls configurePayloadEncryption({ aesAlgorithm, applicationSecret })
|
|
507
|
-
// Remote JWKS is NOT fetched here - fetched lazily via ensureInitialized()
|
|
508
|
-
}
|
|
509
|
-
```
|
|
517
|
+
## Strategy classes
|
|
510
518
|
|
|
511
|
-
|
|
519
|
+
All four strategies extend `BaseHelper` and implement `IAuthenticationStrategy<E>`. Each one carries:
|
|
512
520
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
521
|
+
- A `name` field.
|
|
522
|
+
- A `standard` field (Bearer strategies only).
|
|
523
|
+
- One injected token service.
|
|
524
|
+
- An `authenticate(context)` method that calls `extractCredentials()`, then `verify()`.
|
|
517
525
|
|
|
518
|
-
|
|
526
|
+
| Strategy | `name` | Injects | File |
|
|
527
|
+
|----------|--------|---------|------|
|
|
528
|
+
| `JWSAuthenticationStrategy` | `Authentication.STRATEGY_JWT` | `JWSTokenService` | [`strategies/jws.strategy.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/strategies/jws.strategy.ts) |
|
|
529
|
+
| `JWKSIssuerAuthenticationStrategy` | `Authentication.STRATEGY_JWT` | `JWKSIssuerTokenService` | [`strategies/jwks.strategy.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/strategies/jwks.strategy.ts) |
|
|
530
|
+
| `JWKSVerifierAuthenticationStrategy` | `Authentication.STRATEGY_JWT` | `JWKSVerifierTokenService` | same file |
|
|
531
|
+
| `BasicAuthenticationStrategy` | `Authentication.STRATEGY_BASIC` | `BasicTokenService` | [`strategies/basic.strategy.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/strategies/basic.strategy.ts) |
|
|
519
532
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
| `doVerify(token)` | Calls `ensureInitialized()`, then `jwtVerify(token, this.jwksVerifier!)`, then `this.decryptPayload()` |
|
|
523
|
-
| `getSigner(opts)` | Throws - verifier mode cannot sign tokens |
|
|
524
|
-
| `getSigningKey()` | Throws - verifier mode cannot sign tokens |
|
|
525
|
-
| `getDefaultTokenExpiresFn()` | Throws - verifier mode has no token expiry |
|
|
533
|
+
> [!NOTE]
|
|
534
|
+
> Choose the strategy class that matches your JOSE standard. `JWKSIssuerAuthenticationStrategy` and `JWKSVerifierAuthenticationStrategy` both register under the same `'jwt'` name - use only one of the two per service.
|
|
526
535
|
|
|
527
536
|
## JWKSController
|
|
528
537
|
|
|
529
|
-
Serves the JWKS endpoint (default path `/certs`).
|
|
538
|
+
Serves the JWKS endpoint (default path `/certs`, configurable via `rest.path`). Intentionally unauthenticated - it serves the public keys external verifiers need.
|
|
530
539
|
|
|
531
|
-
**File:** `packages/core/src/components/auth/authenticate/controllers/jwks/controller.ts`
|
|
540
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/controllers/jwks/controller.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/controllers/jwks/controller.ts)
|
|
532
541
|
|
|
533
542
|
```typescript
|
|
534
543
|
class JWKSController extends BaseRestController {
|
|
535
|
-
constructor(
|
|
536
|
-
@inject({
|
|
537
|
-
key: BindingKeys.build({
|
|
538
|
-
namespace: BindingNamespaces.SERVICE,
|
|
539
|
-
key: JWKSIssuerTokenService.name,
|
|
540
|
-
}),
|
|
541
|
-
})
|
|
542
|
-
private jwksService: JWKSIssuerTokenService,
|
|
543
|
-
) {
|
|
544
|
+
constructor(@inject(...) private jwksService: JWKSIssuerTokenService) {
|
|
544
545
|
super({ scope: JWKSController.name, path: '/certs', isStrict: true });
|
|
545
546
|
}
|
|
546
547
|
|
|
547
|
-
override binding()
|
|
548
|
+
override binding() {
|
|
548
549
|
this.defineRoute({
|
|
549
|
-
configs: RouteConfigs.GET_JWKS_CERTS,
|
|
550
|
+
configs: RouteConfigs.GET_JWKS_CERTS, // GET '/'
|
|
550
551
|
handler: async context => {
|
|
551
552
|
const jwks = await this.jwksService.getJWKSAsync();
|
|
552
553
|
context.header('Cache-Control', 'public, max-age=3600, stale-while-revalidate=86400');
|
|
@@ -557,107 +558,40 @@ class JWKSController extends BaseRestController {
|
|
|
557
558
|
}
|
|
558
559
|
```
|
|
559
560
|
|
|
560
|
-
|
|
561
|
-
```json
|
|
562
|
-
{
|
|
563
|
-
"keys": [
|
|
564
|
-
{
|
|
565
|
-
"kty": "EC",
|
|
566
|
-
"kid": "my-key-id-1",
|
|
567
|
-
"use": "sig",
|
|
568
|
-
"alg": "ES256",
|
|
569
|
-
"crv": "P-256",
|
|
570
|
-
"x": "...",
|
|
571
|
-
"y": "..."
|
|
572
|
-
}
|
|
573
|
-
]
|
|
574
|
-
}
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
**Cache headers:** `Cache-Control: public, max-age=3600, stale-while-revalidate=86400`
|
|
578
|
-
|
|
579
|
-
> [!NOTE]
|
|
580
|
-
> The `/certs` path is configurable via `rest.path` in `IJWKSIssuerOptions`. The component applies the `@controller` decorator dynamically with the configured path.
|
|
581
|
-
|
|
582
|
-
## BasicTokenService
|
|
583
|
-
|
|
584
|
-
All methods are instance methods on <code v-pre>BasicTokenService<E extends Env = Env></code>, which extends `BaseService`.
|
|
561
|
+
The component applies `@controller({ path })` to `JWKSController` dynamically at binding time (via `Reflect.decorate`), since the path depends on a runtime option.
|
|
585
562
|
|
|
586
|
-
|
|
563
|
+
## Controller factory
|
|
587
564
|
|
|
588
|
-
|
|
565
|
+
`defineAuthController(opts: TDefineAuthControllerOpts): typeof AuthController` builds a `BaseRestController` subclass at runtime.
|
|
589
566
|
|
|
590
|
-
|
|
567
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/controllers/factory.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/controllers/factory.ts)
|
|
591
568
|
|
|
592
|
-
|
|
593
|
-
class BasicAuthenticationStrategy<E extends Env = Env>
|
|
594
|
-
extends BaseHelper
|
|
595
|
-
implements IAuthenticationStrategy<E>
|
|
596
|
-
{
|
|
597
|
-
name = Authentication.STRATEGY_BASIC;
|
|
598
|
-
|
|
599
|
-
constructor(
|
|
600
|
-
@inject({
|
|
601
|
-
key: BindingKeys.build({
|
|
602
|
-
namespace: BindingNamespaces.SERVICE,
|
|
603
|
-
key: BasicTokenService.name,
|
|
604
|
-
}),
|
|
605
|
-
})
|
|
606
|
-
private service: BasicTokenService<E>,
|
|
607
|
-
) { ... }
|
|
608
|
-
|
|
609
|
-
async authenticate(context: TContext<E, string>): Promise<IAuthUser> {
|
|
610
|
-
const credentials = this.service.extractCredentials(context);
|
|
611
|
-
return this.service.verify({ credentials, context });
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
### Methods
|
|
617
|
-
|
|
618
|
-
| Method | Signature | Description |
|
|
619
|
-
|--------|-----------|-------------|
|
|
620
|
-
| `extractCredentials` | <code v-pre>(context: TContext<E, string>) => { username: string; password: string }</code> | Decodes Base64 <code v-pre>Authorization: Basic <base64></code> header |
|
|
621
|
-
| `verify` | <code v-pre>(opts: { credentials: { username: string; password: string }; context: TContext<E, string> }) => Promise<IAuthUser></code> | Calls user-provided `verifyCredentials` callback |
|
|
622
|
-
|
|
623
|
-
### Constructor Behavior
|
|
569
|
+
**How it works:**
|
|
624
570
|
|
|
625
|
-
|
|
626
|
-
constructor
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
) {
|
|
630
|
-
// Throws '[BasicTokenService] Invalid verifyCredentials function' if !options?.verifyCredentials
|
|
631
|
-
}
|
|
632
|
-
```
|
|
571
|
+
1. **Class creation.** `class AuthController extends BaseRestController {}` inside the factory closure, decorated with `@controller({ path: restPath })`, `isStrict: true`.
|
|
572
|
+
2. **Service injection.** `inject({ key: serviceKey })(AuthController, undefined, 0)` is applied *after* class definition - the programmatic equivalent of decorating constructor parameter 0. The constructor throws `400` if the resolved service is falsy.
|
|
573
|
+
3. **Routes.** Defined in `binding()` via `this.defineRoute()` - see the endpoint table in [Usage & Examples](./usage#api-endpoints).
|
|
574
|
+
4. **Schemas.** Each endpoint uses `payload.<name>.{request,response}.schema` if you provide it. Otherwise it falls back to a built-in default, or `AnyObjectSchema` where there's no built-in schema for that response.
|
|
633
575
|
|
|
634
|
-
|
|
576
|
+
Also exports `JWTTokenPayloadSchema`, the Zod schema backing `/who-am-i`'s response. It's extended at runtime with an optional `userInformation` field, typed from `payload.getUserInformation.response.schema` (or `AnyObjectSchema` if you don't provide one).
|
|
635
577
|
|
|
636
|
-
|
|
578
|
+
## Entity column helper types
|
|
637
579
|
|
|
638
|
-
|
|
580
|
+
Exported for extending the auth entity column helpers - see [Usage & Examples](./usage#entity-column-helpers) for the columns themselves.
|
|
639
581
|
|
|
640
582
|
```typescript
|
|
641
|
-
type TPermissionOptions = {
|
|
642
|
-
idType?: 'string' | 'number';
|
|
643
|
-
};
|
|
644
|
-
|
|
583
|
+
type TPermissionOptions = { idType?: 'string' | 'number' };
|
|
645
584
|
type TPermissionCommonColumns = {
|
|
646
585
|
code: NotNull<PgTextBuilderInitial<...>>;
|
|
647
586
|
name: NotNull<PgTextBuilderInitial<...>>;
|
|
648
587
|
subject: NotNull<PgTextBuilderInitial<...>>;
|
|
588
|
+
method: NotNull<PgTextBuilderInitial<...>>;
|
|
649
589
|
action: NotNull<PgTextBuilderInitial<...>>;
|
|
650
590
|
scope: NotNull<PgTextBuilderInitial<...>>;
|
|
651
|
-
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
### Policy Definition Types
|
|
655
|
-
|
|
656
|
-
```typescript
|
|
657
|
-
type TPolicyDefinitionOptions = {
|
|
658
|
-
idType?: 'string' | 'number';
|
|
591
|
+
description: PgTextBuilderInitial<...>;
|
|
659
592
|
};
|
|
660
593
|
|
|
594
|
+
type TPolicyDefinitionOptions = { idType?: 'string' | 'number' };
|
|
661
595
|
type TPolicyDefinitionCommonColumns = {
|
|
662
596
|
variant: ReturnType<typeof text>;
|
|
663
597
|
subjectType: ReturnType<typeof text>;
|
|
@@ -665,92 +599,45 @@ type TPolicyDefinitionCommonColumns = {
|
|
|
665
599
|
action: ReturnType<typeof text>;
|
|
666
600
|
effect: ReturnType<typeof text>;
|
|
667
601
|
domain: ReturnType<typeof text>;
|
|
602
|
+
metadata: ReturnType<typeof jsonb>;
|
|
668
603
|
};
|
|
669
604
|
```
|
|
670
605
|
|
|
671
|
-
##
|
|
672
|
-
|
|
673
|
-
The `defineAuthController()` function dynamically creates a controller class at runtime using decorator composition:
|
|
674
|
-
|
|
675
|
-
**How it works:**
|
|
676
|
-
|
|
677
|
-
1. **Class creation:** A new class is created dynamically with `class AuthController extends BaseRestController {}` inside the factory closure
|
|
678
|
-
2. **Decorator application:** The `@controller({ path: restPath })` decorator is applied to set the base path. The controller is created with `isStrict: true`
|
|
679
|
-
3. **Service injection:** The auth service is injected via `inject({ key: serviceKey })(AuthController, undefined, 0)` after class definition -- this programmatically applies `@inject` to constructor parameter 0
|
|
680
|
-
- Service key is provided via `controllerOpts.serviceKey` (required)
|
|
681
|
-
- Service must implement `IAuthService` interface
|
|
682
|
-
4. **Route definition:** Routes are defined in the controller's `binding()` method using `this.defineRoute()`
|
|
683
|
-
5. **Schema customization:** Custom Zod schemas can be provided per endpoint via the `payload` option. Defaults to built-in schemas when not provided, with `AnyObjectSchema` as the response fallback.
|
|
684
|
-
|
|
685
|
-
**Factory signature:**
|
|
686
|
-
|
|
687
|
-
```typescript
|
|
688
|
-
function defineAuthController(opts: TDefineAuthControllerOpts): typeof AuthController;
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
> [!NOTE]
|
|
692
|
-
> The factory also exports `JWTTokenPayloadSchema`, a Zod schema used for the `/who-am-i` response validation. For `/who-am-i`, it is extended with an optional `userInformation` field that is populated when the request sets `?withUserInformation=true`. The same `getUserInformation` service method backs the dedicated `GET /me` route.
|
|
693
|
-
|
|
694
|
-
**Service resolution:**
|
|
695
|
-
|
|
696
|
-
The factory applies `@inject` programmatically to constructor parameter 0:
|
|
697
|
-
|
|
698
|
-
```typescript
|
|
699
|
-
// Inside defineAuthController, after class definition:
|
|
700
|
-
inject({ key: serviceKey })(AuthController, undefined, 0);
|
|
701
|
-
```
|
|
702
|
-
|
|
703
|
-
This is equivalent to:
|
|
704
|
-
```typescript
|
|
705
|
-
constructor(
|
|
706
|
-
@inject({ key: serviceKey })
|
|
707
|
-
authService: IAuthService,
|
|
708
|
-
) { ... }
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
If the service is not bound, the component will throw: `"[AuthController] Failed to init auth controller | Invalid injectable authentication service!"`
|
|
712
|
-
|
|
713
|
-
## File Structure
|
|
606
|
+
## File structure
|
|
714
607
|
|
|
715
608
|
```
|
|
716
|
-
packages/core/src/components/auth/
|
|
717
|
-
├──
|
|
718
|
-
│ ├──
|
|
719
|
-
│ ├──
|
|
720
|
-
│ ├──
|
|
721
|
-
│ ├──
|
|
722
|
-
│
|
|
723
|
-
|
|
724
|
-
│ ├──
|
|
725
|
-
│
|
|
726
|
-
│
|
|
727
|
-
│
|
|
728
|
-
|
|
729
|
-
│
|
|
730
|
-
|
|
731
|
-
│
|
|
732
|
-
├──
|
|
733
|
-
│
|
|
734
|
-
│ │
|
|
735
|
-
│ ├──
|
|
736
|
-
│ │
|
|
737
|
-
│
|
|
738
|
-
│
|
|
739
|
-
|
|
740
|
-
│
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
│ ├── basic.strategy.ts # BasicAuthenticationStrategy
|
|
745
|
-
│ ├── jws.strategy.ts # JWSAuthenticationStrategy
|
|
746
|
-
│ ├── jwks.strategy.ts # JWKSIssuerAuthenticationStrategy + JWKSVerifierAuthenticationStrategy
|
|
747
|
-
│ ├── strategy-registry.ts # AuthenticationStrategyRegistry singleton
|
|
748
|
-
│ └── index.ts # Barrel export
|
|
749
|
-
└── component.ts # AuthenticateComponent
|
|
609
|
+
packages/core-server/src/components/auth/
|
|
610
|
+
├── authenticate/
|
|
611
|
+
│ ├── common/
|
|
612
|
+
│ │ ├── codecs.ts # AuthenticationFieldCodecs (ROLES_CODEC, build() factory)
|
|
613
|
+
│ │ ├── constants.ts # AuthenticateStrategy, JOSEStandards, JWKSModes, JWKSKeyDrivers, JWKSKeyFormats, Authentication, AuthenticationTokenTypes, AuthenticationModes
|
|
614
|
+
│ │ ├── keys.ts # AuthenticateBindingKeys
|
|
615
|
+
│ │ ├── types.ts # Option interfaces, discriminated unions, IAuthUser, IJWTTokenPayload, IAuthService
|
|
616
|
+
│ │ └── index.ts
|
|
617
|
+
│ ├── controllers/
|
|
618
|
+
│ │ ├── factory.ts # defineAuthController() + JWTTokenPayloadSchema
|
|
619
|
+
│ │ └── jwks/ # JWKSController + route config
|
|
620
|
+
│ ├── middlewares/
|
|
621
|
+
│ │ └── authenticate.middleware.ts # Standalone authenticate() function
|
|
622
|
+
│ ├── providers/
|
|
623
|
+
│ │ └── authentication.provider.ts # AuthenticationProvider
|
|
624
|
+
│ ├── services/
|
|
625
|
+
│ │ ├── basic/service.ts # BasicTokenService
|
|
626
|
+
│ │ └── bearer/
|
|
627
|
+
│ │ ├── abstract.service.ts # AbstractBearerTokenService
|
|
628
|
+
│ │ ├── jws.service.ts # JWSTokenService
|
|
629
|
+
│ │ └── jwks/ # AbstractJWKSTokenService, JWKSIssuerTokenService, JWKSVerifierTokenService
|
|
630
|
+
│ ├── strategies/ # JWSAuthenticationStrategy, JWKS*, BasicAuthenticationStrategy, AuthenticationStrategyRegistry
|
|
631
|
+
│ └── component.ts # AuthenticateComponent
|
|
632
|
+
├── base/
|
|
633
|
+
│ └── abstract-auth-registry.ts # AbstractAuthRegistry (shared by authenticate + authorize)
|
|
634
|
+
└── models/
|
|
635
|
+
├── entities/ # extraUserColumns, extraRoleColumns, extraPermissionColumns, extraPolicyDefinitionColumns
|
|
636
|
+
└── requests/ # SignInRequestSchema, SignUpRequestSchema, ChangePasswordRequestSchema
|
|
750
637
|
```
|
|
751
638
|
|
|
752
|
-
## See
|
|
639
|
+
## See also
|
|
753
640
|
|
|
754
|
-
- [
|
|
755
|
-
- [Usage & Examples](./usage)
|
|
756
|
-
- [Error Reference](./errors)
|
|
641
|
+
- [Overview](./) - guided introduction and common tasks
|
|
642
|
+
- [Usage & Examples](./usage) - securing routes, auth flows, JWKS microservice patterns, API endpoints
|
|
643
|
+
- [Error Reference](./errors) - every error message and how to fix it
|