@web-ts-toolkit/express-oidc-vault 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (6) hide show
  1. package/README.md +95 -23
  2. package/index.d.mts +65 -14
  3. package/index.d.ts +65 -14
  4. package/index.js +993 -460
  5. package/index.mjs +991 -459
  6. package/package.json +8 -4
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # `@web-ts-toolkit/express-oidc-vault`
2
2
 
3
- Cookie-free OIDC session middleware for Express with server-side storage of upstream refresh tokens and logout-capable `id_token`s.
3
+ OIDC session middleware for Express with body or cookie session transport and server-side storage of upstream refresh tokens and logout-capable `id_token`s.
4
4
 
5
5
  ## Status
6
6
 
7
- This package now implements the core cookie-free OIDC flow.
7
+ This package now implements the core OIDC flow with body or cookie session transport.
8
8
 
9
9
  Current implementation includes:
10
10
 
@@ -12,7 +12,7 @@ Current implementation includes:
12
12
  - OIDC login redirect with PKCE, `state`, and `nonce`
13
13
  - callback token exchange, server-side session creation, and one-time local exchange codes
14
14
  - session refresh with session ID rotation
15
- - upstream logout URL generation using stored `id_token`
15
+ - server-driven upstream logout redirect using stored `id_token`
16
16
  - OIDC backchannel logout handling via `logout_token`
17
17
  - public TypeScript interfaces for hooks, sessions, config helpers, and store providers
18
18
 
@@ -55,6 +55,7 @@ This is the default mode.
55
55
  - `exchange` and `refresh` responses include `sessionId`
56
56
  - the frontend stores `sessionId`, typically in `sessionStorage`
57
57
  - the frontend sends `sessionId` back in the JSON body for `refresh` and `logout`
58
+ - `refresh` and `logout` do not read session cookies in this mode
58
59
 
59
60
  ### `sessionTransport: 'cookie'`
60
61
 
@@ -63,6 +64,7 @@ This mode stores `sessionId` in a backend-managed cookie.
63
64
  - `exchange` sets the session cookie and does not need to return `sessionId` in the JSON body
64
65
  - `refresh` reads the cookie, rotates the session, and updates the cookie
65
66
  - `logout` reads the cookie and clears it
67
+ - `refresh` and `logout` require the cookie and reject body-only `sessionId` values
66
68
  - the frontend does not need to keep `sessionId` in `sessionStorage`
67
69
 
68
70
  Backchannel logout is separate from both transport modes because it is a server-to-server request from the IdP and does not rely on browser storage at all.
@@ -75,8 +77,9 @@ Available cookie options:
75
77
  - `cookie.secure`
76
78
  - `cookie.domain`
77
79
  - `cookie.path`
78
- - `cookie.httpOnly`
79
- - `trustedOrigins`: required when cross-site cookie transport is enabled
80
+ - `trustedOrigins`: browser origins allowed to call cookie-authenticated `refresh` and `logout`; required when cross-site cookie transport is enabled
81
+
82
+ `cookie.httpOnly` is always enforced as `true`. Middleware creation rejects `httpOnly: false` and unsafe cookie names, domains, or paths so untrusted values cannot be serialized into `Set-Cookie` headers.
80
83
 
81
84
  Default cookie behavior:
82
85
 
@@ -87,6 +90,8 @@ Default cookie behavior:
87
90
  - `sameSite`: `lax` unless `deploymentMode` is `cross-site`
88
91
  - `secure`: `true` when `sameSite` resolves to `none` or `deploymentMode` is `cross-site`
89
92
 
93
+ Cookie-authenticated `refresh` and `logout` requests use a fail-closed CSRF policy for every `SameSite` mode. The request must include an `Origin` header, or a valid `Referer` header, whose origin matches `backendOrigin` or one of the configured `trustedOrigins`. Requests with no source-origin header are rejected. Backchannel logout is not affected because it is authenticated with the signed OIDC logout token rather than the browser session cookie.
94
+
90
95
  Recommended frontend boot flow:
91
96
 
92
97
  1. Read `sessionId` from `sessionStorage`.
@@ -112,6 +117,16 @@ The core middleware exposes these endpoints under a configurable base path:
112
117
  - `POST /auth/oidc/logout`
113
118
  - `POST /auth/oidc/backchannel-logout`
114
119
 
120
+ The mounted OIDC router parses JSON and `application/x-www-form-urlencoded` request bodies with an explicit default limit of `16kb`. This covers the small route payloads used by `exchange`, `refresh`, `logout`, and form-encoded backchannel logout. If an IdP requires a larger `logout_token`, set `requestBodyLimit` to a string or byte count accepted by Express body parsers.
121
+
122
+ Parser failures return a JSON client error before route handlers or store/provider hooks run. The stable error codes are:
123
+
124
+ - `OIDC_VAULT_REQUEST_BODY_TOO_LARGE`
125
+ - `OIDC_VAULT_REQUEST_BODY_PARAMETER_LIMIT_EXCEEDED`
126
+ - `OIDC_VAULT_UNSUPPORTED_REQUEST_BODY_ENCODING`
127
+ - `OIDC_VAULT_MALFORMED_REQUEST_BODY`
128
+ - `OIDC_VAULT_INVALID_REQUEST_BODY`
129
+
115
130
  ## Quick Start
116
131
 
117
132
  ```ts
@@ -125,6 +140,7 @@ const storeProvider = createMemoryOidcVaultStore();
125
140
  app.use(
126
141
  createOidcVaultMiddleware({
127
142
  basePath: '/auth/oidc',
143
+ backendOrigin: 'https://api.example.com',
128
144
  config: {
129
145
  issuer: process.env.OIDC_ISSUER,
130
146
  clientId: process.env.OIDC_CLIENT_ID,
@@ -138,6 +154,29 @@ app.use(
138
154
 
139
155
  Use the memory store for local development and tests. For production deployments, prefer a Redis or MongoDB store provider.
140
156
 
157
+ `backendOrigin` must be the public backend origin registered with your OIDC provider, such as `https://api.example.com`. Callback `redirect_uri` values are built from this pinned origin and the configured `basePath`, so reverse proxies and untrusted `Host` headers cannot change the provider callback URL. Configure Express `trust proxy` only for other request metadata needs; it is not used to derive the OIDC callback origin.
158
+
159
+ ## Public Options And Defaults
160
+
161
+ | Option | Default | Contract |
162
+ | ------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
163
+ | `basePath` | `/auth/oidc` | Mount path for the OIDC router. Route paths listed in this README are relative to this value. |
164
+ | `backendOrigin` | required | Public backend origin registered with the OIDC provider. Callback redirect URIs are derived from this pinned origin, not request host headers. |
165
+ | `storeProvider` | required | Durable vault store provider. Use Redis or MongoDB for production and multi-instance deployments. |
166
+ | `config` | env-compatible helper input | Provider config. `issuer` is required for discovery and manual modes so ID and logout tokens are issuer-bound. |
167
+ | `frontendRedirectUri` | unset | Default browser return target after backend callback completion. Required if login accepts a custom `returnTo`. |
168
+ | `postLogoutRedirectUri` | unset | Optional provider-registered HTTP(S) URL used in the upstream end-session redirect. |
169
+ | `fetchUserInfo` | implementation default | When enabled, UserInfo claims are fetched and merged only after the `sub` matches the verified ID token subject. |
170
+ | `authorizationTransactionTtlMs` | `600000` | TTL for one-time authorization transactions created during login. |
171
+ | `exchangeCodeTtlMs` | `30000` | TTL for one-time local exchange codes returned to the frontend callback route. |
172
+ | `sessionTransport` | `body` | `body` returns and accepts JSON `sessionId`; `cookie` stores the session pointer in an `HttpOnly` cookie and rejects body-only refresh/logout IDs. |
173
+ | `cookie` | see cookie defaults above | Cookie transport options. `httpOnly` is always enforced as `true`; unsafe names, paths, and domains are rejected. |
174
+ | `trustedOrigins` | `[]` plus `backendOrigin` internally | Browser origins allowed to call cookie-authenticated `refresh` and `logout`. Required for cross-site cookie transport. |
175
+ | `requestBodyLimit` | `16kb` | Express JSON and URL-encoded parser limit for OIDC route bodies. Increase only for known provider backchannel logout token size needs. |
176
+ | `providerRequestTimeoutMs` | `5000` | Timeout for discovery, token, UserInfo, and remote JWKS requests. Must be a positive finite integer. |
177
+ | `hooks` | unset | Pre-commit hooks can veto operations by throwing; post-commit notification hook failures are reported to `onError` without undoing committed state. |
178
+ | `tokenIssuer` | unset | Issues app-local access tokens for `exchange` and `refresh`. This lifetime is separate from upstream token and vault-session lifetimes. |
179
+
141
180
  ## Frontend Integration Example
142
181
 
143
182
  The backend flow is only half of the integration. In default body transport mode, keep `accessToken` in memory, mirror `sessionId` into `sessionStorage`, and deduplicate refresh calls.
@@ -266,21 +305,11 @@ async function logout(): Promise<void> {
266
305
  return;
267
306
  }
268
307
 
269
- const response = await fetch('/auth/oidc/logout', {
308
+ await fetch('/auth/oidc/logout', {
270
309
  method: 'POST',
271
310
  headers: { 'content-type': 'application/json' },
272
311
  body: JSON.stringify({ sessionId }),
273
312
  });
274
-
275
- if (!response.ok) {
276
- return;
277
- }
278
-
279
- const payload = (await response.json()) as { upstreamLogoutUrl?: string };
280
-
281
- if (payload.upstreamLogoutUrl) {
282
- window.location.assign(payload.upstreamLogoutUrl);
283
- }
284
313
  }
285
314
  ```
286
315
 
@@ -379,6 +408,8 @@ The middleware validates the `logout_token` against the provider JWKS and then r
379
408
  - upstream `sid` when present
380
409
  - otherwise `sub`
381
410
 
411
+ The logout token must include `iat`, `exp`, `jti`, the standard backchannel logout event claim, and either `sid` or `sub`. If the protected header includes `typ`, it must be `logout+jwt`; tokens without `typ` remain accepted for provider compatibility. Each `jti` is consumed once and remembered until the token `exp`, so replaying the same valid token returns a successful no-op response without repeating revocation hooks.
412
+
382
413
  Example request:
383
414
 
384
415
  ```ts
@@ -422,6 +453,7 @@ const app = express();
422
453
  app.use(
423
454
  createOidcVaultMiddleware({
424
455
  basePath: '/auth/oidc',
456
+ backendOrigin: 'https://api.example.com',
425
457
  config: {
426
458
  issuer: process.env.OIDC_ISSUER,
427
459
  clientId: process.env.OIDC_CLIENT_ID,
@@ -450,6 +482,7 @@ await redis.connect();
450
482
  app.use(
451
483
  createOidcVaultMiddleware({
452
484
  basePath: '/auth/oidc',
485
+ backendOrigin: 'https://api.example.com',
453
486
  config: {
454
487
  issuer: process.env.OIDC_ISSUER,
455
488
  clientId: process.env.OIDC_CLIENT_ID,
@@ -481,6 +514,7 @@ await mongo.connect();
481
514
  app.use(
482
515
  createOidcVaultMiddleware({
483
516
  basePath: '/auth/oidc',
517
+ backendOrigin: 'https://api.example.com',
484
518
  config: {
485
519
  issuer: process.env.OIDC_ISSUER,
486
520
  clientId: process.env.OIDC_CLIENT_ID,
@@ -511,6 +545,7 @@ await redis.connect();
511
545
  app.use(
512
546
  createOidcVaultMiddleware({
513
547
  basePath: '/auth/oidc',
548
+ backendOrigin: 'https://api.example.com',
514
549
  config: {
515
550
  issuer: process.env.OIDC_ISSUER,
516
551
  clientId: process.env.OIDC_CLIENT_ID,
@@ -555,15 +590,38 @@ app.use(
555
590
  - `type OidcVaultJwtAccessTokenValidatorOptions`
556
591
  - `type OidcVaultTokenIssuer`
557
592
 
593
+ ## Store Provider Contract
594
+
595
+ The built-in memory, Redis, and MongoDB store packages share the same behavioral contract.
596
+
597
+ - `createAuthorizationTransaction`, `createExchangeCode`, and `createSession` are deliberate upserts keyed by `state`, `code`, and `sessionId`.
598
+ - Store metadata is portable when it is JSON-compatible: strings, finite numbers, booleans, null, arrays, and plain objects. Do not rely on functions, symbols, Dates, Maps, Sets, custom prototypes, undefined object properties, or object identity surviving a store round-trip.
599
+ - Store methods return owned values or serialization round-trips. Mutating an input after a create call or mutating a returned value does not mutate persisted state.
600
+ - Expiry timestamps are epoch milliseconds. Records are expired at `expiresAt <= now`; backchannel logout JTI expiry must be finite and in the future or the consume call returns `false` without storing the JTI.
601
+ - `rotateSession` requires an existing source session and a distinct unused target `sessionId`. Equivalent missing-source, same-ID, and existing-target rotation conflicts throw `OidcVaultStoreConflictError` without deleting or overwriting source or target data.
602
+ - Session rotation preserves the logical session ID when the next session omits one. Old public session IDs remain revocation aliases while the logical lineage remains live, so deleting by an old public ID can revoke the current rotated session.
603
+
558
604
  ## Key Integration Notes
559
605
 
560
606
  - The browser should never receive the upstream refresh token.
561
607
  - The backend should store the latest upstream `id_token` so logout can call the upstream end-session endpoint with `id_token_hint`.
562
608
  - `sessionId` should rotate on refresh.
563
609
  - The frontend should deduplicate concurrent refresh calls so only one refresh is in-flight at a time.
564
- - If `OIDC_ISSUER` is configured, issuer discovery should override explicit endpoint URLs.
610
+ - Upstream OAuth `expires_in` describes the upstream access token only. It does not set `OidcVaultSession.expiresAt` or shorten the refresh-token-backed vault session.
611
+ - `OidcVaultSession.expiresAt`, when set by application code or store policy, is an explicit vault-session expiry in epoch milliseconds and remains enforced by store providers.
612
+ - If only `OIDC_ISSUER` is configured, issuer discovery is used and the discovered issuer must match the configured issuer.
613
+ - Provider discovery metadata and remote JWKS resolvers are cached in bounded process-wide maps keyed by configured issuer URL and `jwks_uri`; these keys are intended to come from static middleware configuration, not request input.
614
+ - Successful discovery entries are reused for up to 10 minutes and both discovery and JWKS resolver maps retain at most 32 entries with oldest-entry eviction. Failed discovery requests are removed from the cache so a later request can retry.
615
+ - Discovery, token, UserInfo, and remote JWKS HTTP requests use a 5 second default timeout and manual redirect handling. Set `providerRequestTimeoutMs` on `createOidcVaultMiddleware(...)` to a positive integer number of milliseconds if your provider needs a different bound.
616
+ - Provider response parse errors return sanitized client messages; oversized or malformed provider bodies are not returned to callers.
617
+ - If manual endpoints are configured, `issuer` is still required so ID and logout tokens are issuer-bound.
618
+ - Token responses must include `token_type: Bearer`; `expires_in`, when present, must be a finite non-negative integer.
619
+ - ID tokens must include `sub`, `exp`, and `iat`; `azp` must match `clientId` when present and is required for multi-audience ID tokens.
620
+ - UserInfo responses must include a `sub` matching the verified ID-token subject before claims are merged into the session user.
621
+ - Refresh responses may omit `id_token`; in that case, the middleware keeps the existing verified ID token and identity claims without requiring that original ID token to still be current. If refresh returns a new `id_token`, its `sub` must match the current session subject.
622
+ - `backendOrigin` is the public origin registered with your OIDC provider for the backend callback URI. The middleware normalizes it to an origin and uses it for `/callback` redirect URIs instead of trusting request `Host` headers.
565
623
  - `frontendRedirectUri` is the default browser return target after the backend completes the upstream callback.
566
- - `postLogoutRedirectUri` is added to the upstream logout URL when configured.
624
+ - `postLogoutRedirectUri` is optional. When configured, it must be an absolute HTTP(S) URL registered with the OIDC provider for post-logout redirects. It may be hosted on a different origin from `frontendRedirectUri` when that exact URL is provider-registered.
567
625
  - backchannel logout revokes local sessions by upstream `sid` when available, otherwise by `sub`
568
626
 
569
627
  ## Config Helpers
@@ -576,8 +634,8 @@ const config = resolveOidcVaultConfigFromEnv(process.env);
576
634
 
577
635
  Resolution behavior:
578
636
 
579
- - if `OIDC_ISSUER` is set, discovery mode wins and endpoint-specific env vars are ignored
580
- - if `OIDC_ISSUER` is not set, manual mode requires `OIDC_AUTHORIZATION_ENDPOINT`, `OIDC_TOKEN_ENDPOINT`, and `OIDC_JWKS_URI`
637
+ - if only `OIDC_ISSUER` is set, discovery mode resolves the provider endpoints and validates that the discovered issuer matches
638
+ - if endpoint-specific env vars are set, manual mode requires `OIDC_ISSUER`, `OIDC_AUTHORIZATION_ENDPOINT`, `OIDC_TOKEN_ENDPOINT`, and `OIDC_JWKS_URI`
581
639
  - `OIDC_SCOPES` defaults to `openid email profile`
582
640
 
583
641
  ### Manual endpoint mode
@@ -598,7 +656,9 @@ await redis.connect();
598
656
  app.use(
599
657
  createOidcVaultMiddleware({
600
658
  basePath: '/auth/oidc',
659
+ backendOrigin: 'https://api.example.com',
601
660
  config: {
661
+ issuer: process.env.OIDC_ISSUER,
602
662
  authorizationEndpoint: process.env.OIDC_AUTHORIZATION_ENDPOINT,
603
663
  tokenEndpoint: process.env.OIDC_TOKEN_ENDPOINT,
604
664
  userInfoEndpoint: process.env.OIDC_USERINFO_ENDPOINT,
@@ -624,6 +684,7 @@ In manual mode, the minimum required config is:
624
684
  - `tokenEndpoint`
625
685
  - `jwksUri`
626
686
  - `clientId`
687
+ - `issuer`
627
688
 
628
689
  `userInfoEndpoint` and `endSessionEndpoint` are optional but recommended when your provider supports them.
629
690
 
@@ -643,6 +704,7 @@ const jwtSecret = new TextEncoder().encode(process.env.APP_JWT_SECRET ?? 'dev-se
643
704
  app.use(
644
705
  createOidcVaultMiddleware({
645
706
  basePath: '/auth/oidc',
707
+ backendOrigin: 'https://api.example.com',
646
708
  config: {
647
709
  issuer: process.env.OIDC_ISSUER,
648
710
  clientId: process.env.OIDC_CLIENT_ID,
@@ -807,6 +869,7 @@ const app = express();
807
869
  app.use(
808
870
  createOidcVaultMiddleware({
809
871
  basePath: '/auth/oidc',
872
+ backendOrigin: 'https://api.example.com',
810
873
  config: {
811
874
  issuer: process.env.OIDC_ISSUER,
812
875
  clientId: process.env.OIDC_CLIENT_ID,
@@ -841,7 +904,7 @@ app.use(
841
904
  async onLogout({ session, metadata }) {
842
905
  console.log('OIDC logout completed', {
843
906
  subject: session?.subject,
844
- upstreamLogoutUrl: metadata?.upstreamLogoutUrl,
907
+ revokedSessions: metadata?.revokedSessions,
845
908
  });
846
909
  },
847
910
  async onError({ error, route, req }) {
@@ -863,11 +926,15 @@ async function upsertLocalUser(input: { oidcSubject: string; email?: string; dis
863
926
 
864
927
  Recommended hook usage:
865
928
 
929
+ - `onLoginStart`, `onAuthorizationUrl`, `onCallbackTokens`, `onUserInfo`, `onBeforeSessionCreate`, and `onBeforeLogout` are pre-commit hooks. Throwing from one of these hooks vetoes the operation before the related durable session state is created, rotated, or deleted.
930
+ - `onSessionCreated`, `onSessionRefreshed`, and `onLogout` are post-commit notification hooks. Their failures are reported to `onError` but do not change a successful callback redirect, refresh response, logout response, or already-committed store mutation.
866
931
  - use `onSessionCreated` for local user provisioning or last-login updates
867
932
  - use `onSessionRefreshed` for audit logs and session rotation tracing
868
933
  - use `onLogout` to revoke local app state that depends on the session
869
934
  - use `onError` for structured logging and alerting
870
935
 
936
+ Client error responses keep a stable `{ code, message }` shape and intentionally avoid returning raw provider, store, hook, token issuer, or access-token validator details. Use `onError` to observe the original error object for private server-side logs.
937
+
871
938
  ## Security Checklist
872
939
 
873
940
  Use these defaults when deploying the package:
@@ -875,15 +942,20 @@ Use these defaults when deploying the package:
875
942
  - keep `sessionId` in `sessionStorage` and keep `accessToken` in memory only
876
943
  - never store the upstream refresh token in the browser
877
944
  - use HTTPS end-to-end for frontend, backend, and IdP communication
945
+ - set `backendOrigin` to the public backend origin registered with the provider; do not rely on request host or proxy headers for callback URL construction
946
+ - keep the default `requestBodyLimit` of `16kb` unless a provider requires a larger form-encoded backchannel `logout_token`
878
947
  - treat XSS prevention as critical because `sessionStorage` is still readable by JavaScript
879
948
  - enable a strict Content Security Policy and avoid unsafe inline scripts
880
949
  - rotate `sessionId` on refresh and overwrite the mirrored `sessionStorage` value immediately
881
950
  - clear in-memory auth state and `sessionStorage` on logout, even if upstream logout fails
882
951
  - set `postLogoutRedirectUri` explicitly so logout destinations stay predictable
883
- - when using cross-site cookie transport, allow only your known frontend origins via `trustedOrigins`
952
+ - when using cookie transport, rely on cookie credentials only for `refresh` and `logout`; do not send fallback body `sessionId` values
953
+ - when using cross-site cookie transport, send frontend requests with `credentials: 'include'`, enable credentialed CORS, use `SameSite=None; Secure`, and allow only known frontend origins via `trustedOrigins`
954
+ - keep cookie-authenticated CSRF protection fail-closed for every `SameSite` mode by requiring an `Origin` or valid `Referer` matching `backendOrigin` or `trustedOrigins`
884
955
  - protect any app-issued local access token with a short lifetime, such as 5 to 15 minutes
956
+ - treat upstream OAuth `expires_in`, local access-token lifetime, and vault-session expiry as separate policies
885
957
  - use Redis or MongoDB, not the memory store, for production or multi-instance deployments
886
- - monitor `onError` and other hooks so failed callback, refresh, and logout flows are visible in logs
958
+ - monitor `onError` and other hooks so failed callback, refresh, and logout flows are visible in private server logs without returning raw provider, token, store, or hook errors to clients
887
959
 
888
960
  ## Store Packages
889
961
 
package/index.d.mts CHANGED
@@ -16,6 +16,12 @@ interface OidcVaultProviderMetadata {
16
16
  }
17
17
  interface OidcVaultSession {
18
18
  sessionId: string;
19
+ /**
20
+ * Stable identifier for the logical refresh-token-backed session across
21
+ * session ID rotations. Stores use this to revoke a session even while a
22
+ * refresh rotates the public session ID.
23
+ */
24
+ logicalSessionId?: string;
19
25
  subject: string;
20
26
  providerSessionId?: string;
21
27
  provider?: OidcVaultProviderMetadata;
@@ -23,10 +29,27 @@ interface OidcVaultSession {
23
29
  idToken: string;
24
30
  accessToken?: string;
25
31
  scope?: string;
32
+ /**
33
+ * Optional vault-session expiry timestamp in epoch milliseconds.
34
+ *
35
+ * This controls the lifetime of the refresh-token-backed server-side session.
36
+ * It is not derived from upstream OAuth `expires_in`, which only describes the
37
+ * upstream access token lifetime. Leave unset when your application or store
38
+ * owns session lifetime through another policy.
39
+ */
26
40
  expiresAt?: number;
27
41
  createdAt: number;
28
42
  updatedAt: number;
29
43
  user?: OidcVaultUserProfile;
44
+ /**
45
+ * Store-portable application metadata.
46
+ *
47
+ * Values should be JSON-compatible: strings, finite numbers, booleans, null,
48
+ * arrays, and plain objects. Store providers return owned copies or
49
+ * serialization round-trips, so callers must not rely on object identity,
50
+ * custom prototypes, functions, symbols, Dates, Maps, Sets, undefined object
51
+ * properties, or other runtime-only values surviving persistence.
52
+ */
30
53
  metadata?: Record<string, unknown>;
31
54
  }
32
55
  interface AuthorizationTransactionInput {
@@ -37,6 +60,7 @@ interface AuthorizationTransactionInput {
37
60
  returnTo?: string;
38
61
  createdAt: number;
39
62
  expiresAt: number;
63
+ /** See `OidcVaultSession.metadata` for the portable metadata value domain. */
40
64
  metadata?: Record<string, unknown>;
41
65
  }
42
66
  type AuthorizationTransaction = AuthorizationTransactionInput;
@@ -66,18 +90,46 @@ interface DeleteSessionsByProviderSessionIdInput {
66
90
  issuer?: string;
67
91
  clientId?: string;
68
92
  }
93
+ interface DeleteSessionsByLogicalSessionIdInput {
94
+ logicalSessionId: string;
95
+ }
96
+ interface ConsumeBackchannelLogoutTokenJtiInput {
97
+ jti: string;
98
+ /** Finite future epoch-millisecond expiry. Values `<= now` are expired. */
99
+ expiresAt: number;
100
+ }
69
101
  declare class OidcVaultStoreConflictError extends Error {
70
102
  constructor(message?: string);
71
103
  }
72
104
  interface OidcVaultStoreProvider {
105
+ /** Upsert an authorization transaction by `state`. */
73
106
  createAuthorizationTransaction(input: AuthorizationTransactionInput): Promise<void>;
74
107
  consumeAuthorizationTransaction(state: string): Promise<AuthorizationTransaction | null>;
108
+ /** Upsert an exchange code record by `code`. */
75
109
  createExchangeCode(input: ExchangeCodeRecordInput): Promise<void>;
76
110
  consumeExchangeCode(code: string): Promise<ExchangeCodeRecord | null>;
111
+ /** Upsert a session by `sessionId`, defaulting timestamps and logical lineage when omitted. */
77
112
  createSession(input: OidcVaultSessionInput): Promise<OidcVaultSession>;
78
113
  getSession(sessionId: string): Promise<OidcVaultSession | null>;
114
+ /**
115
+ * Atomically replace an existing session with a distinct unused `nextSession.sessionId`.
116
+ *
117
+ * Providers preserve the existing logical session ID when the next session omits
118
+ * one, retain the old public session ID as a revocation alias while the lineage
119
+ * remains live, and throw `OidcVaultStoreConflictError` without changing source
120
+ * or target data when the source is missing, the target already exists, or the
121
+ * target ID equals the source ID.
122
+ */
79
123
  rotateSession(input: RotateSessionInput): Promise<OidcVaultSession>;
80
124
  deleteSession(sessionId: string): Promise<void>;
125
+ deleteSessionsByLogicalSessionId(input: string | DeleteSessionsByLogicalSessionIdInput): Promise<number>;
126
+ /**
127
+ * Record a backchannel logout JTI once until its finite future expiry.
128
+ *
129
+ * Returns `false` for duplicate, expired, exact-boundary, `NaN`, or infinite
130
+ * expiries. A JTI rejected for invalid or already-expired expiry is not stored.
131
+ */
132
+ consumeBackchannelLogoutTokenJti(input: ConsumeBackchannelLogoutTokenJtiInput): Promise<boolean>;
81
133
  deleteSessionsBySubject(input: string | DeleteSessionsBySubjectInput): Promise<number>;
82
134
  deleteSessionsByProviderSessionId(input: string | DeleteSessionsByProviderSessionIdInput): Promise<number>;
83
135
  }
@@ -146,7 +198,7 @@ interface OidcVaultJwtAccessTokenValidatorOptions {
146
198
  algorithms?: string[];
147
199
  mapClaims?(claims: Record<string, unknown>): OidcVaultAccessTokenValidationResult;
148
200
  }
149
- declare module 'express' {
201
+ declare module 'express-serve-static-core' {
150
202
  interface Request {
151
203
  auth?: OidcVaultAuthContext;
152
204
  }
@@ -164,7 +216,6 @@ interface OidcVaultConfig {
164
216
  }
165
217
  interface OidcVaultLogoutResult {
166
218
  loggedOut: true;
167
- upstreamLogoutUrl?: string;
168
219
  }
169
220
  interface OidcVaultBackchannelLogoutResult {
170
221
  loggedOut: true;
@@ -188,6 +239,7 @@ interface OidcVaultCookieOptions {
188
239
  }
189
240
  interface OidcVaultOptions {
190
241
  basePath?: string;
242
+ backendOrigin: string;
191
243
  storeProvider: OidcVaultStoreProvider;
192
244
  config?: OidcVaultConfig;
193
245
  hooks?: OidcVaultHooks;
@@ -200,6 +252,8 @@ interface OidcVaultOptions {
200
252
  sessionTransport?: OidcVaultSessionTransport;
201
253
  cookie?: OidcVaultCookieOptions;
202
254
  trustedOrigins?: string[];
255
+ requestBodyLimit?: string | number;
256
+ providerRequestTimeoutMs?: number;
203
257
  now?: () => number;
204
258
  }
205
259
 
@@ -233,26 +287,23 @@ declare function resolveOidcVaultConfigFromEnv(env?: OidcVaultEnv): OidcVaultRes
233
287
  declare const DEFAULT_OIDC_VAULT_BASE_PATH = "/auth/oidc";
234
288
  declare const DEFAULT_AUTHORIZATION_TRANSACTION_TTL_MS: number;
235
289
  declare const DEFAULT_EXCHANGE_CODE_TTL_MS: number;
236
- declare const DEFAULT_SESSION_COOKIE_NAME = "oidc_vault_session";
290
+ declare const DEFAULT_OIDC_VAULT_REQUEST_BODY_LIMIT = "16kb";
291
+ declare const OIDC_VAULT_URL_ENCODED_PARAMETER_LIMIT = 16;
237
292
  declare const OIDC_VAULT_ROUTE_PATHS: Record<OidcVaultRouteName, string>;
238
- /**
239
- * Normalize the mounted base path for the OIDC router.
240
- */
241
- declare function normalizeOidcVaultBasePath(value?: string): string;
242
- /**
243
- * Create a JWT-based access-token validator for use with
244
- * `createOidcVaultAccessTokenMiddleware(...)`.
245
- */
293
+
294
+ declare function createOidcVaultAccessTokenMiddleware(options: OidcVaultAccessTokenMiddlewareOptions): RequestHandler;
295
+
246
296
  declare function createOidcVaultJwtAccessTokenValidator(options: OidcVaultJwtAccessTokenValidatorOptions): {
247
297
  validate(token: string): Promise<OidcVaultAccessTokenValidationResult>;
248
298
  };
299
+
249
300
  /**
250
- * Create bearer-token validation middleware for app-issued access tokens.
301
+ * Normalize the mounted base path for the OIDC router.
251
302
  */
252
- declare function createOidcVaultAccessTokenMiddleware(options: OidcVaultAccessTokenMiddlewareOptions): RequestHandler;
303
+ declare function normalizeOidcVaultBasePath(value?: string): string;
253
304
  /**
254
305
  * Create the core OIDC vault middleware.
255
306
  */
256
307
  declare function createOidcVaultMiddleware(options: OidcVaultOptions): Router;
257
308
 
258
- export { type AuthorizationTransaction, type AuthorizationTransactionInput, DEFAULT_AUTHORIZATION_TRANSACTION_TTL_MS, DEFAULT_EXCHANGE_CODE_TTL_MS, DEFAULT_OIDC_SCOPES, DEFAULT_OIDC_VAULT_BASE_PATH, DEFAULT_SESSION_COOKIE_NAME, type DeleteSessionsByProviderSessionIdInput, type DeleteSessionsBySubjectInput, type ExchangeCodeRecord, type ExchangeCodeRecordInput, type IssueTokenInput, OIDC_VAULT_ROUTE_PATHS, type OidcVaultAccessTokenMiddlewareOptions, type OidcVaultAccessTokenValidationResult, type OidcVaultAccessTokenValidator, type OidcVaultAuthContext, type OidcVaultAuthenticatedRequest, type OidcVaultBackchannelLogoutResult, type OidcVaultConfig, type OidcVaultConfigMode, type OidcVaultCookieDeploymentMode, type OidcVaultCookieOptions, type OidcVaultCookieSameSite, type OidcVaultEnv, type OidcVaultErrorContext, type OidcVaultExchangeResult, type OidcVaultHookContext, type OidcVaultHooks, type OidcVaultJwtAccessTokenValidatorOptions, type OidcVaultLogoutResult, type OidcVaultOptions, type OidcVaultProviderMetadata, type OidcVaultResolvedConfig, type OidcVaultRouteName, type OidcVaultSession, type OidcVaultSessionInput, type OidcVaultSessionTransport, OidcVaultStoreConflictError, type OidcVaultStoreProvider, type OidcVaultTokenIssueResult, type OidcVaultTokenIssuer, type OidcVaultUserProfile, type RotateSessionInput, createOidcVaultAccessTokenMiddleware, createOidcVaultJwtAccessTokenValidator, createOidcVaultMiddleware, normalizeOidcVaultBasePath, resolveOidcVaultConfig, resolveOidcVaultConfigFromEnv };
309
+ export { type AuthorizationTransaction, type AuthorizationTransactionInput, type ConsumeBackchannelLogoutTokenJtiInput, DEFAULT_AUTHORIZATION_TRANSACTION_TTL_MS, DEFAULT_EXCHANGE_CODE_TTL_MS, DEFAULT_OIDC_SCOPES, DEFAULT_OIDC_VAULT_BASE_PATH, DEFAULT_OIDC_VAULT_REQUEST_BODY_LIMIT, type DeleteSessionsByLogicalSessionIdInput, type DeleteSessionsByProviderSessionIdInput, type DeleteSessionsBySubjectInput, type ExchangeCodeRecord, type ExchangeCodeRecordInput, type IssueTokenInput, OIDC_VAULT_ROUTE_PATHS, OIDC_VAULT_URL_ENCODED_PARAMETER_LIMIT, type OidcVaultAccessTokenMiddlewareOptions, type OidcVaultAccessTokenValidationResult, type OidcVaultAccessTokenValidator, type OidcVaultAuthContext, type OidcVaultAuthenticatedRequest, type OidcVaultBackchannelLogoutResult, type OidcVaultConfig, type OidcVaultConfigMode, type OidcVaultCookieDeploymentMode, type OidcVaultCookieOptions, type OidcVaultCookieSameSite, type OidcVaultEnv, type OidcVaultErrorContext, type OidcVaultExchangeResult, type OidcVaultHookContext, type OidcVaultHooks, type OidcVaultJwtAccessTokenValidatorOptions, type OidcVaultLogoutResult, type OidcVaultOptions, type OidcVaultProviderMetadata, type OidcVaultResolvedConfig, type OidcVaultRouteName, type OidcVaultSession, type OidcVaultSessionInput, type OidcVaultSessionTransport, OidcVaultStoreConflictError, type OidcVaultStoreProvider, type OidcVaultTokenIssueResult, type OidcVaultTokenIssuer, type OidcVaultUserProfile, type RotateSessionInput, createOidcVaultAccessTokenMiddleware, createOidcVaultJwtAccessTokenValidator, createOidcVaultMiddleware, normalizeOidcVaultBasePath, resolveOidcVaultConfig, resolveOidcVaultConfigFromEnv };
package/index.d.ts CHANGED
@@ -16,6 +16,12 @@ interface OidcVaultProviderMetadata {
16
16
  }
17
17
  interface OidcVaultSession {
18
18
  sessionId: string;
19
+ /**
20
+ * Stable identifier for the logical refresh-token-backed session across
21
+ * session ID rotations. Stores use this to revoke a session even while a
22
+ * refresh rotates the public session ID.
23
+ */
24
+ logicalSessionId?: string;
19
25
  subject: string;
20
26
  providerSessionId?: string;
21
27
  provider?: OidcVaultProviderMetadata;
@@ -23,10 +29,27 @@ interface OidcVaultSession {
23
29
  idToken: string;
24
30
  accessToken?: string;
25
31
  scope?: string;
32
+ /**
33
+ * Optional vault-session expiry timestamp in epoch milliseconds.
34
+ *
35
+ * This controls the lifetime of the refresh-token-backed server-side session.
36
+ * It is not derived from upstream OAuth `expires_in`, which only describes the
37
+ * upstream access token lifetime. Leave unset when your application or store
38
+ * owns session lifetime through another policy.
39
+ */
26
40
  expiresAt?: number;
27
41
  createdAt: number;
28
42
  updatedAt: number;
29
43
  user?: OidcVaultUserProfile;
44
+ /**
45
+ * Store-portable application metadata.
46
+ *
47
+ * Values should be JSON-compatible: strings, finite numbers, booleans, null,
48
+ * arrays, and plain objects. Store providers return owned copies or
49
+ * serialization round-trips, so callers must not rely on object identity,
50
+ * custom prototypes, functions, symbols, Dates, Maps, Sets, undefined object
51
+ * properties, or other runtime-only values surviving persistence.
52
+ */
30
53
  metadata?: Record<string, unknown>;
31
54
  }
32
55
  interface AuthorizationTransactionInput {
@@ -37,6 +60,7 @@ interface AuthorizationTransactionInput {
37
60
  returnTo?: string;
38
61
  createdAt: number;
39
62
  expiresAt: number;
63
+ /** See `OidcVaultSession.metadata` for the portable metadata value domain. */
40
64
  metadata?: Record<string, unknown>;
41
65
  }
42
66
  type AuthorizationTransaction = AuthorizationTransactionInput;
@@ -66,18 +90,46 @@ interface DeleteSessionsByProviderSessionIdInput {
66
90
  issuer?: string;
67
91
  clientId?: string;
68
92
  }
93
+ interface DeleteSessionsByLogicalSessionIdInput {
94
+ logicalSessionId: string;
95
+ }
96
+ interface ConsumeBackchannelLogoutTokenJtiInput {
97
+ jti: string;
98
+ /** Finite future epoch-millisecond expiry. Values `<= now` are expired. */
99
+ expiresAt: number;
100
+ }
69
101
  declare class OidcVaultStoreConflictError extends Error {
70
102
  constructor(message?: string);
71
103
  }
72
104
  interface OidcVaultStoreProvider {
105
+ /** Upsert an authorization transaction by `state`. */
73
106
  createAuthorizationTransaction(input: AuthorizationTransactionInput): Promise<void>;
74
107
  consumeAuthorizationTransaction(state: string): Promise<AuthorizationTransaction | null>;
108
+ /** Upsert an exchange code record by `code`. */
75
109
  createExchangeCode(input: ExchangeCodeRecordInput): Promise<void>;
76
110
  consumeExchangeCode(code: string): Promise<ExchangeCodeRecord | null>;
111
+ /** Upsert a session by `sessionId`, defaulting timestamps and logical lineage when omitted. */
77
112
  createSession(input: OidcVaultSessionInput): Promise<OidcVaultSession>;
78
113
  getSession(sessionId: string): Promise<OidcVaultSession | null>;
114
+ /**
115
+ * Atomically replace an existing session with a distinct unused `nextSession.sessionId`.
116
+ *
117
+ * Providers preserve the existing logical session ID when the next session omits
118
+ * one, retain the old public session ID as a revocation alias while the lineage
119
+ * remains live, and throw `OidcVaultStoreConflictError` without changing source
120
+ * or target data when the source is missing, the target already exists, or the
121
+ * target ID equals the source ID.
122
+ */
79
123
  rotateSession(input: RotateSessionInput): Promise<OidcVaultSession>;
80
124
  deleteSession(sessionId: string): Promise<void>;
125
+ deleteSessionsByLogicalSessionId(input: string | DeleteSessionsByLogicalSessionIdInput): Promise<number>;
126
+ /**
127
+ * Record a backchannel logout JTI once until its finite future expiry.
128
+ *
129
+ * Returns `false` for duplicate, expired, exact-boundary, `NaN`, or infinite
130
+ * expiries. A JTI rejected for invalid or already-expired expiry is not stored.
131
+ */
132
+ consumeBackchannelLogoutTokenJti(input: ConsumeBackchannelLogoutTokenJtiInput): Promise<boolean>;
81
133
  deleteSessionsBySubject(input: string | DeleteSessionsBySubjectInput): Promise<number>;
82
134
  deleteSessionsByProviderSessionId(input: string | DeleteSessionsByProviderSessionIdInput): Promise<number>;
83
135
  }
@@ -146,7 +198,7 @@ interface OidcVaultJwtAccessTokenValidatorOptions {
146
198
  algorithms?: string[];
147
199
  mapClaims?(claims: Record<string, unknown>): OidcVaultAccessTokenValidationResult;
148
200
  }
149
- declare module 'express' {
201
+ declare module 'express-serve-static-core' {
150
202
  interface Request {
151
203
  auth?: OidcVaultAuthContext;
152
204
  }
@@ -164,7 +216,6 @@ interface OidcVaultConfig {
164
216
  }
165
217
  interface OidcVaultLogoutResult {
166
218
  loggedOut: true;
167
- upstreamLogoutUrl?: string;
168
219
  }
169
220
  interface OidcVaultBackchannelLogoutResult {
170
221
  loggedOut: true;
@@ -188,6 +239,7 @@ interface OidcVaultCookieOptions {
188
239
  }
189
240
  interface OidcVaultOptions {
190
241
  basePath?: string;
242
+ backendOrigin: string;
191
243
  storeProvider: OidcVaultStoreProvider;
192
244
  config?: OidcVaultConfig;
193
245
  hooks?: OidcVaultHooks;
@@ -200,6 +252,8 @@ interface OidcVaultOptions {
200
252
  sessionTransport?: OidcVaultSessionTransport;
201
253
  cookie?: OidcVaultCookieOptions;
202
254
  trustedOrigins?: string[];
255
+ requestBodyLimit?: string | number;
256
+ providerRequestTimeoutMs?: number;
203
257
  now?: () => number;
204
258
  }
205
259
 
@@ -233,26 +287,23 @@ declare function resolveOidcVaultConfigFromEnv(env?: OidcVaultEnv): OidcVaultRes
233
287
  declare const DEFAULT_OIDC_VAULT_BASE_PATH = "/auth/oidc";
234
288
  declare const DEFAULT_AUTHORIZATION_TRANSACTION_TTL_MS: number;
235
289
  declare const DEFAULT_EXCHANGE_CODE_TTL_MS: number;
236
- declare const DEFAULT_SESSION_COOKIE_NAME = "oidc_vault_session";
290
+ declare const DEFAULT_OIDC_VAULT_REQUEST_BODY_LIMIT = "16kb";
291
+ declare const OIDC_VAULT_URL_ENCODED_PARAMETER_LIMIT = 16;
237
292
  declare const OIDC_VAULT_ROUTE_PATHS: Record<OidcVaultRouteName, string>;
238
- /**
239
- * Normalize the mounted base path for the OIDC router.
240
- */
241
- declare function normalizeOidcVaultBasePath(value?: string): string;
242
- /**
243
- * Create a JWT-based access-token validator for use with
244
- * `createOidcVaultAccessTokenMiddleware(...)`.
245
- */
293
+
294
+ declare function createOidcVaultAccessTokenMiddleware(options: OidcVaultAccessTokenMiddlewareOptions): RequestHandler;
295
+
246
296
  declare function createOidcVaultJwtAccessTokenValidator(options: OidcVaultJwtAccessTokenValidatorOptions): {
247
297
  validate(token: string): Promise<OidcVaultAccessTokenValidationResult>;
248
298
  };
299
+
249
300
  /**
250
- * Create bearer-token validation middleware for app-issued access tokens.
301
+ * Normalize the mounted base path for the OIDC router.
251
302
  */
252
- declare function createOidcVaultAccessTokenMiddleware(options: OidcVaultAccessTokenMiddlewareOptions): RequestHandler;
303
+ declare function normalizeOidcVaultBasePath(value?: string): string;
253
304
  /**
254
305
  * Create the core OIDC vault middleware.
255
306
  */
256
307
  declare function createOidcVaultMiddleware(options: OidcVaultOptions): Router;
257
308
 
258
- export { type AuthorizationTransaction, type AuthorizationTransactionInput, DEFAULT_AUTHORIZATION_TRANSACTION_TTL_MS, DEFAULT_EXCHANGE_CODE_TTL_MS, DEFAULT_OIDC_SCOPES, DEFAULT_OIDC_VAULT_BASE_PATH, DEFAULT_SESSION_COOKIE_NAME, type DeleteSessionsByProviderSessionIdInput, type DeleteSessionsBySubjectInput, type ExchangeCodeRecord, type ExchangeCodeRecordInput, type IssueTokenInput, OIDC_VAULT_ROUTE_PATHS, type OidcVaultAccessTokenMiddlewareOptions, type OidcVaultAccessTokenValidationResult, type OidcVaultAccessTokenValidator, type OidcVaultAuthContext, type OidcVaultAuthenticatedRequest, type OidcVaultBackchannelLogoutResult, type OidcVaultConfig, type OidcVaultConfigMode, type OidcVaultCookieDeploymentMode, type OidcVaultCookieOptions, type OidcVaultCookieSameSite, type OidcVaultEnv, type OidcVaultErrorContext, type OidcVaultExchangeResult, type OidcVaultHookContext, type OidcVaultHooks, type OidcVaultJwtAccessTokenValidatorOptions, type OidcVaultLogoutResult, type OidcVaultOptions, type OidcVaultProviderMetadata, type OidcVaultResolvedConfig, type OidcVaultRouteName, type OidcVaultSession, type OidcVaultSessionInput, type OidcVaultSessionTransport, OidcVaultStoreConflictError, type OidcVaultStoreProvider, type OidcVaultTokenIssueResult, type OidcVaultTokenIssuer, type OidcVaultUserProfile, type RotateSessionInput, createOidcVaultAccessTokenMiddleware, createOidcVaultJwtAccessTokenValidator, createOidcVaultMiddleware, normalizeOidcVaultBasePath, resolveOidcVaultConfig, resolveOidcVaultConfigFromEnv };
309
+ export { type AuthorizationTransaction, type AuthorizationTransactionInput, type ConsumeBackchannelLogoutTokenJtiInput, DEFAULT_AUTHORIZATION_TRANSACTION_TTL_MS, DEFAULT_EXCHANGE_CODE_TTL_MS, DEFAULT_OIDC_SCOPES, DEFAULT_OIDC_VAULT_BASE_PATH, DEFAULT_OIDC_VAULT_REQUEST_BODY_LIMIT, type DeleteSessionsByLogicalSessionIdInput, type DeleteSessionsByProviderSessionIdInput, type DeleteSessionsBySubjectInput, type ExchangeCodeRecord, type ExchangeCodeRecordInput, type IssueTokenInput, OIDC_VAULT_ROUTE_PATHS, OIDC_VAULT_URL_ENCODED_PARAMETER_LIMIT, type OidcVaultAccessTokenMiddlewareOptions, type OidcVaultAccessTokenValidationResult, type OidcVaultAccessTokenValidator, type OidcVaultAuthContext, type OidcVaultAuthenticatedRequest, type OidcVaultBackchannelLogoutResult, type OidcVaultConfig, type OidcVaultConfigMode, type OidcVaultCookieDeploymentMode, type OidcVaultCookieOptions, type OidcVaultCookieSameSite, type OidcVaultEnv, type OidcVaultErrorContext, type OidcVaultExchangeResult, type OidcVaultHookContext, type OidcVaultHooks, type OidcVaultJwtAccessTokenValidatorOptions, type OidcVaultLogoutResult, type OidcVaultOptions, type OidcVaultProviderMetadata, type OidcVaultResolvedConfig, type OidcVaultRouteName, type OidcVaultSession, type OidcVaultSessionInput, type OidcVaultSessionTransport, OidcVaultStoreConflictError, type OidcVaultStoreProvider, type OidcVaultTokenIssueResult, type OidcVaultTokenIssuer, type OidcVaultUserProfile, type RotateSessionInput, createOidcVaultAccessTokenMiddleware, createOidcVaultJwtAccessTokenValidator, createOidcVaultMiddleware, normalizeOidcVaultBasePath, resolveOidcVaultConfig, resolveOidcVaultConfigFromEnv };