@venizia/ignis-docs 0.2.0 → 0.2.1-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.
- package/README.md +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +22 -11
- 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 +26 -2
- 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 +6 -2
- 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 +182 -93
- 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 +107 -322
- package/content/extensions/components/authentication/api.md +454 -603
- package/content/extensions/components/authentication/errors.md +121 -498
- package/content/extensions/components/authentication/index.md +88 -801
- package/content/extensions/components/authentication/usage.md +207 -956
- package/content/extensions/components/authorization/api.md +736 -656
- package/content/extensions/components/authorization/errors.md +168 -206
- package/content/extensions/components/authorization/index.md +82 -797
- package/content/extensions/components/authorization/usage.md +194 -527
- package/content/extensions/components/health-check.md +71 -243
- package/content/extensions/components/mail/api.md +504 -287
- package/content/extensions/components/mail/errors.md +73 -61
- package/content/extensions/components/mail/index.md +96 -467
- package/content/extensions/components/mail/usage.md +130 -172
- package/content/extensions/components/request-tracker.md +66 -173
- package/content/extensions/components/socket-io/api.md +195 -13
- package/content/extensions/components/socket-io/errors.md +3 -3
- package/content/extensions/components/socket-io/index.md +50 -337
- package/content/extensions/components/socket-io/usage.md +143 -26
- package/content/extensions/components/static-asset/api.md +410 -142
- package/content/extensions/components/static-asset/errors.md +110 -53
- package/content/extensions/components/static-asset/index.md +79 -608
- package/content/extensions/components/static-asset/usage.md +180 -300
- package/content/extensions/components/websocket/api.md +275 -399
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +74 -407
- package/content/extensions/components/websocket/usage.md +110 -341
- package/content/extensions/helpers/cron/index.md +51 -160
- package/content/extensions/helpers/crypto/index.md +62 -483
- package/content/extensions/helpers/crypto/reference.md +456 -0
- package/content/extensions/helpers/env/index.md +60 -178
- package/content/extensions/helpers/error/index.md +221 -207
- package/content/extensions/helpers/inversion/index.md +65 -556
- package/content/extensions/helpers/inversion/reference.md +522 -0
- package/content/extensions/helpers/kafka/admin.md +20 -1
- package/content/extensions/helpers/kafka/compile-binary.md +41 -35
- package/content/extensions/helpers/kafka/consumer.md +54 -20
- package/content/extensions/helpers/kafka/examples.md +21 -16
- package/content/extensions/helpers/kafka/index.md +80 -610
- package/content/extensions/helpers/kafka/producer.md +134 -5
- package/content/extensions/helpers/kafka/schema-registry.md +45 -70
- package/content/extensions/helpers/logger/hf-logger.md +193 -0
- package/content/extensions/helpers/logger/index.md +64 -563
- package/content/extensions/helpers/logger/pino.md +85 -0
- package/content/extensions/helpers/logger/reference.md +746 -0
- package/content/extensions/helpers/network/api.md +241 -195
- package/content/extensions/helpers/network/index.md +72 -530
- package/content/extensions/helpers/queue/index.md +72 -900
- package/content/extensions/helpers/queue/reference.md +467 -0
- package/content/extensions/helpers/redis/index.md +73 -645
- package/content/extensions/helpers/redis/reference.md +727 -0
- package/content/extensions/helpers/secrets/index.md +66 -0
- package/content/extensions/helpers/socket-io/api.md +305 -203
- package/content/extensions/helpers/socket-io/index.md +66 -432
- package/content/extensions/helpers/storage/api.md +564 -462
- package/content/extensions/helpers/storage/index.md +77 -573
- package/content/extensions/helpers/types/index.md +66 -499
- package/content/extensions/helpers/types/reference.md +650 -0
- package/content/extensions/helpers/uid/index.md +58 -227
- package/content/extensions/helpers/websocket/api.md +329 -216
- package/content/extensions/helpers/websocket/index.md +65 -503
- package/content/extensions/helpers/worker-thread/index.md +58 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
- package/content/guides/core-concepts/persistent/transactions.md +1 -1
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/migrations/redis-helpers-migration.md +1 -1
- package/content/guides/migrations/unified-connectors-migration.md +2 -2
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/references/base/application.md +1 -1
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/datasources-reference.md +599 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +17 -39
- package/content/references/base/filter-system/application-usage.md +69 -121
- package/content/references/base/filter-system/array-operators.md +12 -0
- package/content/references/base/filter-system/comparison-operators.md +12 -0
- package/content/references/base/filter-system/default-filter.md +136 -348
- package/content/references/base/filter-system/fields-order-pagination.md +38 -16
- package/content/references/base/filter-system/index.md +106 -257
- package/content/references/base/filter-system/json-filtering.md +12 -2
- package/content/references/base/filter-system/list-operators.md +16 -2
- package/content/references/base/filter-system/logical-operators.md +13 -0
- package/content/references/base/filter-system/null-operators.md +13 -0
- package/content/references/base/filter-system/pattern-matching.md +12 -0
- package/content/references/base/filter-system/quick-reference.md +11 -2
- package/content/references/base/filter-system/range-operators.md +12 -0
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +156 -233
- package/content/references/base/middlewares.md +35 -21
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +80 -1452
- package/content/references/base/repositories/advanced.md +156 -192
- package/content/references/base/repositories/index.md +77 -650
- package/content/references/base/repositories/mixins.md +22 -18
- package/content/references/base/repositories/relations.md +123 -171
- package/content/references/base/repositories/soft-deletable.md +58 -56
- package/content/references/base/secrets.md +263 -0
- package/content/references/base/services.md +2 -2
- package/content/references/configuration/environment-variables.md +48 -4
- package/content/references/configuration/index.md +49 -31
- 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/index.md +1 -1
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +29 -62
- 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 +57 -218
- 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/package.json +8 -8
|
@@ -1,346 +1,110 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
| Component | Purpose |
|
|
16
|
-
|-----------|---------|
|
|
17
|
-
| **AuthenticateComponent** | Main component registering auth services and controllers |
|
|
18
|
-
| **AuthenticationStrategyRegistry** | Singleton managing available auth strategies |
|
|
19
|
-
| **JWSAuthenticationStrategy** | JWT verification using symmetric `JWSTokenService` (HS256) |
|
|
20
|
-
| **JWKSIssuerAuthenticationStrategy** | JWT verification using asymmetric `JWKSIssuerTokenService` (ES256/RS256/EdDSA) |
|
|
21
|
-
| **JWKSVerifierAuthenticationStrategy** | JWT verification via remote JWKS URL |
|
|
22
|
-
| **BasicAuthenticationStrategy** | Basic HTTP authentication using `BasicTokenService` |
|
|
23
|
-
| **AbstractBearerTokenService** | Base class for all Bearer token services (JWS, JWKS Issuer, JWKS Verifier) |
|
|
24
|
-
| **JWSTokenService** | Symmetric JWT - sign, verify, optional AES encrypt/decrypt |
|
|
25
|
-
| **JWKSIssuerTokenService** | Asymmetric JWT - sign with private key, verify with public key, serve JWKS endpoint |
|
|
26
|
-
| **JWKSVerifierTokenService** | Asymmetric JWT - verify-only via remote JWKS URL |
|
|
27
|
-
| **BasicTokenService** | Extract and verify Basic auth credentials |
|
|
28
|
-
| **JWKSController** | Serves `/.well-known/jwks.json`-style endpoint at `/certs` |
|
|
29
|
-
| **IAuthService** | Interface for custom auth implementation (sign-in, sign-up) |
|
|
30
|
-
| **defineAuthController** | Factory function for creating custom auth controllers |
|
|
31
|
-
| **AbstractJWKSTokenService** | Base class for JWKS services with lazy initialization and retry-on-failure |
|
|
32
|
-
| **authenticate** | Standalone middleware function using `AuthenticationProvider` to create auth middleware |
|
|
33
|
-
|
|
34
|
-
### JOSE Standards
|
|
35
|
-
|
|
36
|
-
The authentication module supports two JOSE (JSON Object Signing and Encryption) standards:
|
|
37
|
-
|
|
38
|
-
| Standard | Class | Use Case | Signing | Key Type |
|
|
39
|
-
|----------|-------|----------|---------|----------|
|
|
40
|
-
| **JWS** | `JWSTokenService` | Single-service apps where the same service signs and verifies | HS256 (symmetric) | Shared secret (`jwtSecret`) |
|
|
41
|
-
| **JWKS (Issuer)** | `JWKSIssuerTokenService` | Multi-service / microservice architectures where one service issues tokens | ES256 / RS256 / EdDSA (asymmetric) | Private key (sign) + Public key (verify) |
|
|
42
|
-
| **JWKS (Verifier)** | `JWKSVerifierTokenService` | Services that only verify tokens issued by another service | N/A (verify-only) | Remote JWKS URL |
|
|
43
|
-
|
|
44
|
-
### Environment Variables
|
|
45
|
-
|
|
46
|
-
| Variable | Purpose | Required |
|
|
47
|
-
|----------|---------|----------|
|
|
48
|
-
| `APP_ENV_JWT_SECRET` | Sign and verify JWT signature (JWS only) | Required for JWS |
|
|
49
|
-
| `APP_ENV_APPLICATION_SECRET` | AES-encrypt JWT payload fields | Optional |
|
|
50
|
-
| `APP_ENV_JWT_EXPIRES_IN` | Token expiration (seconds) | Required |
|
|
51
|
-
| `APP_ENV_JWKS_ALGORITHM` | JWKS signing algorithm (e.g., `ES256`) | Required for JWKS |
|
|
52
|
-
| `APP_ENV_JWKS_KEY_DRIVER` | Key source: `text` or `file` | Required for JWKS |
|
|
53
|
-
| `APP_ENV_JWKS_KEY_FORMAT` | Key format: `pem` or `jwk` | Required for JWKS |
|
|
54
|
-
| `APP_ENV_JWKS_PRIVATE_KEY` | Private key content or file path | Required for JWKS Issuer |
|
|
55
|
-
| `APP_ENV_JWKS_PUBLIC_KEY` | Public key content or file path | Required for JWKS Issuer |
|
|
56
|
-
| `APP_ENV_JWKS_KID` | Key ID for JWKS endpoint | Required for JWKS Issuer |
|
|
57
|
-
|
|
58
|
-
### Auth Modes
|
|
59
|
-
|
|
60
|
-
| Mode | Behavior |
|
|
61
|
-
|------|----------|
|
|
62
|
-
| `'any'` | First successful strategy wins (fallback mode) |
|
|
63
|
-
| `'all'` | All strategies must pass (MFA mode) |
|
|
64
|
-
|
|
65
|
-
### Token Types
|
|
66
|
-
|
|
67
|
-
| Constant | Value | Description |
|
|
68
|
-
|----------|-------|-------------|
|
|
69
|
-
| `AuthenticationTokenTypes.TYPE_AUTHORIZATION_CODE` | `'000_AUTHORIZATION_CODE'` | Authorization code grant type |
|
|
70
|
-
| `AuthenticationTokenTypes.TYPE_ACCESS_TOKEN` | `'100_ACCESS_TOKEN'` | Access token type |
|
|
71
|
-
| `AuthenticationTokenTypes.TYPE_REFRESH_TOKEN` | `'200_REFRESH_TOKEN'` | Refresh token type |
|
|
72
|
-
|
|
73
|
-
### Authentication Constants
|
|
74
|
-
|
|
75
|
-
| Constant | Value | Description |
|
|
76
|
-
|----------|-------|-------------|
|
|
77
|
-
| `Authentication.AUTHENTICATION_STRATEGY` | `'authentication.strategy'` | Namespace prefix for strategy binding keys |
|
|
78
|
-
| `Authentication.STRATEGY_JWT` | `'jwt'` | JWT strategy name |
|
|
79
|
-
| `Authentication.STRATEGY_BASIC` | `'basic'` | Basic strategy name |
|
|
80
|
-
| `Authentication.TYPE_BEARER` | `'Bearer'` | Bearer token type prefix |
|
|
81
|
-
| `Authentication.TYPE_BASIC` | `'Basic'` | Basic token type prefix |
|
|
82
|
-
| `Authentication.SKIP_AUTHENTICATION` | `'authentication.skip'` | Context key to dynamically skip auth |
|
|
83
|
-
| `Authentication.CURRENT_USER` | `'auth.current.user'` | Context key for the authenticated user payload |
|
|
84
|
-
| `Authentication.AUDIT_USER_ID` | `'audit.user.id'` | Context key for the authenticated user ID |
|
|
85
|
-
|
|
86
|
-
### JWKS Constants
|
|
87
|
-
|
|
88
|
-
| Class | Constant | Value | Description |
|
|
89
|
-
|-------|----------|-------|-------------|
|
|
90
|
-
| `JOSEStandards` | `JWS` | `'JWS'` | Symmetric JWT standard |
|
|
91
|
-
| `JOSEStandards` | `JWKS` | `'JWKS'` | Asymmetric JWT standard |
|
|
92
|
-
| `JWKSModes` | `ISSUER` | `'issuer'` | Issuer mode (sign + verify + serve JWKS) |
|
|
93
|
-
| `JWKSModes` | `VERIFIER` | `'verifier'` | Verifier mode (verify-only via remote JWKS) |
|
|
94
|
-
| `JWKSKeyDrivers` | `TEXT` | `'text'` | Key provided as inline text |
|
|
95
|
-
| `JWKSKeyDrivers` | `FILE` | `'file'` | Key loaded from file path |
|
|
96
|
-
| `JWKSKeyFormats` | `PEM` | `'pem'` | PEM-encoded key format |
|
|
97
|
-
| `JWKSKeyFormats` | `JWK` | `'jwk'` | JSON Web Key format |
|
|
98
|
-
|
|
99
|
-
Each constants class also provides:
|
|
100
|
-
- `SCHEME_SET: Set<string>` - set of all valid values
|
|
101
|
-
- `isValid(input: string): boolean` - check if a value is recognized
|
|
102
|
-
|
|
103
|
-
#### Import Paths
|
|
1
|
+
---
|
|
2
|
+
title: Authentication
|
|
3
|
+
description: JWT (JWS/JWKS) and Basic HTTP authentication component with multi-strategy support and a built-in auth controller
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Authentication
|
|
8
|
+
|
|
9
|
+
`AuthenticateComponent` wires up JWT and/or Basic HTTP authentication - token services, route-level strategies, and an optional built-in `/auth` controller for sign-in, sign-up, and password change.
|
|
10
|
+
|
|
11
|
+
## In one example
|
|
12
|
+
|
|
13
|
+
The smallest real setup: symmetric JWT (JWS), one strategy, one protected route.
|
|
104
14
|
|
|
105
15
|
```typescript
|
|
106
16
|
import {
|
|
107
|
-
// Component + Registry
|
|
108
|
-
AuthenticateComponent,
|
|
109
17
|
AuthenticateBindingKeys,
|
|
18
|
+
AuthenticateComponent,
|
|
110
19
|
Authentication,
|
|
111
|
-
AuthenticationFieldCodecs,
|
|
112
|
-
AuthenticationModes,
|
|
113
|
-
AuthenticationTokenTypes,
|
|
114
20
|
AuthenticationStrategyRegistry,
|
|
115
|
-
|
|
116
|
-
// JOSE Standards + Constants
|
|
21
|
+
BaseApplication,
|
|
117
22
|
JOSEStandards,
|
|
118
|
-
JWKSModes,
|
|
119
|
-
JWKSKeyDrivers,
|
|
120
|
-
JWKSKeyFormats,
|
|
121
|
-
|
|
122
|
-
// Strategies
|
|
123
23
|
JWSAuthenticationStrategy,
|
|
124
|
-
JWKSIssuerAuthenticationStrategy,
|
|
125
|
-
JWKSVerifierAuthenticationStrategy,
|
|
126
|
-
BasicAuthenticationStrategy,
|
|
127
|
-
|
|
128
|
-
// Services
|
|
129
|
-
AbstractBearerTokenService,
|
|
130
|
-
JWSTokenService,
|
|
131
|
-
JWKSIssuerTokenService,
|
|
132
|
-
JWKSVerifierTokenService,
|
|
133
|
-
BasicTokenService,
|
|
134
|
-
|
|
135
|
-
// Controllers
|
|
136
|
-
defineAuthController,
|
|
137
|
-
JWKSController,
|
|
138
|
-
authenticate,
|
|
139
|
-
} from '@venizia/ignis';
|
|
140
|
-
|
|
141
|
-
import type {
|
|
142
|
-
// Option types
|
|
143
|
-
TAuthenticationRestOptions,
|
|
144
24
|
TJWTTokenServiceOptions,
|
|
145
|
-
|
|
146
|
-
IJWKSIssuerOptions,
|
|
147
|
-
IJWKSVerifierOptions,
|
|
148
|
-
TJWKSTokenServiceOptions,
|
|
149
|
-
TBasicTokenServiceOptions,
|
|
150
|
-
IAuthenticateOptions,
|
|
151
|
-
|
|
152
|
-
// User + payload types
|
|
153
|
-
IAuthUser,
|
|
154
|
-
IJWTTokenPayload,
|
|
155
|
-
IPayloadFieldCodec,
|
|
156
|
-
IAuthService,
|
|
157
|
-
IAuthenticationStrategy,
|
|
158
|
-
|
|
159
|
-
// Controller types
|
|
160
|
-
TDefineAuthControllerOpts,
|
|
161
|
-
|
|
162
|
-
// Utility types
|
|
163
|
-
TAuthStrategy,
|
|
164
|
-
TAuthMode,
|
|
165
|
-
TGetTokenExpiresFn,
|
|
166
|
-
TJWKSAlgorithm,
|
|
167
|
-
TJWKSKeyDriver,
|
|
168
|
-
TJWKSKeyFormat,
|
|
169
|
-
TJOSEStandard,
|
|
170
|
-
TJWKSMode,
|
|
25
|
+
ValueOrPromise,
|
|
171
26
|
} from '@venizia/ignis';
|
|
172
|
-
```
|
|
173
27
|
|
|
174
|
-
|
|
28
|
+
export class Application extends BaseApplication {
|
|
29
|
+
preConfigure(): ValueOrPromise<void> {
|
|
30
|
+
this.bind<TJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS }).toValue({
|
|
31
|
+
standard: JOSEStandards.JWS,
|
|
32
|
+
options: {
|
|
33
|
+
jwtSecret: process.env.APP_ENV_JWT_SECRET!,
|
|
34
|
+
getTokenExpiresFn: () => Number(process.env.APP_ENV_JWT_EXPIRES_IN || 86400),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
175
37
|
|
|
176
|
-
|
|
177
|
-
import {
|
|
178
|
-
extraUserColumns,
|
|
179
|
-
extraRoleColumns,
|
|
180
|
-
extraPermissionColumns,
|
|
181
|
-
extraPolicyDefinitionColumns,
|
|
182
|
-
} from '@venizia/ignis';
|
|
38
|
+
this.component(AuthenticateComponent);
|
|
183
39
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
40
|
+
// Strategies are NOT auto-registered - register them after the component
|
|
41
|
+
AuthenticationStrategyRegistry.getInstance().register({
|
|
42
|
+
container: this,
|
|
43
|
+
strategies: [{ name: Authentication.STRATEGY_JWT, strategy: JWSAuthenticationStrategy }],
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
190
47
|
```
|
|
191
48
|
|
|
192
|
-
#### Status and Type Imports
|
|
193
|
-
|
|
194
49
|
```typescript
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
50
|
+
const SECURE_ROUTE = {
|
|
51
|
+
path: '/data',
|
|
52
|
+
method: HTTP.Methods.GET,
|
|
53
|
+
authenticate: { strategies: [Authentication.STRATEGY_JWT] },
|
|
54
|
+
responses: jsonResponse({ description: 'Protected', schema: z.object({ data: z.any() }) }),
|
|
55
|
+
} as const;
|
|
200
56
|
```
|
|
201
57
|
|
|
202
|
-
##
|
|
203
|
-
|
|
204
|
-
```mermaid
|
|
205
|
-
flowchart TD
|
|
206
|
-
A["preConfigure()"] --> B["Bind JWT_OPTIONS / BASIC_OPTIONS / REST_OPTIONS"]
|
|
207
|
-
B --> C["this.component(AuthenticateComponent)"]
|
|
208
|
-
C --> D["AuthenticateComponent.binding()"]
|
|
209
|
-
D --> E{"jwtOptions.standard?"}
|
|
210
|
-
E -->|"JWS"| F["defineJWSAuth()"]
|
|
211
|
-
E -->|"JWKS"| G["defineJWKSAuth()"]
|
|
212
|
-
E -->|"none"| H["Skip JWT"]
|
|
213
|
-
F --> I["Register JWSTokenService"]
|
|
214
|
-
G --> J{"mode?"}
|
|
215
|
-
J -->|"issuer"| K["Register JWKSIssuerTokenService + JWKSController"]
|
|
216
|
-
J -->|"verifier"| L["Register JWKSVerifierTokenService"]
|
|
217
|
-
D --> M["defineBasicAuth()"]
|
|
218
|
-
M --> N["Register BasicTokenService"]
|
|
219
|
-
D --> O["defineControllers()"]
|
|
220
|
-
O --> P["Register AuthController (factory-built)"]
|
|
221
|
-
C --> Q["Manual strategy registration"]
|
|
222
|
-
Q --> R["AuthenticationStrategyRegistry.register()"]
|
|
223
|
-
|
|
224
|
-
style E fill:#fff3cd,stroke:#ffc107
|
|
225
|
-
style J fill:#fff3cd,stroke:#ffc107
|
|
226
|
-
```
|
|
58
|
+
## How it works
|
|
227
59
|
|
|
228
|
-
|
|
60
|
+
- **One component, three auth mechanisms.** `AuthenticateComponent.binding()` reads `JWT_OPTIONS` (JWS or JWKS, via a discriminated `standard` field) and `BASIC_OPTIONS` from the DI container and registers whichever token services their presence implies. At least one of the two must be bound, or the component throws at startup.
|
|
61
|
+
- **Strategies are manual, on purpose.** The component registers *token services* (JWS/JWKS/Basic), not *strategies*. You register strategies yourself via `AuthenticationStrategyRegistry.getInstance().register(...)` after `this.component(AuthenticateComponent)` - this is what gives routes a `Authentication.STRATEGY_JWT` / `'basic'` name to reference.
|
|
62
|
+
- **The registry is a DI-backed singleton.** `AuthenticationStrategyRegistry` binds each registered strategy into the container as a singleton under `authentication.strategy.<name>` and resolves it by name when a route's `authenticate.strategies` list is checked.
|
|
63
|
+
- **`authenticate()` is the middleware entry point.** Route-level `authenticate: { strategies, mode }` config and the standalone `authenticate()` function both go through the same `AuthenticationProvider`, which tries strategies in `'any'` (first success wins) or `'all'` (every strategy must pass) mode and sets `Authentication.CURRENT_USER` on the Hono context.
|
|
64
|
+
- **The auth controller is optional and generated.** Setting `REST_OPTIONS.useAuthController: true` calls `defineAuthController()`, which builds a `BaseRestController` subclass at runtime with `/sign-in`, `/sign-up`, `/change-password`, `/token/refresh`, `/who-am-i`, and `/me` routes, backed by your own `IAuthService` implementation.
|
|
229
65
|
|
|
230
|
-
|
|
66
|
+
**JOSE standards**
|
|
231
67
|
|
|
232
|
-
|
|
68
|
+
| Standard | Class | Keying | Use case |
|
|
69
|
+
|----------|-------|--------|----------|
|
|
70
|
+
| JWS | `JWSTokenService` | Shared secret (HS256) | Single service signs and verifies |
|
|
71
|
+
| JWKS Issuer | `JWKSIssuerTokenService` | Private + public key (ES256/RS256/EdDSA) | This service issues tokens and serves `/certs` |
|
|
72
|
+
| JWKS Verifier | `JWKSVerifierTokenService` | Remote JWKS URL | This service only verifies tokens from another issuer |
|
|
233
73
|
|
|
234
|
-
|
|
74
|
+
## Common tasks
|
|
235
75
|
|
|
236
|
-
|
|
237
|
-
import {
|
|
238
|
-
AuthenticateBindingKeys,
|
|
239
|
-
JOSEStandards,
|
|
240
|
-
TJWTTokenServiceOptions,
|
|
241
|
-
} from '@venizia/ignis';
|
|
76
|
+
**Configure JWS (symmetric JWT).** One shared secret signs and verifies.
|
|
242
77
|
|
|
78
|
+
```typescript
|
|
243
79
|
this.bind<TJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS }).toValue({
|
|
244
80
|
standard: JOSEStandards.JWS,
|
|
245
81
|
options: {
|
|
246
|
-
jwtSecret: process.env.APP_ENV_JWT_SECRET
|
|
247
|
-
applicationSecret: process.env.APP_ENV_APPLICATION_SECRET, // Optional - enables AES payload encryption
|
|
82
|
+
jwtSecret: process.env.APP_ENV_JWT_SECRET!,
|
|
248
83
|
getTokenExpiresFn: () => Number(process.env.APP_ENV_JWT_EXPIRES_IN || 86400),
|
|
249
84
|
},
|
|
250
85
|
});
|
|
251
86
|
```
|
|
252
87
|
|
|
253
|
-
**
|
|
254
|
-
|
|
255
|
-
```
|
|
256
|
-
APP_ENV_JWT_SECRET=your-strong-jwt-secret
|
|
257
|
-
APP_ENV_APPLICATION_SECRET=your-strong-application-secret
|
|
258
|
-
APP_ENV_JWT_EXPIRES_IN=86400
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
> [!NOTE]
|
|
262
|
-
> `applicationSecret` is optional. When provided, all custom JWT claim keys and values are AES-encrypted. When omitted, payloads are stored in plaintext (standard JWT behavior).
|
|
263
|
-
|
|
264
|
-
### JWKS Issuer (Asymmetric JWT) Setup
|
|
88
|
+
**Configure JWKS Issuer (asymmetric JWT, microservice-friendly).** Signs with a private key, serves the public key at `/certs`.
|
|
265
89
|
|
|
266
90
|
```typescript
|
|
267
|
-
import {
|
|
268
|
-
AuthenticateBindingKeys,
|
|
269
|
-
JOSEStandards,
|
|
270
|
-
JWKSModes,
|
|
271
|
-
JWKSKeyDrivers,
|
|
272
|
-
JWKSKeyFormats,
|
|
273
|
-
TJWTTokenServiceOptions,
|
|
274
|
-
} from '@venizia/ignis';
|
|
275
|
-
|
|
276
91
|
this.bind<TJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS }).toValue({
|
|
277
92
|
standard: JOSEStandards.JWKS,
|
|
278
93
|
options: {
|
|
279
94
|
mode: JWKSModes.ISSUER,
|
|
280
95
|
algorithm: 'ES256',
|
|
281
|
-
keys: {
|
|
282
|
-
driver: JWKSKeyDrivers.FILE, // or JWKSKeyDrivers.TEXT
|
|
283
|
-
format: JWKSKeyFormats.PEM, // or JWKSKeyFormats.JWK
|
|
284
|
-
private: './keys/private.pem',
|
|
285
|
-
public: './keys/public.pem',
|
|
286
|
-
},
|
|
96
|
+
keys: { driver: JWKSKeyDrivers.FILE, format: JWKSKeyFormats.PEM, private: './keys/private.pem', public: './keys/public.pem' },
|
|
287
97
|
kid: 'my-key-id-1',
|
|
288
98
|
getTokenExpiresFn: () => Number(process.env.APP_ENV_JWT_EXPIRES_IN || 86400),
|
|
289
|
-
// Optional AES payload encryption
|
|
290
|
-
aesAlgorithm: 'aes-256-cbc',
|
|
291
|
-
applicationSecret: process.env.APP_ENV_APPLICATION_SECRET,
|
|
292
|
-
},
|
|
293
|
-
});
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
**Example `.env` file (JWKS Issuer):**
|
|
297
|
-
|
|
298
|
-
```
|
|
299
|
-
APP_ENV_JWKS_ALGORITHM=ES256
|
|
300
|
-
APP_ENV_JWKS_KEY_DRIVER=file
|
|
301
|
-
APP_ENV_JWKS_KEY_FORMAT=pem
|
|
302
|
-
APP_ENV_JWKS_PRIVATE_KEY=./keys/private.pem
|
|
303
|
-
APP_ENV_JWKS_PUBLIC_KEY=./keys/public.pem
|
|
304
|
-
APP_ENV_JWKS_KID=my-key-id-1
|
|
305
|
-
APP_ENV_JWT_EXPIRES_IN=86400
|
|
306
|
-
APP_ENV_APPLICATION_SECRET=your-strong-application-secret
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
### JWKS Verifier (Remote Verification) Setup
|
|
310
|
-
|
|
311
|
-
```typescript
|
|
312
|
-
import {
|
|
313
|
-
AuthenticateBindingKeys,
|
|
314
|
-
JOSEStandards,
|
|
315
|
-
JWKSModes,
|
|
316
|
-
TJWTTokenServiceOptions,
|
|
317
|
-
} from '@venizia/ignis';
|
|
318
|
-
|
|
319
|
-
this.bind<TJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS }).toValue({
|
|
320
|
-
standard: JOSEStandards.JWKS,
|
|
321
|
-
options: {
|
|
322
|
-
mode: JWKSModes.VERIFIER,
|
|
323
|
-
jwksUrl: 'https://auth-service.example.com/certs',
|
|
324
|
-
cacheTtlMs: 43_200_000, // Cache JWKS for 12 hours (default)
|
|
325
|
-
cooldownMs: 30_000, // Wait 30s between JWKS refreshes (default)
|
|
326
|
-
// Optional AES payload decryption (must match issuer's applicationSecret)
|
|
327
|
-
aesAlgorithm: 'aes-256-cbc',
|
|
328
|
-
applicationSecret: process.env.APP_ENV_APPLICATION_SECRET,
|
|
329
99
|
},
|
|
330
100
|
});
|
|
331
101
|
```
|
|
332
102
|
|
|
333
|
-
|
|
103
|
+
**Add Basic auth.** Provide a `verifyCredentials` callback; it becomes the `'basic'` strategy's source of truth.
|
|
334
104
|
|
|
335
105
|
```typescript
|
|
336
|
-
import {
|
|
337
|
-
AuthenticateBindingKeys,
|
|
338
|
-
TBasicTokenServiceOptions,
|
|
339
|
-
} from '@venizia/ignis';
|
|
340
|
-
|
|
341
106
|
this.bind<TBasicTokenServiceOptions>({ key: AuthenticateBindingKeys.BASIC_OPTIONS }).toValue({
|
|
342
|
-
verifyCredentials: async (
|
|
343
|
-
const { credentials, context } = opts;
|
|
107
|
+
verifyCredentials: async ({ credentials, context }) => {
|
|
344
108
|
const user = await userRepo.findByUsername(credentials.username);
|
|
345
109
|
if (user && await bcrypt.compare(credentials.password, user.passwordHash)) {
|
|
346
110
|
return { userId: user.id, roles: user.roles };
|
|
@@ -350,534 +114,57 @@ this.bind<TBasicTokenServiceOptions>({ key: AuthenticateBindingKeys.BASIC_OPTION
|
|
|
350
114
|
});
|
|
351
115
|
```
|
|
352
116
|
|
|
353
|
-
|
|
117
|
+
**Enable the built-in `/auth` controller.** Requires `jwtOptions` and a bound `IAuthService`.
|
|
354
118
|
|
|
355
119
|
```typescript
|
|
356
|
-
|
|
357
|
-
AuthenticateBindingKeys,
|
|
358
|
-
JOSEStandards,
|
|
359
|
-
JWKSModes,
|
|
360
|
-
JWKSKeyDrivers,
|
|
361
|
-
JWKSKeyFormats,
|
|
362
|
-
TJWTTokenServiceOptions,
|
|
363
|
-
TBasicTokenServiceOptions,
|
|
364
|
-
TAuthenticationRestOptions,
|
|
365
|
-
BindingKeys,
|
|
366
|
-
BindingNamespaces,
|
|
367
|
-
} from '@venizia/ignis';
|
|
120
|
+
this.service(AuthenticationService);
|
|
368
121
|
|
|
369
|
-
// Bind REST options (enables auth controller)
|
|
370
122
|
this.bind<TAuthenticationRestOptions>({ key: AuthenticateBindingKeys.REST_OPTIONS }).toValue({
|
|
371
123
|
useAuthController: true,
|
|
372
124
|
controllerOpts: {
|
|
373
125
|
restPath: '/auth',
|
|
374
|
-
serviceKey: BindingKeys.build({
|
|
375
|
-
namespace: BindingNamespaces.SERVICE,
|
|
376
|
-
key: AuthenticationService.name,
|
|
377
|
-
}),
|
|
378
|
-
payload: {
|
|
379
|
-
signIn: {
|
|
380
|
-
request: { schema: SignInRequestSchema },
|
|
381
|
-
response: { schema: SignInResponseSchema },
|
|
382
|
-
},
|
|
383
|
-
signUp: {
|
|
384
|
-
request: { schema: SignUpRequestSchema },
|
|
385
|
-
response: { schema: SignUpResponseSchema },
|
|
386
|
-
},
|
|
387
|
-
changePassword: {
|
|
388
|
-
request: { schema: ChangePasswordRequestSchema },
|
|
389
|
-
response: { schema: ChangePasswordResponseSchema },
|
|
390
|
-
},
|
|
391
|
-
},
|
|
392
|
-
},
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
// Bind JWT options (JWKS issuer mode)
|
|
396
|
-
this.bind<TJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS }).toValue({
|
|
397
|
-
standard: JOSEStandards.JWKS,
|
|
398
|
-
options: {
|
|
399
|
-
mode: JWKSModes.ISSUER,
|
|
400
|
-
algorithm: 'ES256',
|
|
401
|
-
keys: {
|
|
402
|
-
driver: JWKSKeyDrivers.FILE,
|
|
403
|
-
format: JWKSKeyFormats.PEM,
|
|
404
|
-
private: './keys/private.pem',
|
|
405
|
-
public: './keys/public.pem',
|
|
406
|
-
},
|
|
407
|
-
kid: 'my-key-id-1',
|
|
408
|
-
getTokenExpiresFn: () => Number(process.env.APP_ENV_JWT_EXPIRES_IN || 86400),
|
|
409
|
-
},
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
// Bind Basic auth options
|
|
413
|
-
this.bind<TBasicTokenServiceOptions>({ key: AuthenticateBindingKeys.BASIC_OPTIONS }).toValue({
|
|
414
|
-
verifyCredentials: async (opts) => {
|
|
415
|
-
const authenticateService = this.get<AuthenticationService>({
|
|
416
|
-
key: BindingKeys.build({
|
|
417
|
-
namespace: BindingNamespaces.SERVICE,
|
|
418
|
-
key: AuthenticationService.name,
|
|
419
|
-
}),
|
|
420
|
-
});
|
|
421
|
-
return authenticateService.signIn(opts.context, {
|
|
422
|
-
identifier: { scheme: 'username', value: opts.credentials.username },
|
|
423
|
-
credential: { scheme: 'basic', value: opts.credentials.password },
|
|
424
|
-
});
|
|
126
|
+
serviceKey: BindingKeys.build({ namespace: BindingNamespaces.SERVICE, key: AuthenticationService.name }),
|
|
425
127
|
},
|
|
426
128
|
});
|
|
427
129
|
```
|
|
428
130
|
|
|
429
|
-
|
|
131
|
+
**Secure a route with multiple strategies.** `mode: 'any'` (default) falls back through strategies; `mode: 'all'` requires every one to pass.
|
|
430
132
|
|
|
431
133
|
```typescript
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
Authentication,
|
|
435
|
-
AuthenticationStrategyRegistry,
|
|
436
|
-
JWKSIssuerAuthenticationStrategy,
|
|
437
|
-
BasicAuthenticationStrategy,
|
|
438
|
-
BaseApplication,
|
|
439
|
-
ValueOrPromise,
|
|
440
|
-
} from '@venizia/ignis';
|
|
441
|
-
|
|
442
|
-
export class Application extends BaseApplication {
|
|
443
|
-
preConfigure(): ValueOrPromise<void> {
|
|
444
|
-
// Register your auth service (if using auth controller)
|
|
445
|
-
this.service(AuthenticationService);
|
|
446
|
-
|
|
447
|
-
// Step 1 bindings here...
|
|
448
|
-
|
|
449
|
-
// Register component
|
|
450
|
-
this.component(AuthenticateComponent);
|
|
451
|
-
|
|
452
|
-
// Register strategies manually AFTER the component
|
|
453
|
-
AuthenticationStrategyRegistry.getInstance().register({
|
|
454
|
-
container: this,
|
|
455
|
-
strategies: [
|
|
456
|
-
{ name: Authentication.STRATEGY_JWT, strategy: JWKSIssuerAuthenticationStrategy },
|
|
457
|
-
{ name: Authentication.STRATEGY_BASIC, strategy: BasicAuthenticationStrategy },
|
|
458
|
-
],
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
> [!IMPORTANT]
|
|
465
|
-
> Strategies are NOT auto-registered by `AuthenticateComponent`. You must manually register them after calling `this.component(AuthenticateComponent)`. This gives you full control over which strategies are available.
|
|
466
|
-
|
|
467
|
-
> [!NOTE]
|
|
468
|
-
> Choose the strategy class matching your JOSE standard:
|
|
469
|
-
> - JWS: `JWSAuthenticationStrategy`
|
|
470
|
-
> - JWKS Issuer: `JWKSIssuerAuthenticationStrategy`
|
|
471
|
-
> - JWKS Verifier: `JWKSVerifierAuthenticationStrategy`
|
|
472
|
-
|
|
473
|
-
## Configuration
|
|
474
|
-
|
|
475
|
-
### TJWTTokenServiceOptions (Discriminated Union)
|
|
476
|
-
|
|
477
|
-
```mermaid
|
|
478
|
-
flowchart LR
|
|
479
|
-
T["TJWTTokenServiceOptions"] --> S{"standard"}
|
|
480
|
-
S -->|"'JWS'"| JWS["IJWSTokenServiceOptions"]
|
|
481
|
-
S -->|"'JWKS'"| JWKS["TJWKSTokenServiceOptions"]
|
|
482
|
-
JWKS --> M{"mode"}
|
|
483
|
-
M -->|"'issuer'"| ISS["IJWKSIssuerOptions"]
|
|
484
|
-
M -->|"'verifier'"| VER["IJWKSVerifierOptions"]
|
|
485
|
-
|
|
486
|
-
style S fill:#e8f4fd,stroke:#0d6efd
|
|
487
|
-
style M fill:#e8f4fd,stroke:#0d6efd
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
The top-level JWT options use a discriminated union on the `standard` field:
|
|
491
|
-
|
|
492
|
-
```typescript
|
|
493
|
-
type TJWTTokenServiceOptions =
|
|
494
|
-
| { standard: typeof JOSEStandards.JWS; options: IJWSTokenServiceOptions }
|
|
495
|
-
| { standard: typeof JOSEStandards.JWKS; options: TJWKSTokenServiceOptions };
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
This enables clean TypeScript narrowing - once you set `standard: JOSEStandards.JWS`, the `options` field is typed as `IJWSTokenServiceOptions`; with `standard: JOSEStandards.JWKS`, it becomes `TJWKSTokenServiceOptions`.
|
|
499
|
-
|
|
500
|
-
### JWS Options (IJWSTokenServiceOptions)
|
|
501
|
-
|
|
502
|
-
| Option | Type | Default | Required | Description |
|
|
503
|
-
|--------|------|---------|----------|-------------|
|
|
504
|
-
| `jwtSecret` | `string` | -- | Yes | Secret for signing and verifying JWT signature |
|
|
505
|
-
| `getTokenExpiresFn` | `TGetTokenExpiresFn` | -- | Yes | Function returning token expiration in seconds |
|
|
506
|
-
| `applicationSecret` | `string` | -- | No | Secret for AES-encrypting JWT payload fields |
|
|
507
|
-
| `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload encryption |
|
|
508
|
-
| `headerAlgorithm` | `string` | `'HS256'` | No | JWT signing algorithm |
|
|
509
|
-
| `fieldCodecs` | `IPayloadFieldCodec[]` | `[]` | No | Custom field codecs for payload serialization |
|
|
510
|
-
|
|
511
|
-
```typescript
|
|
512
|
-
interface IJWSTokenServiceOptions {
|
|
513
|
-
headerAlgorithm?: string;
|
|
514
|
-
jwtSecret: string;
|
|
515
|
-
getTokenExpiresFn: TGetTokenExpiresFn;
|
|
516
|
-
aesAlgorithm?: AESAlgorithmType;
|
|
517
|
-
applicationSecret?: string;
|
|
518
|
-
fieldCodecs?: IPayloadFieldCodec[];
|
|
519
|
-
}
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
> [!WARNING]
|
|
523
|
-
> `jwtSecret` is mandatory. The component will throw an error if it is missing or set to `'unknown_secret'`. The error message from `defineJWSAuth` **includes the actual provided secret value** in the error output, so ensure these errors are never exposed to end users.
|
|
524
|
-
|
|
525
|
-
> [!NOTE]
|
|
526
|
-
> `applicationSecret` is optional. When provided, custom JWT payload fields are AES-encrypted (keys and values). When omitted, the JWT payload is stored in standard plaintext. Standard JWT fields (`iss`, `sub`, `aud`, `jti`, `nbf`, `exp`, `iat`) are never encrypted.
|
|
527
|
-
|
|
528
|
-
### JWKS Issuer Options (IJWKSIssuerOptions)
|
|
529
|
-
|
|
530
|
-
| Option | Type | Default | Required | Description |
|
|
531
|
-
|--------|------|---------|----------|-------------|
|
|
532
|
-
| `mode` | `typeof JWKSModes.ISSUER` | -- | Yes | Must be `'issuer'` |
|
|
533
|
-
| `algorithm` | `TJWKSAlgorithm` | -- | Yes | Signing algorithm: `'ES256'`, `'RS256'`, or `'EdDSA'` |
|
|
534
|
-
| `keys.driver` | `TJWKSKeyDriver` | -- | Yes | Key source: `'text'` (inline) or `'file'` (file path) |
|
|
535
|
-
| `keys.format` | `TJWKSKeyFormat` | -- | Yes | Key format: `'pem'` or `'jwk'` |
|
|
536
|
-
| `keys.private` | `string` | -- | Yes | Private key content (text) or file path (file) |
|
|
537
|
-
| `keys.public` | `string` | -- | Yes | Public key content (text) or file path (file) |
|
|
538
|
-
| `kid` | `string` | -- | Yes | Key ID exposed in the JWKS endpoint |
|
|
539
|
-
| `getTokenExpiresFn` | `TGetTokenExpiresFn` | -- | Yes | Function returning token expiration in seconds |
|
|
540
|
-
| `rest` | `{ path: string }` | `{ path: '/certs' }` | No | Custom path for the JWKS endpoint |
|
|
541
|
-
| `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload encryption |
|
|
542
|
-
| `applicationSecret` | `string` | -- | No | Secret for AES-encrypting JWT payload fields |
|
|
543
|
-
| `fieldCodecs` | `IPayloadFieldCodec[]` | `[]` | No | Custom field codecs for payload serialization |
|
|
544
|
-
|
|
545
|
-
```typescript
|
|
546
|
-
interface IJWKSIssuerOptions {
|
|
547
|
-
mode: typeof JWKSModes.ISSUER;
|
|
548
|
-
algorithm: TJWKSAlgorithm;
|
|
549
|
-
rest?: { path: string };
|
|
550
|
-
keys: {
|
|
551
|
-
driver: TJWKSKeyDriver;
|
|
552
|
-
format: TJWKSKeyFormat;
|
|
553
|
-
private: string;
|
|
554
|
-
public: string;
|
|
555
|
-
};
|
|
556
|
-
kid: string;
|
|
557
|
-
getTokenExpiresFn: TGetTokenExpiresFn;
|
|
558
|
-
aesAlgorithm?: AESAlgorithmType;
|
|
559
|
-
applicationSecret?: string;
|
|
560
|
-
fieldCodecs?: IPayloadFieldCodec[];
|
|
561
|
-
}
|
|
562
|
-
```
|
|
563
|
-
|
|
564
|
-
### JWKS Verifier Options (IJWKSVerifierOptions)
|
|
565
|
-
|
|
566
|
-
| Option | Type | Default | Required | Description |
|
|
567
|
-
|--------|------|---------|----------|-------------|
|
|
568
|
-
| `mode` | `typeof JWKSModes.VERIFIER` | -- | Yes | Must be `'verifier'` |
|
|
569
|
-
| `jwksUrl` | `string` | -- | Yes | URL of the remote JWKS endpoint |
|
|
570
|
-
| `cacheTtlMs` | `number` | `43_200_000` (12h) | No | How long to cache the JWKS response |
|
|
571
|
-
| `cooldownMs` | `number` | `30_000` (30s) | No | Minimum time between JWKS refreshes |
|
|
572
|
-
| `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload decryption |
|
|
573
|
-
| `applicationSecret` | `string` | -- | No | Secret for AES-decrypting JWT payload fields |
|
|
574
|
-
| `fieldCodecs` | `IPayloadFieldCodec[]` | `[]` | No | Custom field codecs for payload deserialization |
|
|
575
|
-
|
|
576
|
-
```typescript
|
|
577
|
-
interface IJWKSVerifierOptions {
|
|
578
|
-
mode: typeof JWKSModes.VERIFIER;
|
|
579
|
-
jwksUrl: string;
|
|
580
|
-
cacheTtlMs?: number;
|
|
581
|
-
cooldownMs?: number;
|
|
582
|
-
aesAlgorithm?: AESAlgorithmType;
|
|
583
|
-
applicationSecret?: string;
|
|
584
|
-
fieldCodecs?: IPayloadFieldCodec[];
|
|
585
|
-
}
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
> [!IMPORTANT]
|
|
589
|
-
> In verifier mode, the `applicationSecret` must match the issuer's secret exactly. If the issuer encrypts payloads with AES, the verifier must use the same `applicationSecret` to decrypt them.
|
|
590
|
-
|
|
591
|
-
### JWKS Token Service Options (Union)
|
|
592
|
-
|
|
593
|
-
```typescript
|
|
594
|
-
type TJWKSTokenServiceOptions = IJWKSIssuerOptions | IJWKSVerifierOptions;
|
|
595
|
-
```
|
|
596
|
-
|
|
597
|
-
This union is discriminated on the `mode` field (`'issuer'` vs `'verifier'`).
|
|
598
|
-
|
|
599
|
-
### Basic Auth Options
|
|
600
|
-
|
|
601
|
-
| Option | Type | Default | Description |
|
|
602
|
-
|--------|------|---------|-------------|
|
|
603
|
-
| `verifyCredentials` | `(opts: { credentials, context }) => Promise<IAuthUser \| null>` | -- | Callback to verify Basic auth credentials |
|
|
604
|
-
|
|
605
|
-
The `verifyCredentials` function receives an options object:
|
|
606
|
-
|
|
607
|
-
```typescript
|
|
608
|
-
type TBasicAuthVerifyFn<E extends Env = Env> = (opts: {
|
|
609
|
-
credentials: { username: string; password: string };
|
|
610
|
-
context: TContext<E, string>;
|
|
611
|
-
}) => Promise<IAuthUser | null>;
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
#### TBasicTokenServiceOptions -- Full Interface
|
|
615
|
-
```typescript
|
|
616
|
-
type TBasicTokenServiceOptions<E extends Env = Env> = {
|
|
617
|
-
verifyCredentials: (opts: {
|
|
618
|
-
credentials: { username: string; password: string };
|
|
619
|
-
context: TContext<E, string>;
|
|
620
|
-
}) => Promise<IAuthUser | null>;
|
|
621
|
-
};
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
### REST Options
|
|
625
|
-
|
|
626
|
-
| Option | Type | Default | Description |
|
|
627
|
-
|--------|------|---------|-------------|
|
|
628
|
-
| `useAuthController` | `boolean` | `false` | Enable/disable built-in auth controller |
|
|
629
|
-
| `controllerOpts` | `TDefineAuthControllerOpts` | -- | Configuration for built-in auth controller (required when `useAuthController` is `true`) |
|
|
630
|
-
|
|
631
|
-
`TAuthenticationRestOptions` is a discriminated union type:
|
|
632
|
-
|
|
633
|
-
```typescript
|
|
634
|
-
type TAuthenticationRestOptions = {} & (
|
|
635
|
-
| { useAuthController?: false | undefined }
|
|
636
|
-
| {
|
|
637
|
-
useAuthController: true;
|
|
638
|
-
controllerOpts: TDefineAuthControllerOpts;
|
|
639
|
-
}
|
|
640
|
-
);
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
> [!IMPORTANT]
|
|
644
|
-
> When `useAuthController` is `true`, the `controllerOpts` field becomes required. The discriminated union enforces this at the type level -- you cannot set `useAuthController: true` without providing `controllerOpts`.
|
|
645
|
-
|
|
646
|
-
### Controller Options
|
|
647
|
-
|
|
648
|
-
| Option | Type | Default | Description |
|
|
649
|
-
|--------|------|---------|-------------|
|
|
650
|
-
| `restPath` | `string` | `'/auth'` | Base path for auth endpoints |
|
|
651
|
-
| `serviceKey` | `string` | -- | DI key for the auth service (required) |
|
|
652
|
-
| `requireAuthenticatedSignUp` | `boolean` | `false` | Whether sign-up requires JWT authentication |
|
|
653
|
-
| `payload` | `object` | `{}` | Custom Zod schemas for request/response payloads |
|
|
654
|
-
|
|
655
|
-
#### TDefineAuthControllerOpts -- Full Interface
|
|
656
|
-
```typescript
|
|
657
|
-
type TDefineAuthControllerOpts = {
|
|
658
|
-
restPath?: string;
|
|
659
|
-
serviceKey: string;
|
|
660
|
-
requireAuthenticatedSignUp?: boolean;
|
|
661
|
-
payload?: {
|
|
662
|
-
signIn?: {
|
|
663
|
-
request: { schema: TAnyObjectSchema };
|
|
664
|
-
response: { schema: TAnyObjectSchema };
|
|
665
|
-
};
|
|
666
|
-
signUp?: {
|
|
667
|
-
request: { schema: TAnyObjectSchema };
|
|
668
|
-
response: { schema: TAnyObjectSchema };
|
|
669
|
-
};
|
|
670
|
-
changePassword?: {
|
|
671
|
-
request: { schema?: TAnyObjectSchema };
|
|
672
|
-
response: { schema: TAnyObjectSchema };
|
|
673
|
-
};
|
|
674
|
-
};
|
|
675
|
-
};
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
### Route Configuration Options
|
|
679
|
-
|
|
680
|
-
Per-route authentication is configured via the `authenticate` field on route configs, using `TRouteAuthenticateConfig`:
|
|
681
|
-
|
|
682
|
-
```typescript
|
|
683
|
-
type TRouteAuthenticateConfig =
|
|
684
|
-
| { skip: true }
|
|
685
|
-
| { skip?: false; strategies?: TAuthStrategy[]; mode?: TAuthMode };
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
| Option | Type | Default | Description |
|
|
689
|
-
|--------|------|---------|-------------|
|
|
690
|
-
| `authenticate.strategies` | `TAuthStrategy[]` | -- | Array of strategy names (e.g., `['jwt']`, `['jwt', 'basic']`) |
|
|
691
|
-
| `authenticate.mode` | `'any' \| 'all'` | `'any'` | How to handle multiple strategies |
|
|
692
|
-
| `authenticate.skip` | `true` | -- | Skip authentication for this route |
|
|
693
|
-
|
|
694
|
-
Example route config:
|
|
695
|
-
```typescript
|
|
696
|
-
const SECURE_ROUTE = {
|
|
697
|
-
path: '/data',
|
|
134
|
+
const FALLBACK_AUTH_CONFIG = {
|
|
135
|
+
path: '/api/data',
|
|
698
136
|
method: HTTP.Methods.GET,
|
|
699
|
-
authenticate: { strategies: [Authentication.STRATEGY_JWT] },
|
|
700
|
-
responses: jsonResponse({ description: '
|
|
137
|
+
authenticate: { strategies: [Authentication.STRATEGY_JWT, Authentication.STRATEGY_BASIC], mode: AuthenticationModes.ANY },
|
|
138
|
+
responses: jsonResponse({ description: 'Data', schema: z.object({ data: z.any() }) }),
|
|
701
139
|
} as const;
|
|
702
140
|
```
|
|
703
141
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
The base authenticated user type returned by strategies and available on the context:
|
|
707
|
-
|
|
708
|
-
```typescript
|
|
709
|
-
interface IAuthUser {
|
|
710
|
-
userId: IdType;
|
|
711
|
-
[extra: string | symbol]: any;
|
|
712
|
-
}
|
|
713
|
-
```
|
|
714
|
-
|
|
715
|
-
> [!TIP]
|
|
716
|
-
> `IAuthUser` is intentionally minimal. Your `IAuthService` implementation can extend the user payload with additional fields (roles, email, provider, etc.) -- these extra fields will be preserved through JWT token generation and available after authentication via `Authentication.CURRENT_USER`.
|
|
717
|
-
|
|
718
|
-
### SignInRequestSchema Field Constraints
|
|
719
|
-
|
|
720
|
-
The built-in `SignInRequestSchema` enforces the following validation constraints on sign-in request payloads:
|
|
721
|
-
|
|
722
|
-
| Field | Type | Constraints |
|
|
723
|
-
|-------|------|-------------|
|
|
724
|
-
| `identifier.scheme` | `string` | Non-empty, min 4 chars (required) |
|
|
725
|
-
| `identifier.value` | `string` | Non-empty, min 8 chars (required) |
|
|
726
|
-
| `credential.scheme` | `string` | Non-empty (required) |
|
|
727
|
-
| `credential.value` | `string` | Non-empty, min 8 chars (required) |
|
|
728
|
-
| `clientId` | `string` | Optional |
|
|
729
|
-
|
|
730
|
-
### SignUpRequestSchema Field Constraints
|
|
731
|
-
|
|
732
|
-
The built-in `SignUpRequestSchema` uses a **flat structure** (not nested like `SignInRequestSchema`):
|
|
733
|
-
|
|
734
|
-
| Field | Type | Constraints |
|
|
735
|
-
|-------|------|-------------|
|
|
736
|
-
| `username` | `string` | Non-empty, min 8 chars (required) |
|
|
737
|
-
| `credential` | `string` | Non-empty, min 8 chars (required) |
|
|
142
|
+
**Add auth entity columns to a Drizzle table.** Spread helper functions into `pgTable()` for User/Role/Permission/PolicyDefinition columns.
|
|
738
143
|
|
|
739
|
-
### ChangePasswordRequestSchema Field Constraints
|
|
740
|
-
|
|
741
|
-
The built-in `ChangePasswordRequestSchema` uses scheme-based credential naming:
|
|
742
|
-
|
|
743
|
-
| Field | Type | Constraints |
|
|
744
|
-
|-------|------|-------------|
|
|
745
|
-
| `scheme` | `string` | Required |
|
|
746
|
-
| `oldCredential` | `string` | Non-empty, min 8 chars (required) |
|
|
747
|
-
| `newCredential` | `string` | Non-empty, min 8 chars (required) |
|
|
748
|
-
| `userId` | `string \| number` | Required |
|
|
749
|
-
|
|
750
|
-
#### IAuthService -- Full Interface
|
|
751
144
|
```typescript
|
|
752
|
-
|
|
753
|
-
E extends Env = Env,
|
|
754
|
-
SIRQ extends TSignInRequest = TSignInRequest,
|
|
755
|
-
SIRS = AnyObject,
|
|
756
|
-
SURQ extends TSignUpRequest = TSignUpRequest,
|
|
757
|
-
SURS = AnyObject,
|
|
758
|
-
CPRQ extends TChangePasswordRequest = TChangePasswordRequest,
|
|
759
|
-
CPRS = AnyObject,
|
|
760
|
-
UIRQ = AnyObject,
|
|
761
|
-
UIRS = AnyObject,
|
|
762
|
-
RTRS = AnyObject,
|
|
763
|
-
> {
|
|
764
|
-
signIn(context: TContext<E>, opts: SIRQ): Promise<SIRS>;
|
|
765
|
-
signUp(context: TContext<E>, opts: SURQ): Promise<SURS>;
|
|
766
|
-
changePassword(context: TContext<E>, opts: CPRQ): Promise<CPRS>;
|
|
767
|
-
getUserInformation?(context: TContext<E>, opts: UIRQ): Promise<UIRS>;
|
|
768
|
-
refreshToken?(context: TContext<E>): Promise<RTRS>;
|
|
769
|
-
}
|
|
770
|
-
```
|
|
771
|
-
|
|
772
|
-
> [!NOTE]
|
|
773
|
-
> `IAuthService` is generic on the Hono `Env` type as well as all request/response types. The `getUserInformation` and `refreshToken` methods are both optional -- the auth controller factory returns a 501 (`core.not_supported`) when an endpoint that depends on one is called but the method is not implemented.
|
|
145
|
+
import { extraUserColumns } from '@venizia/ignis';
|
|
774
146
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
provider?: string;
|
|
782
|
-
email?: string;
|
|
783
|
-
name?: string;
|
|
784
|
-
[extra: string | symbol]: any;
|
|
785
|
-
}
|
|
786
|
-
```
|
|
787
|
-
|
|
788
|
-
## Binding Keys
|
|
789
|
-
|
|
790
|
-
| Key | Constant | Type | Required | Default |
|
|
791
|
-
|-----|----------|------|----------|---------|
|
|
792
|
-
| `@app/authenticate/rest-options` | `AuthenticateBindingKeys.REST_OPTIONS` | `TAuthenticationRestOptions` | No | <code v-pre>{ useAuthController: false }</code> |
|
|
793
|
-
| `@app/authenticate/jwt-options` | `AuthenticateBindingKeys.JWT_OPTIONS` | `TJWTTokenServiceOptions` | Conditional | -- |
|
|
794
|
-
| `@app/authenticate/jwks-options` | `AuthenticateBindingKeys.JWKS_OPTIONS` | `IJWKSIssuerOptions \| IJWKSVerifierOptions` | Internal | Bound by the component |
|
|
795
|
-
| `@app/authenticate/basic-options` | `AuthenticateBindingKeys.BASIC_OPTIONS` | `TBasicTokenServiceOptions` | Conditional | -- |
|
|
796
|
-
|
|
797
|
-
> [!IMPORTANT]
|
|
798
|
-
> At least one of `JWT_OPTIONS` or `BASIC_OPTIONS` must be bound. If neither is configured, the component will throw an error during `binding()`.
|
|
799
|
-
|
|
800
|
-
> [!NOTE]
|
|
801
|
-
> `JWKS_OPTIONS` is bound internally by the component when `standard: JOSEStandards.JWKS` is configured. You do not need to bind it manually. The component extracts the JWKS options from the discriminated union and re-binds them to `JWKS_OPTIONS` so that the JWKS services can resolve them via `@inject`.
|
|
802
|
-
|
|
803
|
-
### Context Variables
|
|
804
|
-
|
|
805
|
-
These values are set on the Hono `Context` during authentication and can be accessed via `context.get()`:
|
|
806
|
-
|
|
807
|
-
| Key | Constant | Type | Description |
|
|
808
|
-
|-----|----------|------|-------------|
|
|
809
|
-
| `auth.current.user` | `Authentication.CURRENT_USER` | `IAuthUser` | Authenticated user payload |
|
|
810
|
-
| `audit.user.id` | `Authentication.AUDIT_USER_ID` | `IdType` | Authenticated user's ID |
|
|
811
|
-
| `authentication.skip` | `Authentication.SKIP_AUTHENTICATION` | `boolean` | Dynamically skip auth |
|
|
812
|
-
|
|
813
|
-
### Strategy Constants
|
|
814
|
-
|
|
815
|
-
| Constant | Value | Description |
|
|
816
|
-
|----------|-------|-------------|
|
|
817
|
-
| `Authentication.STRATEGY_JWT` | `'jwt'` | JWT strategy name |
|
|
818
|
-
| `Authentication.STRATEGY_BASIC` | `'basic'` | Basic strategy name |
|
|
819
|
-
| `Authentication.TYPE_BEARER` | `'Bearer'` | Bearer token type |
|
|
820
|
-
| `Authentication.TYPE_BASIC` | `'Basic'` | Basic token type |
|
|
821
|
-
|
|
822
|
-
### AuthenticateStrategy Class
|
|
823
|
-
|
|
824
|
-
Utility class for validating strategy names:
|
|
825
|
-
|
|
826
|
-
```typescript
|
|
827
|
-
class AuthenticateStrategy {
|
|
828
|
-
static readonly BASIC = 'basic';
|
|
829
|
-
static readonly JWT = 'jwt';
|
|
830
|
-
static readonly SCHEME_SET: Set<string>;
|
|
831
|
-
static isValid(input: string): boolean;
|
|
832
|
-
}
|
|
833
|
-
type TAuthStrategy = TConstValue<typeof AuthenticateStrategy>;
|
|
834
|
-
```
|
|
835
|
-
|
|
836
|
-
| Member | Type | Description |
|
|
837
|
-
|--------|------|-------------|
|
|
838
|
-
| `BASIC` | `string` | Constant for basic strategy name |
|
|
839
|
-
| `JWT` | `string` | Constant for JWT strategy name |
|
|
840
|
-
| `SCHEME_SET` | `Set<string>` | Set containing all valid strategy names |
|
|
841
|
-
| `isValid(input)` | `(input: string) => boolean` | Returns `true` if the input is a recognized strategy name |
|
|
842
|
-
|
|
843
|
-
### AuthenticationModes Class
|
|
844
|
-
|
|
845
|
-
Utility class for validating authentication modes:
|
|
846
|
-
|
|
847
|
-
```typescript
|
|
848
|
-
class AuthenticationModes {
|
|
849
|
-
static readonly ANY = 'any';
|
|
850
|
-
static readonly ALL = 'all';
|
|
851
|
-
}
|
|
852
|
-
type TAuthMode = TConstValue<typeof AuthenticationModes>;
|
|
147
|
+
export const users = pgTable('users', {
|
|
148
|
+
...withSerialId(),
|
|
149
|
+
...withTimestamps(),
|
|
150
|
+
...extraUserColumns(),
|
|
151
|
+
username: text('username').unique().notNull(),
|
|
152
|
+
});
|
|
853
153
|
```
|
|
854
154
|
|
|
855
|
-
|
|
856
|
-
|--------|------|-------------|
|
|
857
|
-
| `ANY` | `string` | First successful strategy wins (fallback) |
|
|
858
|
-
| `ALL` | `string` | All strategies must pass (MFA) |
|
|
859
|
-
|
|
860
|
-
## See Also
|
|
861
|
-
|
|
862
|
-
- [Usage & Examples](./usage) -- Securing routes, auth flows, and API endpoints
|
|
863
|
-
- [API Reference](./api) -- Architecture, service internals, and strategy registry
|
|
864
|
-
- [Error Reference](./errors) -- Error messages and troubleshooting
|
|
865
|
-
|
|
866
|
-
- **Guides:**
|
|
867
|
-
- [Components Overview](/guides/core-concepts/components) -- Component system basics
|
|
868
|
-
- [REST Controllers](/guides/core-concepts/rest-controllers) | [gRPC Controllers](/guides/core-concepts/grpc-controllers) -- Protecting routes with auth
|
|
869
|
-
|
|
870
|
-
- **Components:**
|
|
871
|
-
- [All Components](../index) -- Built-in components list
|
|
872
|
-
|
|
873
|
-
- **Helpers:**
|
|
874
|
-
- [Crypto Helper](/extensions/helpers/crypto/) -- Password hashing utilities
|
|
155
|
+
## See also
|
|
875
156
|
|
|
876
|
-
-
|
|
877
|
-
|
|
157
|
+
- [Usage & Examples](./usage) - securing routes, auth flows, JWKS microservice patterns, entity column helpers
|
|
158
|
+
- [API Reference](./api) - architecture, service class hierarchy, strategy registry, controller factory
|
|
159
|
+
- [Error Reference](./errors) - every error message and how to fix it
|
|
160
|
+
- [Components Overview](/guides/core-concepts/components) - component system basics
|
|
161
|
+
- [REST Controllers](/guides/core-concepts/rest-controllers) - protecting routes with `authenticate`
|
|
162
|
+
- [Crypto Helper](/extensions/helpers/crypto/) - password hashing utilities for `verifyCredentials`
|
|
878
163
|
|
|
879
|
-
|
|
880
|
-
- [Security Guidelines](/best-practices/security-guidelines) -- Authentication best practices
|
|
164
|
+
**Files:**
|
|
881
165
|
|
|
882
|
-
-
|
|
883
|
-
|
|
166
|
+
- [`packages/core/src/components/auth/authenticate/component.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/component.ts) - `AuthenticateComponent`
|
|
167
|
+
- [`packages/core/src/components/auth/authenticate/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/common/types.ts) - all option interfaces
|
|
168
|
+
- [`packages/core/src/components/auth/authenticate/common/keys.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/common/keys.ts) - `AuthenticateBindingKeys`
|
|
169
|
+
- [`packages/core/src/components/auth/authenticate/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/common/constants.ts) - `Authentication`, `JOSEStandards`, `JWKSModes`, `JWKSKeyDrivers`, `JWKSKeyFormats`
|
|
170
|
+
- [`packages/core/src/components/auth/models/entities`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/models/entities) - `extraUserColumns`, `extraRoleColumns`, `extraPermissionColumns`, `extraPolicyDefinitionColumns`
|