@authrim/server 0.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 +610 -0
- package/dist/adapters/express.cjs +3 -0
- package/dist/adapters/express.cjs.map +1 -0
- package/dist/adapters/express.d.cts +75 -0
- package/dist/adapters/express.d.ts +75 -0
- package/dist/adapters/express.js +3 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/fastify.cjs +3 -0
- package/dist/adapters/fastify.cjs.map +1 -0
- package/dist/adapters/fastify.d.cts +101 -0
- package/dist/adapters/fastify.d.ts +101 -0
- package/dist/adapters/fastify.js +3 -0
- package/dist/adapters/fastify.js.map +1 -0
- package/dist/adapters/hono.cjs +2 -0
- package/dist/adapters/hono.cjs.map +1 -0
- package/dist/adapters/hono.d.cts +85 -0
- package/dist/adapters/hono.d.ts +85 -0
- package/dist/adapters/hono.js +2 -0
- package/dist/adapters/hono.js.map +1 -0
- package/dist/adapters/koa.cjs +3 -0
- package/dist/adapters/koa.cjs.map +1 -0
- package/dist/adapters/koa.d.cts +75 -0
- package/dist/adapters/koa.d.ts +75 -0
- package/dist/adapters/koa.js +3 -0
- package/dist/adapters/koa.js.map +1 -0
- package/dist/adapters/nestjs.cjs +3 -0
- package/dist/adapters/nestjs.cjs.map +1 -0
- package/dist/adapters/nestjs.d.cts +126 -0
- package/dist/adapters/nestjs.d.ts +126 -0
- package/dist/adapters/nestjs.js +3 -0
- package/dist/adapters/nestjs.js.map +1 -0
- package/dist/chunk-7POGA5LZ.cjs +3 -0
- package/dist/chunk-7POGA5LZ.cjs.map +1 -0
- package/dist/chunk-N3ONRO35.js +2 -0
- package/dist/chunk-N3ONRO35.js.map +1 -0
- package/dist/chunk-O2ALCNXB.cjs +2 -0
- package/dist/chunk-O2ALCNXB.cjs.map +1 -0
- package/dist/chunk-OS567YCE.js +3 -0
- package/dist/chunk-OS567YCE.js.map +1 -0
- package/dist/chunk-TPROSFE7.cjs +2 -0
- package/dist/chunk-TPROSFE7.cjs.map +1 -0
- package/dist/chunk-XOFM2JHF.js +2 -0
- package/dist/chunk-XOFM2JHF.js.map +1 -0
- package/dist/config-I0GIVJA_.d.cts +364 -0
- package/dist/config-I0GIVJA_.d.ts +364 -0
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +791 -0
- package/dist/index.d.ts +791 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/index.cjs +2 -0
- package/dist/providers/index.cjs.map +1 -0
- package/dist/providers/index.d.cts +79 -0
- package/dist/providers/index.d.ts +79 -0
- package/dist/providers/index.js +2 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/types-CzpMdWFR.d.cts +435 -0
- package/dist/types-D7gjcvs9.d.ts +435 -0
- package/package.json +119 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
import { A as AuthrimServer, a as AuthenticateRequest, b as AuthenticateResult, J as JwtHeader, P as ParsedJwt, S as StandardClaims, C as ClaimsValidationOptions, c as ClaimsValidationResult, T as TokenValidationOptions, d as TokenValidationResult, I as IntrospectionRequest, e as IntrospectionResponse, R as RevocationRequest, D as DPoPValidationOptions, f as DPoPValidationResult } from './types-CzpMdWFR.cjs';
|
|
2
|
+
export { g as AccessTokenClaims, h as AuthenticateError, i as AuthenticateSuccess, j as ConfirmationClaim, k as DPoPProofHeader, l as DPoPProofPayload, m as IdTokenClaims, M as MiddlewareOptions, V as ValidatedToken, n as createAuthrimServer } from './types-CzpMdWFR.cjs';
|
|
3
|
+
import { C as CachedJwk, H as HttpProvider, a as CryptoProvider, b as ClockProvider, c as CacheProvider } from './config-I0GIVJA_.cjs';
|
|
4
|
+
export { A as AuthrimServerConfig, E as EcPublicJwk, J as JwkKeyType, d as JwkKeyUse, e as JwkSet, f as JwkSigningAlgorithm, M as MemoryCacheOptions, O as OkpPublicJwk, P as PublicJwk, R as ResolvedAuthrimServerConfig, g as RsaPublicJwk } from './config-I0GIVJA_.cjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Authrim Server SDK Error Types
|
|
8
|
+
*
|
|
9
|
+
* These error codes are specific to server-side token validation and DPoP operations.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Server SDK error codes
|
|
13
|
+
*/
|
|
14
|
+
type AuthrimServerErrorCode = 'invalid_token' | 'token_expired' | 'token_not_yet_valid' | 'token_malformed' | 'signature_invalid' | 'algorithm_mismatch' | 'invalid_issuer' | 'invalid_audience' | 'jwks_fetch_error' | 'jwks_key_not_found' | 'jwks_key_ambiguous' | 'jwks_key_import_error' | 'dpop_proof_missing' | 'dpop_proof_invalid' | 'dpop_proof_signature_invalid' | 'dpop_method_mismatch' | 'dpop_uri_mismatch' | 'dpop_ath_mismatch' | 'dpop_binding_mismatch' | 'dpop_iat_expired' | 'dpop_nonce_required' | 'introspection_error' | 'revocation_error' | 'configuration_error' | 'provider_error' | 'network_error' | 'timeout_error';
|
|
15
|
+
/**
|
|
16
|
+
* Error metadata for recovery information
|
|
17
|
+
*/
|
|
18
|
+
interface AuthrimServerErrorMeta {
|
|
19
|
+
/** HTTP status code to return */
|
|
20
|
+
httpStatus: number;
|
|
21
|
+
/** Whether this is a transient error */
|
|
22
|
+
transient: boolean;
|
|
23
|
+
/** Whether automatic retry is possible */
|
|
24
|
+
retryable: boolean;
|
|
25
|
+
/** WWW-Authenticate error attribute */
|
|
26
|
+
wwwAuthenticateError?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Options for creating an AuthrimServerError
|
|
30
|
+
*/
|
|
31
|
+
interface AuthrimServerErrorOptions {
|
|
32
|
+
details?: Record<string, unknown>;
|
|
33
|
+
cause?: Error;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Authrim Server SDK Error class
|
|
37
|
+
*/
|
|
38
|
+
declare class AuthrimServerError extends Error {
|
|
39
|
+
/** Error code for programmatic handling */
|
|
40
|
+
readonly code: AuthrimServerErrorCode;
|
|
41
|
+
/** Additional error details */
|
|
42
|
+
readonly details?: Record<string, unknown>;
|
|
43
|
+
/** Underlying cause */
|
|
44
|
+
readonly cause?: Error;
|
|
45
|
+
constructor(code: AuthrimServerErrorCode, message: string, options?: AuthrimServerErrorOptions);
|
|
46
|
+
/**
|
|
47
|
+
* Get error metadata for HTTP response
|
|
48
|
+
*/
|
|
49
|
+
get meta(): AuthrimServerErrorMeta;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get error metadata for a given error code
|
|
53
|
+
*/
|
|
54
|
+
declare function getServerErrorMeta(code: AuthrimServerErrorCode): AuthrimServerErrorMeta;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Session Management Type Definitions
|
|
58
|
+
*
|
|
59
|
+
* OpenID Connect Back-Channel Logout 1.0
|
|
60
|
+
* https://openid.net/specs/openid-connect-backchannel-1_0.html
|
|
61
|
+
*/
|
|
62
|
+
/**
|
|
63
|
+
* Logout token claims (for back-channel logout)
|
|
64
|
+
*
|
|
65
|
+
* Per OIDC Back-Channel Logout 1.0 Section 2.4, the logout token MUST contain:
|
|
66
|
+
* - iss: Issuer Identifier
|
|
67
|
+
* - aud: Audience (one or more)
|
|
68
|
+
* - iat: Issued At
|
|
69
|
+
* - exp: Expiration Time
|
|
70
|
+
* - jti: JWT ID (unique identifier)
|
|
71
|
+
* - events: MUST contain "http://schemas.openid.net/event/backchannel-logout" key
|
|
72
|
+
* - sub and/or sid: At least one MUST be present
|
|
73
|
+
*
|
|
74
|
+
* The logout token MUST NOT contain:
|
|
75
|
+
* - nonce claim (to prevent confusion with ID tokens)
|
|
76
|
+
*/
|
|
77
|
+
interface LogoutTokenClaims {
|
|
78
|
+
/** Issuer */
|
|
79
|
+
iss: string;
|
|
80
|
+
/** Subject (optional, but either sub or sid must be present) */
|
|
81
|
+
sub?: string;
|
|
82
|
+
/** Audience (one or more client_ids) */
|
|
83
|
+
aud: string | string[];
|
|
84
|
+
/** Issued at time (Unix timestamp) */
|
|
85
|
+
iat: number;
|
|
86
|
+
/** Expiration time (Unix timestamp) - REQUIRED per Section 2.4 */
|
|
87
|
+
exp: number;
|
|
88
|
+
/** JWT ID (unique identifier for replay protection) */
|
|
89
|
+
jti: string;
|
|
90
|
+
/**
|
|
91
|
+
* Events claim
|
|
92
|
+
* MUST contain the back-channel logout event with an empty object value
|
|
93
|
+
*/
|
|
94
|
+
events: {
|
|
95
|
+
'http://schemas.openid.net/event/backchannel-logout': Record<string, never>;
|
|
96
|
+
};
|
|
97
|
+
/** Session ID (optional, but either sub or sid must be present) */
|
|
98
|
+
sid?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Core Authentication Function (Framework-Agnostic)
|
|
103
|
+
*
|
|
104
|
+
* This function is the ONLY authentication logic.
|
|
105
|
+
* Framework adapters are thin wrappers that:
|
|
106
|
+
* 1. Extract headers/method/url from framework-specific request
|
|
107
|
+
* 2. Call authenticateRequest()
|
|
108
|
+
* 3. Attach result to framework-specific context
|
|
109
|
+
*
|
|
110
|
+
* Header normalization:
|
|
111
|
+
* - All header names are normalized to lowercase internally
|
|
112
|
+
* - If multiple Authorization headers exist, only the first is used
|
|
113
|
+
* - DPoP header lookup is case-insensitive
|
|
114
|
+
*/
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Authenticate a request
|
|
118
|
+
*
|
|
119
|
+
* @param server - AuthrimServer instance
|
|
120
|
+
* @param request - Framework-agnostic request
|
|
121
|
+
* @returns Authentication result
|
|
122
|
+
*/
|
|
123
|
+
declare function authenticateRequest(server: AuthrimServer, request: AuthenticateRequest): Promise<AuthenticateResult>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* JWKS Key Selection Algorithm
|
|
127
|
+
*
|
|
128
|
+
* This algorithm MUST be implemented identically across all language SDKs.
|
|
129
|
+
*
|
|
130
|
+
* 1. If token header contains `kid`:
|
|
131
|
+
* a. Find key where key.kid === header.kid AND key.use === 'sig' (or undefined)
|
|
132
|
+
* b. If not found AND cache is stale, refresh JWKS and retry ONCE
|
|
133
|
+
* c. If still not found, return error: jwks_key_not_found
|
|
134
|
+
*
|
|
135
|
+
* 2. If no `kid` in token header:
|
|
136
|
+
* a. Filter keys by: key.alg === header.alg AND key.use === 'sig' (or undefined)
|
|
137
|
+
* b. If exactly ONE key matches, use it
|
|
138
|
+
* c. If ZERO or MORE THAN ONE keys match, return error: jwks_key_ambiguous
|
|
139
|
+
*
|
|
140
|
+
* 3. Algorithm mismatch:
|
|
141
|
+
* - If key.alg is present AND key.alg !== header.alg, skip the key
|
|
142
|
+
*
|
|
143
|
+
* 4. Cache behavior:
|
|
144
|
+
* - Default TTL: 1 hour
|
|
145
|
+
* - Stale-while-revalidate: NOT implemented (fail-closed)
|
|
146
|
+
* - Thundering herd: Use single-flight pattern (one concurrent fetch per issuer)
|
|
147
|
+
*
|
|
148
|
+
* 5. Cache-Control header:
|
|
149
|
+
* - SHOULD be respected if present
|
|
150
|
+
* - max-age takes precedence over default TTL
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Key selection result
|
|
155
|
+
*/
|
|
156
|
+
interface KeySelectionResult {
|
|
157
|
+
key: CachedJwk | null;
|
|
158
|
+
error: AuthrimServerError | null;
|
|
159
|
+
needsRefresh: boolean;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Select a key by kid (Key ID)
|
|
163
|
+
*
|
|
164
|
+
* @param keys - Available keys
|
|
165
|
+
* @param kid - Key ID from token header
|
|
166
|
+
* @param alg - Algorithm from token header
|
|
167
|
+
* @returns KeySelectionResult
|
|
168
|
+
*/
|
|
169
|
+
declare function selectKeyByKid(keys: CachedJwk[], kid: string, alg: string): KeySelectionResult;
|
|
170
|
+
/**
|
|
171
|
+
* Select a key by algorithm (when no kid is present)
|
|
172
|
+
*
|
|
173
|
+
* @param keys - Available keys
|
|
174
|
+
* @param alg - Algorithm from token header
|
|
175
|
+
* @returns KeySelectionResult
|
|
176
|
+
*/
|
|
177
|
+
declare function selectKeyByAlgorithm(keys: CachedJwk[], alg: string): KeySelectionResult;
|
|
178
|
+
/**
|
|
179
|
+
* Select a key from JWKS based on token header
|
|
180
|
+
*
|
|
181
|
+
* @param keys - Available keys
|
|
182
|
+
* @param header - JWT header
|
|
183
|
+
* @returns KeySelectionResult
|
|
184
|
+
*/
|
|
185
|
+
declare function selectKey(keys: CachedJwk[], header: JwtHeader): KeySelectionResult;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* JWKS Manager
|
|
189
|
+
*
|
|
190
|
+
* Handles JWKS fetching, caching, and key rotation.
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* JWKS Manager configuration
|
|
195
|
+
*/
|
|
196
|
+
interface JwksManagerConfig {
|
|
197
|
+
/** JWKS endpoint URL */
|
|
198
|
+
jwksUri: string;
|
|
199
|
+
/** Cache TTL in milliseconds */
|
|
200
|
+
cacheTtlMs: number;
|
|
201
|
+
/** HTTP provider */
|
|
202
|
+
http: HttpProvider;
|
|
203
|
+
/** Crypto provider */
|
|
204
|
+
crypto: CryptoProvider;
|
|
205
|
+
/** Clock provider */
|
|
206
|
+
clock: ClockProvider;
|
|
207
|
+
/** Cache provider */
|
|
208
|
+
cache: CacheProvider<CachedJwk[]>;
|
|
209
|
+
/**
|
|
210
|
+
* Optional callback for key import warnings
|
|
211
|
+
* Called when a key fails to import (may be encryption key, unsupported algorithm, etc.)
|
|
212
|
+
*/
|
|
213
|
+
onKeyImportWarning?: (warning: KeyImportWarning) => void;
|
|
214
|
+
/**
|
|
215
|
+
* Allow redirects to different hosts (default: false)
|
|
216
|
+
*
|
|
217
|
+
* SECURITY: By default, JWKS fetches will fail if redirected to a different host
|
|
218
|
+
* to prevent SSRF attacks. Only enable this if you explicitly trust the issuer
|
|
219
|
+
* to redirect to arbitrary hosts.
|
|
220
|
+
*/
|
|
221
|
+
allowCrossOriginRedirect?: boolean;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Key import warning information
|
|
225
|
+
*/
|
|
226
|
+
interface KeyImportWarning {
|
|
227
|
+
/** Key ID (if present) */
|
|
228
|
+
kid?: string;
|
|
229
|
+
/** Key type */
|
|
230
|
+
kty: string;
|
|
231
|
+
/** Algorithm (if present) */
|
|
232
|
+
alg?: string;
|
|
233
|
+
/** Reason for failure */
|
|
234
|
+
reason: 'unsupported_algorithm' | 'import_failed' | 'unknown_key_type';
|
|
235
|
+
/** Error message */
|
|
236
|
+
message: string;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* JWKS Manager
|
|
240
|
+
*
|
|
241
|
+
* Features:
|
|
242
|
+
* - Automatic JWKS fetching and caching
|
|
243
|
+
* - Key rotation handling (retry on kid not found)
|
|
244
|
+
* - Single-flight pattern (one concurrent fetch per issuer)
|
|
245
|
+
* - Cache-Control header support
|
|
246
|
+
*/
|
|
247
|
+
declare class JwksManager {
|
|
248
|
+
private readonly config;
|
|
249
|
+
private readonly cacheKey;
|
|
250
|
+
private inFlight;
|
|
251
|
+
constructor(config: JwksManagerConfig);
|
|
252
|
+
/**
|
|
253
|
+
* Get a key for verifying a JWT
|
|
254
|
+
*
|
|
255
|
+
* Implements the key selection algorithm with automatic refresh on key not found.
|
|
256
|
+
*
|
|
257
|
+
* @param header - JWT header
|
|
258
|
+
* @returns Selected key or error
|
|
259
|
+
*/
|
|
260
|
+
getKey(header: JwtHeader): Promise<KeySelectionResult>;
|
|
261
|
+
/**
|
|
262
|
+
* Get cached keys or fetch if not available
|
|
263
|
+
*/
|
|
264
|
+
private getKeys;
|
|
265
|
+
/**
|
|
266
|
+
* Fetch JWKS and cache keys
|
|
267
|
+
*
|
|
268
|
+
* Uses single-flight pattern to prevent thundering herd.
|
|
269
|
+
*/
|
|
270
|
+
private fetchAndCacheKeys;
|
|
271
|
+
/**
|
|
272
|
+
* Actually fetch and cache keys
|
|
273
|
+
*/
|
|
274
|
+
private doFetchAndCache;
|
|
275
|
+
/**
|
|
276
|
+
* Determine the algorithm for a JWK
|
|
277
|
+
*/
|
|
278
|
+
private determineAlgorithm;
|
|
279
|
+
/**
|
|
280
|
+
* Maximum cache TTL (24 hours) to prevent overflow and unreasonable cache times
|
|
281
|
+
*/
|
|
282
|
+
private static readonly MAX_CACHE_TTL_MS;
|
|
283
|
+
/**
|
|
284
|
+
* Parse Cache-Control header for max-age
|
|
285
|
+
*
|
|
286
|
+
* @param header - Cache-Control header value
|
|
287
|
+
* @returns TTL in milliseconds
|
|
288
|
+
*/
|
|
289
|
+
private parseCacheControl;
|
|
290
|
+
/**
|
|
291
|
+
* Invalidate cached keys
|
|
292
|
+
*/
|
|
293
|
+
invalidate(): void;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* JWT Signature Verification
|
|
298
|
+
*
|
|
299
|
+
* RFC 7519 - JSON Web Token (JWT)
|
|
300
|
+
*
|
|
301
|
+
* This module handles ONLY cryptographic signature verification.
|
|
302
|
+
* Claims validation is handled separately in validate-claims.ts.
|
|
303
|
+
*/
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Parse a JWT without verification
|
|
307
|
+
*
|
|
308
|
+
* @param token - JWT string
|
|
309
|
+
* @returns Parsed JWT structure
|
|
310
|
+
* @throws Error if JWT format is invalid
|
|
311
|
+
* @throws InsecureAlgorithmError if JWT uses alg: none
|
|
312
|
+
*/
|
|
313
|
+
declare function parseJwt<T = Record<string, unknown>>(token: string): ParsedJwt<T>;
|
|
314
|
+
/**
|
|
315
|
+
* Verify JWT signature
|
|
316
|
+
*
|
|
317
|
+
* @param token - JWT string
|
|
318
|
+
* @param key - CryptoKey for verification
|
|
319
|
+
* @param crypto - Crypto provider
|
|
320
|
+
* @returns Parsed JWT if signature is valid, null otherwise
|
|
321
|
+
*/
|
|
322
|
+
declare function verifyJwtSignature<T = Record<string, unknown>>(token: string, key: CryptoKey, crypto: CryptoProvider): Promise<ParsedJwt<T> | null>;
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* JWT Claims Validation
|
|
326
|
+
*
|
|
327
|
+
* RFC 7519 - JSON Web Token (JWT)
|
|
328
|
+
*
|
|
329
|
+
* This module handles claims validation (iss, aud, exp, nbf, iat).
|
|
330
|
+
* Signature verification is handled separately in verify-jwt.ts.
|
|
331
|
+
*/
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Validate JWT claims
|
|
335
|
+
*
|
|
336
|
+
* Validates:
|
|
337
|
+
* - iss (issuer) - must match expected issuer
|
|
338
|
+
* - aud (audience) - must include expected audience
|
|
339
|
+
* - exp (expiration) - must not be expired (with tolerance)
|
|
340
|
+
* - nbf (not before) - must be past nbf (with tolerance)
|
|
341
|
+
* - iat (issued at) - must not be in the future (with tolerance)
|
|
342
|
+
*
|
|
343
|
+
* For ID Token validation (OIDC Core 1.0 Section 3.1.3.7), set:
|
|
344
|
+
* - requireExp: true
|
|
345
|
+
* - requireIat: true
|
|
346
|
+
*
|
|
347
|
+
* @param payload - JWT payload containing claims
|
|
348
|
+
* @param options - Validation options
|
|
349
|
+
* @returns Validation result
|
|
350
|
+
*/
|
|
351
|
+
declare function validateClaims(payload: StandardClaims, options: ClaimsValidationOptions): ClaimsValidationResult;
|
|
352
|
+
/**
|
|
353
|
+
* Calculate time remaining until token expiration
|
|
354
|
+
*
|
|
355
|
+
* @param exp - Expiration timestamp (Unix seconds)
|
|
356
|
+
* @param now - Current timestamp (Unix seconds)
|
|
357
|
+
* @returns Seconds until expiration, or undefined if no exp claim
|
|
358
|
+
*/
|
|
359
|
+
declare function getExpiresIn(exp: number | undefined, now: number): number | undefined;
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Token Validator
|
|
363
|
+
*
|
|
364
|
+
* Orchestrates JWT signature verification and claims validation.
|
|
365
|
+
*/
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Token Validator configuration
|
|
369
|
+
*/
|
|
370
|
+
interface TokenValidatorConfig {
|
|
371
|
+
/** JWKS Manager */
|
|
372
|
+
jwksManager: JwksManager;
|
|
373
|
+
/** Crypto provider */
|
|
374
|
+
crypto: CryptoProvider;
|
|
375
|
+
/** Clock provider */
|
|
376
|
+
clock: ClockProvider;
|
|
377
|
+
/** Validation options */
|
|
378
|
+
options: TokenValidationOptions;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Token Validator
|
|
382
|
+
*
|
|
383
|
+
* Combines JWKS key retrieval, signature verification, and claims validation.
|
|
384
|
+
*/
|
|
385
|
+
declare class TokenValidator {
|
|
386
|
+
private readonly config;
|
|
387
|
+
constructor(config: TokenValidatorConfig);
|
|
388
|
+
/**
|
|
389
|
+
* Validate a JWT access token
|
|
390
|
+
*
|
|
391
|
+
* Steps:
|
|
392
|
+
* 1. Parse JWT structure
|
|
393
|
+
* 2. Get signing key from JWKS
|
|
394
|
+
* 3. Verify signature
|
|
395
|
+
* 4. Validate claims (iss, aud, exp, nbf, iat)
|
|
396
|
+
*
|
|
397
|
+
* @param token - JWT string
|
|
398
|
+
* @returns Validation result
|
|
399
|
+
*/
|
|
400
|
+
validate(token: string): Promise<TokenValidationResult>;
|
|
401
|
+
/**
|
|
402
|
+
* Validate required scopes
|
|
403
|
+
*/
|
|
404
|
+
private validateScopes;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Token Introspection (RFC 7662)
|
|
409
|
+
*
|
|
410
|
+
* OAuth 2.0 Token Introspection
|
|
411
|
+
*/
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Introspection client configuration
|
|
415
|
+
*/
|
|
416
|
+
interface IntrospectionClientConfig {
|
|
417
|
+
/** Introspection endpoint URL */
|
|
418
|
+
endpoint: string;
|
|
419
|
+
/** Client ID */
|
|
420
|
+
clientId: string;
|
|
421
|
+
/** Client secret */
|
|
422
|
+
clientSecret: string;
|
|
423
|
+
/** HTTP provider */
|
|
424
|
+
http: HttpProvider;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Token Introspection Client
|
|
428
|
+
*
|
|
429
|
+
* Calls the authorization server's introspection endpoint to check token validity.
|
|
430
|
+
*/
|
|
431
|
+
declare class IntrospectionClient {
|
|
432
|
+
private readonly config;
|
|
433
|
+
constructor(config: IntrospectionClientConfig);
|
|
434
|
+
/**
|
|
435
|
+
* Introspect a token
|
|
436
|
+
*
|
|
437
|
+
* @param request - Introspection request
|
|
438
|
+
* @returns Introspection response
|
|
439
|
+
*/
|
|
440
|
+
introspect(request: IntrospectionRequest): Promise<IntrospectionResponse>;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Token Revocation (RFC 7009)
|
|
445
|
+
*
|
|
446
|
+
* OAuth 2.0 Token Revocation
|
|
447
|
+
*/
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Revocation client configuration
|
|
451
|
+
*/
|
|
452
|
+
interface RevocationClientConfig {
|
|
453
|
+
/** Revocation endpoint URL */
|
|
454
|
+
endpoint: string;
|
|
455
|
+
/** Client ID */
|
|
456
|
+
clientId: string;
|
|
457
|
+
/** Client secret */
|
|
458
|
+
clientSecret: string;
|
|
459
|
+
/** HTTP provider */
|
|
460
|
+
http: HttpProvider;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Token Revocation Client
|
|
464
|
+
*
|
|
465
|
+
* Calls the authorization server's revocation endpoint to invalidate tokens.
|
|
466
|
+
*/
|
|
467
|
+
declare class RevocationClient {
|
|
468
|
+
private readonly config;
|
|
469
|
+
constructor(config: RevocationClientConfig);
|
|
470
|
+
/**
|
|
471
|
+
* Revoke a token
|
|
472
|
+
*
|
|
473
|
+
* Note: Per RFC 7009, a successful response (200) does not guarantee
|
|
474
|
+
* the token was revoked. The server may silently ignore invalid tokens.
|
|
475
|
+
*
|
|
476
|
+
* @param request - Revocation request
|
|
477
|
+
*/
|
|
478
|
+
revoke(request: RevocationRequest): Promise<void>;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* DPoP Proof Validator (RFC 9449)
|
|
483
|
+
*
|
|
484
|
+
* IMPORTANT: This SDK validates DPoP proofs STATELESSLY.
|
|
485
|
+
*
|
|
486
|
+
* What this SDK validates:
|
|
487
|
+
* - Proof signature (embedded JWK in header)
|
|
488
|
+
* - htm (HTTP method) matches request
|
|
489
|
+
* - htu (HTTP URI) matches request
|
|
490
|
+
* - ath (access token hash) if present
|
|
491
|
+
* - cnf.jkt binding (thumbprint matches)
|
|
492
|
+
* - iat is within acceptable window
|
|
493
|
+
*
|
|
494
|
+
* What this SDK does NOT validate:
|
|
495
|
+
* - jti uniqueness (replay detection)
|
|
496
|
+
* → MUST be implemented by the resource server if required
|
|
497
|
+
* → Recommended: Store jti with TTL = proof lifetime + clock tolerance
|
|
498
|
+
*
|
|
499
|
+
* Nonce handling:
|
|
500
|
+
* - If server returns use_dpop_nonce error, SDK returns dpop_nonce_required
|
|
501
|
+
* - Application is responsible for retry with nonce
|
|
502
|
+
*/
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* DPoP Proof Validator
|
|
506
|
+
*/
|
|
507
|
+
declare class DPoPValidator {
|
|
508
|
+
private readonly crypto;
|
|
509
|
+
private readonly clock;
|
|
510
|
+
constructor(crypto: CryptoProvider, clock: ClockProvider);
|
|
511
|
+
/**
|
|
512
|
+
* Validate a DPoP proof
|
|
513
|
+
*
|
|
514
|
+
* @param proof - DPoP proof JWT
|
|
515
|
+
* @param options - Validation options
|
|
516
|
+
* @returns Validation result
|
|
517
|
+
*/
|
|
518
|
+
validate(proof: string, options: DPoPValidationOptions): Promise<DPoPValidationResult>;
|
|
519
|
+
/**
|
|
520
|
+
* Validate DPoP header
|
|
521
|
+
*/
|
|
522
|
+
private validateHeader;
|
|
523
|
+
/**
|
|
524
|
+
* Validate DPoP payload structure
|
|
525
|
+
*/
|
|
526
|
+
private validatePayloadStructure;
|
|
527
|
+
/**
|
|
528
|
+
* Verify DPoP proof signature
|
|
529
|
+
*/
|
|
530
|
+
private verifySignature;
|
|
531
|
+
/**
|
|
532
|
+
* Validate access token hash (ath claim)
|
|
533
|
+
*
|
|
534
|
+
* Per RFC 9449 Section 4.2:
|
|
535
|
+
* When the DPoP proof is used with an access token, the ath claim MUST be present
|
|
536
|
+
* and contain the base64url-encoded SHA-256 hash of the access token.
|
|
537
|
+
*/
|
|
538
|
+
private validateAccessTokenHash;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* JWK Thumbprint (RFC 7638)
|
|
543
|
+
*
|
|
544
|
+
* Computes a hash of a JWK for use as a key identifier.
|
|
545
|
+
*/
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Calculate JWK thumbprint
|
|
549
|
+
*
|
|
550
|
+
* Per RFC 7638, the thumbprint is computed as:
|
|
551
|
+
* 1. Construct a JSON object with only the required members, in lexicographic order
|
|
552
|
+
* 2. Compute SHA-256 hash of the UTF-8 encoding of the JSON
|
|
553
|
+
* 3. Base64url encode the hash
|
|
554
|
+
*
|
|
555
|
+
* @param jwk - JSON Web Key
|
|
556
|
+
* @param crypto - Crypto provider
|
|
557
|
+
* @returns Base64url-encoded thumbprint
|
|
558
|
+
*/
|
|
559
|
+
declare function calculateJwkThumbprint(jwk: JsonWebKey, crypto: CryptoProvider): Promise<string>;
|
|
560
|
+
/**
|
|
561
|
+
* Verify that a JWK thumbprint matches the expected value
|
|
562
|
+
*
|
|
563
|
+
* @param jwk - JSON Web Key to verify
|
|
564
|
+
* @param expectedThumbprint - Expected thumbprint value
|
|
565
|
+
* @param crypto - Crypto provider
|
|
566
|
+
* @returns true if thumbprints match
|
|
567
|
+
*/
|
|
568
|
+
declare function verifyJwkThumbprint(jwk: JsonWebKey, expectedThumbprint: string, crypto: CryptoProvider): Promise<boolean>;
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Base64URL Encoding/Decoding Utilities
|
|
572
|
+
*
|
|
573
|
+
* RFC 4648 Section 5 - Base64 Encoding with URL and Filename Safe Alphabet
|
|
574
|
+
*/
|
|
575
|
+
/**
|
|
576
|
+
* Encode bytes to base64url string
|
|
577
|
+
*
|
|
578
|
+
* @param data - Bytes to encode
|
|
579
|
+
* @returns Base64url-encoded string (no padding)
|
|
580
|
+
*/
|
|
581
|
+
declare function base64UrlEncode(data: Uint8Array): string;
|
|
582
|
+
/**
|
|
583
|
+
* Decode base64url string to bytes
|
|
584
|
+
*
|
|
585
|
+
* @param str - Base64url-encoded string
|
|
586
|
+
* @returns Decoded bytes
|
|
587
|
+
*/
|
|
588
|
+
declare function base64UrlDecode(str: string): Uint8Array;
|
|
589
|
+
/**
|
|
590
|
+
* Encode string to base64url
|
|
591
|
+
*
|
|
592
|
+
* @param str - String to encode (UTF-8)
|
|
593
|
+
* @returns Base64url-encoded string
|
|
594
|
+
*/
|
|
595
|
+
declare function base64UrlEncodeString(str: string): string;
|
|
596
|
+
/**
|
|
597
|
+
* Decode base64url to string
|
|
598
|
+
*
|
|
599
|
+
* @param str - Base64url-encoded string
|
|
600
|
+
* @returns Decoded string (UTF-8)
|
|
601
|
+
*/
|
|
602
|
+
declare function base64UrlDecodeString(str: string): string;
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* HTTP Error Response Utilities
|
|
606
|
+
*
|
|
607
|
+
* Helpers for building OAuth 2.0 / RFC 6750 compliant error responses.
|
|
608
|
+
*/
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* OAuth 2.0 error response body
|
|
612
|
+
*/
|
|
613
|
+
interface ErrorResponseBody {
|
|
614
|
+
error: string;
|
|
615
|
+
error_description?: string;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Build an error response body from an AuthrimServerError
|
|
619
|
+
*
|
|
620
|
+
* @param error - The error to convert
|
|
621
|
+
* @returns Error response body
|
|
622
|
+
*/
|
|
623
|
+
declare function buildErrorResponse(error: AuthrimServerError): ErrorResponseBody;
|
|
624
|
+
/**
|
|
625
|
+
* Build WWW-Authenticate header value (RFC 6750)
|
|
626
|
+
*
|
|
627
|
+
* @param error - The error
|
|
628
|
+
* @param realm - Optional realm value
|
|
629
|
+
* @param scheme - Authentication scheme ('Bearer' or 'DPoP')
|
|
630
|
+
* @returns WWW-Authenticate header value
|
|
631
|
+
*/
|
|
632
|
+
declare function buildWwwAuthenticateHeader(error: AuthrimServerError, realm?: string, scheme?: 'Bearer' | 'DPoP'): string;
|
|
633
|
+
/**
|
|
634
|
+
* Build error headers for HTTP response
|
|
635
|
+
*
|
|
636
|
+
* @param error - The error
|
|
637
|
+
* @param options - Options for header building
|
|
638
|
+
* @returns Headers object
|
|
639
|
+
*/
|
|
640
|
+
declare function buildErrorHeaders(error: AuthrimServerError, options?: {
|
|
641
|
+
realm?: string;
|
|
642
|
+
scheme?: 'Bearer' | 'DPoP';
|
|
643
|
+
dpopNonce?: string;
|
|
644
|
+
}): Record<string, string>;
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Back-Channel Logout Validator
|
|
648
|
+
*
|
|
649
|
+
* Implements OpenID Connect Back-Channel Logout 1.0
|
|
650
|
+
* https://openid.net/specs/openid-connect-backchannel-1_0.html
|
|
651
|
+
*
|
|
652
|
+
* Back-channel logout allows the OP to notify RPs of logout events
|
|
653
|
+
* via direct HTTP calls (server-to-server).
|
|
654
|
+
*
|
|
655
|
+
* NOTE: This validator performs claims validation only.
|
|
656
|
+
* JWT signature verification MUST be performed separately using JWKS.
|
|
657
|
+
*/
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Back-channel logout event URI
|
|
661
|
+
*/
|
|
662
|
+
declare const BACKCHANNEL_LOGOUT_EVENT = "http://schemas.openid.net/event/backchannel-logout";
|
|
663
|
+
/**
|
|
664
|
+
* Back-channel logout validation error codes
|
|
665
|
+
*/
|
|
666
|
+
type BackChannelLogoutErrorCode = 'invalid_format' | 'insecure_algorithm' | 'invalid_issuer' | 'invalid_audience' | 'invalid_events' | 'token_expired' | 'iat_too_old' | 'not_yet_valid' | 'missing_sub_and_sid' | 'sub_mismatch' | 'sid_mismatch' | 'missing_jti' | 'nonce_present';
|
|
667
|
+
/**
|
|
668
|
+
* Options for validating a logout token
|
|
669
|
+
*/
|
|
670
|
+
interface BackChannelLogoutValidationOptions {
|
|
671
|
+
/** Expected issuer */
|
|
672
|
+
issuer: string;
|
|
673
|
+
/** Expected audience (client_id) */
|
|
674
|
+
audience: string;
|
|
675
|
+
/** Maximum age in seconds (default: 60) */
|
|
676
|
+
maxAge?: number;
|
|
677
|
+
/** Clock skew tolerance in seconds (default: 30) */
|
|
678
|
+
clockSkew?: number;
|
|
679
|
+
/** Expected session ID (optional) */
|
|
680
|
+
expectedSid?: string;
|
|
681
|
+
/** Expected subject (optional) */
|
|
682
|
+
expectedSub?: string;
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Result of back-channel logout token validation
|
|
686
|
+
*/
|
|
687
|
+
interface BackChannelLogoutValidationResult {
|
|
688
|
+
/** Whether the token is valid */
|
|
689
|
+
valid: boolean;
|
|
690
|
+
/** Validated claims (if valid) */
|
|
691
|
+
claims?: LogoutTokenClaims;
|
|
692
|
+
/** JWT header (if valid) */
|
|
693
|
+
header?: JwtHeader;
|
|
694
|
+
/** Error message (if invalid) */
|
|
695
|
+
error?: string;
|
|
696
|
+
/** Error code (if invalid) */
|
|
697
|
+
errorCode?: BackChannelLogoutErrorCode;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Back-Channel Logout Validator
|
|
701
|
+
*
|
|
702
|
+
* Validates logout_token JWTs per OIDC Back-Channel Logout 1.0.
|
|
703
|
+
*
|
|
704
|
+
* ## Security Notes
|
|
705
|
+
*
|
|
706
|
+
* 1. **JWT Signature Verification**: This class performs claims validation only.
|
|
707
|
+
* JWT signature verification MUST be performed separately using JWKS
|
|
708
|
+
* before calling validate(). The caller MUST reject tokens with alg: none.
|
|
709
|
+
*
|
|
710
|
+
* 2. **Algorithm Validation**: Per Section 2.6, "alg with the value none MUST NOT
|
|
711
|
+
* be used". This validator explicitly rejects alg: none tokens.
|
|
712
|
+
*
|
|
713
|
+
* 3. **JTI Replay Protection**: This validator checks for jti presence only.
|
|
714
|
+
* The application MUST implement jti replay protection by:
|
|
715
|
+
* - Storing used jti values (at least until token expiry + clock skew)
|
|
716
|
+
* - Rejecting tokens with previously-used jti values
|
|
717
|
+
*
|
|
718
|
+
* 4. **Expiration Validation**: Per Section 2.4, exp is REQUIRED. This validator
|
|
719
|
+
* checks that the token has not expired (with clock skew tolerance).
|
|
720
|
+
*
|
|
721
|
+
* Usage (Node.js server receiving back-channel logout request):
|
|
722
|
+
* ```typescript
|
|
723
|
+
* // 1. Receive logout_token from POST body
|
|
724
|
+
* const logoutToken = req.body.logout_token;
|
|
725
|
+
*
|
|
726
|
+
* // 2. Verify JWT signature using JWKS (MUST reject alg: none)
|
|
727
|
+
* const jwks = await jwksManager.getJwks();
|
|
728
|
+
* const verified = await verifyJwtSignature(logoutToken, key, crypto);
|
|
729
|
+
* if (!verified) {
|
|
730
|
+
* return res.status(400).send('Invalid signature');
|
|
731
|
+
* }
|
|
732
|
+
*
|
|
733
|
+
* // 3. Validate claims (includes alg: none rejection)
|
|
734
|
+
* const validator = new BackChannelLogoutValidator();
|
|
735
|
+
* const result = validator.validate(logoutToken, {
|
|
736
|
+
* issuer: 'https://op.example.com',
|
|
737
|
+
* audience: 'my-client-id'
|
|
738
|
+
* });
|
|
739
|
+
*
|
|
740
|
+
* if (!result.valid) {
|
|
741
|
+
* return res.status(400).send('Invalid logout token');
|
|
742
|
+
* }
|
|
743
|
+
*
|
|
744
|
+
* // 4. Check for jti replay (application responsibility)
|
|
745
|
+
* if (await jtiStore.has(result.claims.jti)) {
|
|
746
|
+
* return res.status(400).send('Token replay detected');
|
|
747
|
+
* }
|
|
748
|
+
* await jtiStore.set(result.claims.jti, true, result.claims.exp + clockSkew);
|
|
749
|
+
*
|
|
750
|
+
* // 5. Perform logout for sub and/or sid
|
|
751
|
+
* await logoutUser(result.claims.sub, result.claims.sid);
|
|
752
|
+
* ```
|
|
753
|
+
*/
|
|
754
|
+
declare class BackChannelLogoutValidator {
|
|
755
|
+
/**
|
|
756
|
+
* Validate a logout_token
|
|
757
|
+
*
|
|
758
|
+
* Per OIDC Back-Channel Logout 1.0 Section 2.4 and 2.6, the logout_token MUST:
|
|
759
|
+
* - Be a valid JWT with alg != "none"
|
|
760
|
+
* - Contain iss, aud, iat, exp, jti claims
|
|
761
|
+
* - Contain events claim with back-channel logout event
|
|
762
|
+
* - Contain either sub or sid (or both)
|
|
763
|
+
* - NOT contain a nonce claim
|
|
764
|
+
* - NOT be expired (exp validation)
|
|
765
|
+
*
|
|
766
|
+
* @param logoutToken - JWT logout token to validate
|
|
767
|
+
* @param options - Validation options
|
|
768
|
+
* @returns Validation result
|
|
769
|
+
*/
|
|
770
|
+
validate(logoutToken: string, options: BackChannelLogoutValidationOptions): BackChannelLogoutValidationResult;
|
|
771
|
+
/**
|
|
772
|
+
* Extract claims from a logout token without validation
|
|
773
|
+
*
|
|
774
|
+
* Useful for inspecting the token before/during validation.
|
|
775
|
+
*
|
|
776
|
+
* @param logoutToken - JWT logout token
|
|
777
|
+
* @returns Claims or null if invalid format
|
|
778
|
+
*/
|
|
779
|
+
extractClaims(logoutToken: string): LogoutTokenClaims | null;
|
|
780
|
+
/**
|
|
781
|
+
* Extract header from a logout token without validation
|
|
782
|
+
*
|
|
783
|
+
* Useful for getting kid to select the correct JWKS key.
|
|
784
|
+
*
|
|
785
|
+
* @param logoutToken - JWT logout token
|
|
786
|
+
* @returns Header or null if invalid format
|
|
787
|
+
*/
|
|
788
|
+
extractHeader(logoutToken: string): JwtHeader | null;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
export { AuthenticateRequest, AuthenticateResult, AuthrimServer, AuthrimServerError, type AuthrimServerErrorCode, type AuthrimServerErrorMeta, type AuthrimServerErrorOptions, BACKCHANNEL_LOGOUT_EVENT, type BackChannelLogoutErrorCode, type BackChannelLogoutValidationOptions, type BackChannelLogoutValidationResult, BackChannelLogoutValidator, ClaimsValidationOptions, ClaimsValidationResult, DPoPValidationOptions, DPoPValidationResult, DPoPValidator, IntrospectionClient, type IntrospectionClientConfig, IntrospectionRequest, IntrospectionResponse, JwksManager, type JwksManagerConfig, JwtHeader, type KeyImportWarning, type LogoutTokenClaims, ParsedJwt, RevocationClient, type RevocationClientConfig, RevocationRequest, StandardClaims, TokenValidationOptions, TokenValidationResult, TokenValidator, type TokenValidatorConfig, authenticateRequest, base64UrlDecode, base64UrlDecodeString, base64UrlEncode, base64UrlEncodeString, buildErrorHeaders, buildErrorResponse, buildWwwAuthenticateHeader, calculateJwkThumbprint, getExpiresIn, getServerErrorMeta, parseJwt, selectKey, selectKeyByAlgorithm, selectKeyByKid, validateClaims, verifyJwkThumbprint, verifyJwtSignature };
|