@zitadel/sdk-core 0.1.0-alpha.9 → 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 +106 -1
- package/dist/index.d.mts +23 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +40 -0
- package/dist/index.mjs.map +1 -0
- package/dist/{jwt.d.ts → jwt.d.mts} +115 -83
- package/dist/jwt.d.mts.map +1 -0
- package/dist/jwt.mjs +465 -0
- package/dist/jwt.mjs.map +1 -0
- package/dist/middleware.d.mts +556 -0
- package/dist/middleware.d.mts.map +1 -0
- package/dist/middleware.mjs +764 -0
- package/dist/middleware.mjs.map +1 -0
- package/dist/types.d.mts +310 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +78 -0
- package/dist/types.mjs.map +1 -0
- package/package.json +29 -12
- package/dist/index.d.ts +0 -22
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -51
- package/dist/jwt.d.ts.map +0 -1
- package/dist/jwt.js +0 -383
- package/dist/middleware.d.ts +0 -39
- package/dist/middleware.d.ts.map +0 -1
- package/dist/middleware.js +0 -76
- package/dist/tsconfig.lib.tsbuildinfo +0 -1
- package/dist/types.d.ts +0 -206
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -8
package/README.md
CHANGED
|
@@ -1,3 +1,108 @@
|
|
|
1
1
|
# @zitadel/sdk-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The shared runtime contract behind the framework SDKs: JWT verification,
|
|
4
|
+
middleware primitives, runtime resolution, and the session types every
|
|
5
|
+
`@zitadel/sdk-*` package re-exports. Application code usually consumes a
|
|
6
|
+
framework SDK (`@zitadel/sdk-next`, `@zitadel/sdk-nuxt`, or one of the SPA
|
|
7
|
+
SDKs) instead of this package directly — reach for `sdk-core` when you are
|
|
8
|
+
building your own middleware or need the types without a framework wrapper.
|
|
9
|
+
|
|
10
|
+
## Entry points
|
|
11
|
+
|
|
12
|
+
| Import | What it carries |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| `@zitadel/sdk-core` | The middleware, JWT, and runtime-resolution exports in one namespace. The `/types` widget contract is **not** re-exported from the root — import it from `@zitadel/sdk-core/types` |
|
|
15
|
+
| `@zitadel/sdk-core/types` | The SPA widget contract (`ZitadelFlowStepDetail` and the other widget event/config/handler/props types the SPA SDKs re-export) |
|
|
16
|
+
| `@zitadel/sdk-core/jwt` | `verifyJwt`, `decodeJwt`, `isJwtShaped`, `JWKS_TTL_MS` |
|
|
17
|
+
| `@zitadel/sdk-core/middleware` | The middleware layer: `NextgenSession`, `AuthResult`, `NextgenMiddlewareOptions`, route matching (`matchesRoutes`), response-header filtering (`filterResponseHeaders`, `HOP_BY_HOP`), session judgement (`resolveSession`, the opaque session check `checkOpaqueSessionToken`), and the session cookie per project (`sessionCookieName`, `proxySessionCookies`, `serializeSessionCookie`) |
|
|
18
|
+
|
|
19
|
+
It also exports `resolveZitadelRuntime` / `resolveZitadelRuntimeEnv` and
|
|
20
|
+
`ZitadelRuntimeError` for resolving the runtime environment
|
|
21
|
+
(`development` / `preview` / `production`).
|
|
22
|
+
|
|
23
|
+
## How JWT verification works
|
|
24
|
+
|
|
25
|
+
This is the canonical description of the verification pipeline every SDK
|
|
26
|
+
middleware runs (`verifyJwt`):
|
|
27
|
+
|
|
28
|
+
1. The bearer token from the `Authorization` header is checked first; the
|
|
29
|
+
session cookie is the fallback (see
|
|
30
|
+
[The session cookie](#the-session-cookie)).
|
|
31
|
+
2. The JWT header is decoded to extract `kid` and `alg`.
|
|
32
|
+
3. Tokens with an `alg` not in `allowedAlgorithms` (`RS256`, `ES256` by
|
|
33
|
+
default) are rejected immediately — no JWKS fetch.
|
|
34
|
+
4. Tokens with a `typ` not in `allowedTokenTypes` are rejected immediately.
|
|
35
|
+
5. The public key is fetched from `{url}/auth/keys` (JWKS) using the Web
|
|
36
|
+
Crypto API, with a timeout (`jwksTimeoutMs`), and cached per `kid`
|
|
37
|
+
(`JWKS_TTL_MS`).
|
|
38
|
+
6. The signature is verified **before** any claim checks.
|
|
39
|
+
7. `iss` must be present and equal `url` — tokens without an issuer are
|
|
40
|
+
rejected.
|
|
41
|
+
8. `exp` must be present and in the future (with `clockSkewMs` tolerance) —
|
|
42
|
+
tokens without an expiry are rejected.
|
|
43
|
+
9. `nbf` and `iat` are validated with `clockSkewMs` tolerance when present.
|
|
44
|
+
10. `aud` must name the app's project (`projectId`).
|
|
45
|
+
|
|
46
|
+
Opaque (non-JWT) session tokens are validated against the backend's
|
|
47
|
+
`GET /sessions/me?project_id=…` instead, bounded by `opaqueTokenTimeoutMs`
|
|
48
|
+
(`checkOpaqueSessionToken`).
|
|
49
|
+
|
|
50
|
+
A session belongs to one project, while a browser sends the session cookie
|
|
51
|
+
to every app on the same host, including the Console of a local server. So a
|
|
52
|
+
session counts as signed in only in the app's own project: `projectId`, which
|
|
53
|
+
each SDK otherwise reads from `ZITADEL_PROJECT_ID` (then its framework's public
|
|
54
|
+
project id variable, as `zitadel setup` writes both). The backend refuses an
|
|
55
|
+
opaque session of another project (`403 sess.project_mismatch`), and the SDK
|
|
56
|
+
checks the project the answer names as well. A session of another project
|
|
57
|
+
reads as signed out and keeps its cookie, since it is live in its own app.
|
|
58
|
+
With no project configured, no session counts as signed in.
|
|
59
|
+
|
|
60
|
+
Only the backend's refusal makes the session cookie dead, and only then does
|
|
61
|
+
the middleware clear it: a `401`, or a `404` with the `sess.not_found` code for
|
|
62
|
+
a session that is gone. A JWT session cookie is dead when it fails verification
|
|
63
|
+
or names no user of this project. Any other outcome (a `5xx`, no answer in
|
|
64
|
+
time, a network error) is not a refusal: the request counts as signed out, the
|
|
65
|
+
cookie stays, and a protected route answers `503 Service Unavailable` rather
|
|
66
|
+
than send a user who may well be signed in to the login page. A Bearer token
|
|
67
|
+
that signs no one in here leaves the cookie to be judged on its own.
|
|
68
|
+
|
|
69
|
+
`resolveSession` is the one implementation of these rules; every server SDK's
|
|
70
|
+
middleware calls it.
|
|
71
|
+
|
|
72
|
+
## The session cookie
|
|
73
|
+
|
|
74
|
+
The server sets and reads one session cookie, `__nextgen_session`. A browser
|
|
75
|
+
shares a host's cookies across every port, so under that name alone the Console
|
|
76
|
+
of a local server and every app on `localhost` would keep their sessions in one
|
|
77
|
+
slot and sign each other out. An app with a server SDK therefore keeps its
|
|
78
|
+
session in a cookie of its own, `sessionCookieName(projectId)`:
|
|
79
|
+
`__nextgen_session_<projectId>` (ADR 073).
|
|
80
|
+
|
|
81
|
+
- **The proxy** (`proxySessionCookies`) renames the server's `Set-Cookie` to the
|
|
82
|
+
app's name on the way to the browser, and on the way to the server sends the
|
|
83
|
+
app's cookie under the server's name. No other session cookie, the
|
|
84
|
+
Console's or another app's, goes up.
|
|
85
|
+
- **The middleware** (`resolveSession`) reads the app's cookie, and clears only
|
|
86
|
+
that cookie when the backend refuses its session.
|
|
87
|
+
- **A session kept under the server's name** before the change, by this app or,
|
|
88
|
+
on `localhost`, by the Console, is read only where the server holds it to the
|
|
89
|
+
app's project: by the middleware, and by the proxy for the session probe
|
|
90
|
+
`GET /sessions/me?project_id=…`. A session that signs a user of this project
|
|
91
|
+
in is copied to the app's cookie, and the old cookie stays: the Console may
|
|
92
|
+
share the project. When the app next signs a user in (`POST /sessions/exchange`)
|
|
93
|
+
or out (`DELETE /sessions/me`), the proxy retires it: cleared when it holds a
|
|
94
|
+
session of this project (and on sign-out that session is ended), or a dead
|
|
95
|
+
one; left when it holds another project's.
|
|
96
|
+
- **Every response that sets a session cookie** sends
|
|
97
|
+
`Cache-Control: private, no-store` (`SESSION_COOKIE_CACHE_CONTROL`), and
|
|
98
|
+
`Secure` follows the server's rule on the scheme and host a proxy in front of
|
|
99
|
+
the app forwarded (`X-Forwarded-Proto`, `X-Forwarded-Host`).
|
|
100
|
+
- **Without a project**, the proxy passes the cookies through unchanged.
|
|
101
|
+
|
|
102
|
+
Framework-specific steps (header tunnelling, `auth()` re-verification) are
|
|
103
|
+
documented in each framework SDK's README on top of this pipeline.
|
|
104
|
+
|
|
105
|
+
## Requirements
|
|
106
|
+
|
|
107
|
+
TypeScript ≥ 5.0 — the published type definitions re-export with
|
|
108
|
+
`export type *`.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DecodedJwt, JWKS_MIN_REFRESH_MS, JWKS_TTL_MS, JwtHeader, JwtPayload, VerifyJwtOptions, base64UrlDecode, clearJwksCache, decodeJwt, isJwtShaped, verifyJwt } from "./jwt.mjs";
|
|
2
|
+
import { AuthResult, AuthState, BrowserRequest, HOP_BY_HOP, INTERNAL_HEADERS, NextgenMiddlewareOptions, NextgenSession, OpaqueSession, OpaqueSessionCheck, ProxiedAnswer, ProxiedRequest, ProxiedSessionCookies, ProxyUpstream, ResolveSessionOptions, ResolvedSession, SERVER_SESSION_COOKIE, SESSION_COOKIE_CACHE_CONTROL, SESSION_PROJECT_MISMATCH, SessionCookie, SessionRequest, UnauthState, checkOpaqueSessionToken, clearSessionCheckCache, filterResponseHeaders, jwtNamesProject, matchesRoutes, proxySessionCookies, requiresProjectSecret, resolveSession, serializeSessionCookie, sessionCookieName, validateOpaqueSessionToken } from "./middleware.mjs";
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
export type ZitadelEnvironment = "development" | "preview" | "production";
|
|
5
|
+
export type ZitadelRuntime = {
|
|
6
|
+
projectId: string;
|
|
7
|
+
environment: ZitadelEnvironment;
|
|
8
|
+
issuer?: string;
|
|
9
|
+
};
|
|
10
|
+
export type ZitadelRuntimeInput = {
|
|
11
|
+
projectId?: string;
|
|
12
|
+
environment?: ZitadelEnvironment;
|
|
13
|
+
issuer?: string;
|
|
14
|
+
};
|
|
15
|
+
export declare class ZitadelRuntimeError extends Error {
|
|
16
|
+
readonly code: string;
|
|
17
|
+
constructor(code: string, message: string);
|
|
18
|
+
}
|
|
19
|
+
export declare function resolveZitadelRuntimeEnv(env?: Record<string, string | undefined>): ZitadelRuntime;
|
|
20
|
+
export declare function resolveZitadelRuntime(input: ZitadelRuntimeInput): ZitadelRuntime;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { type AuthResult, type AuthState, type BrowserRequest, type DecodedJwt, HOP_BY_HOP, INTERNAL_HEADERS, JWKS_MIN_REFRESH_MS, JWKS_TTL_MS, type JwtHeader, type JwtPayload, type NextgenMiddlewareOptions, type NextgenSession, type OpaqueSession, type OpaqueSessionCheck, type ProxiedAnswer, type ProxiedRequest, type ProxiedSessionCookies, type ProxyUpstream, type ResolveSessionOptions, type ResolvedSession, SERVER_SESSION_COOKIE, SESSION_COOKIE_CACHE_CONTROL, SESSION_PROJECT_MISMATCH, type SessionCookie, type SessionRequest, type UnauthState, type VerifyJwtOptions, base64UrlDecode, checkOpaqueSessionToken, clearJwksCache, clearSessionCheckCache, decodeJwt, filterResponseHeaders, isJwtShaped, jwtNamesProject, matchesRoutes, proxySessionCookies, requiresProjectSecret, resolveSession, serializeSessionCookie, sessionCookieName, validateOpaqueSessionToken, verifyJwt };
|
|
23
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;YAiDY;YAEA;EACV;EACA,aAAa;EACb;;YAGU;EACV;EACA,cAAc;EACd;;qBAGW,4BAA4B;WAC9B;EAEG,YAAA,cAAc;;wBAOZ,yBACd,MAAK,qCACJ;wBAQa,sBAAsB,OAAO,sBAAsB"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { JWKS_MIN_REFRESH_MS, JWKS_TTL_MS, base64UrlDecode, clearJwksCache, decodeJwt, isJwtShaped, verifyJwt } from "./jwt.mjs";
|
|
2
|
+
import { HOP_BY_HOP, INTERNAL_HEADERS, SERVER_SESSION_COOKIE, SESSION_COOKIE_CACHE_CONTROL, SESSION_PROJECT_MISMATCH, checkOpaqueSessionToken, clearSessionCheckCache, filterResponseHeaders, jwtNamesProject, matchesRoutes, proxySessionCookies, requiresProjectSecret, resolveSession, serializeSessionCookie, sessionCookieName, validateOpaqueSessionToken } from "./middleware.mjs";
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
var ZitadelRuntimeError = class extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "ZitadelRuntimeError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
function resolveZitadelRuntimeEnv(env = currentEnv()) {
|
|
13
|
+
return resolveZitadelRuntime({
|
|
14
|
+
projectId: env.NEXT_PUBLIC_ZITADEL_PROJECT_ID ?? env.ZITADEL_PROJECT_ID,
|
|
15
|
+
environment: parseEnvironment(env.NEXT_PUBLIC_ZITADEL_ENVIRONMENT ?? env.ZITADEL_ENVIRONMENT),
|
|
16
|
+
issuer: env.NEXT_PUBLIC_ZITADEL_ISSUER ?? env.ZITADEL_ISSUER
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function resolveZitadelRuntime(input) {
|
|
20
|
+
const environment = input.environment ?? "development";
|
|
21
|
+
if (!input.projectId) throw new ZitadelRuntimeError("E_ZITADEL_CONFIG", "ZITADEL_PROJECT_ID is required for Zitadel runtime.");
|
|
22
|
+
if (environment === "production" && !input.issuer) throw new ZitadelRuntimeError("E_ZITADEL_CONFIG", "ZITADEL_ISSUER is required in production.");
|
|
23
|
+
return {
|
|
24
|
+
projectId: input.projectId,
|
|
25
|
+
environment,
|
|
26
|
+
issuer: input.issuer
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function parseEnvironment(value) {
|
|
30
|
+
if (!value || value === "development") return "development";
|
|
31
|
+
if (value === "preview" || value === "production") return value;
|
|
32
|
+
throw new ZitadelRuntimeError("E_ZITADEL_CONFIG", `Unsupported ZITADEL_ENVIRONMENT "${value}".`);
|
|
33
|
+
}
|
|
34
|
+
function currentEnv() {
|
|
35
|
+
return globalThis.process?.env ?? {};
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { HOP_BY_HOP, INTERNAL_HEADERS, JWKS_MIN_REFRESH_MS, JWKS_TTL_MS, SERVER_SESSION_COOKIE, SESSION_COOKIE_CACHE_CONTROL, SESSION_PROJECT_MISMATCH, ZitadelRuntimeError, base64UrlDecode, checkOpaqueSessionToken, clearJwksCache, clearSessionCheckCache, decodeJwt, filterResponseHeaders, isJwtShaped, jwtNamesProject, matchesRoutes, proxySessionCookies, requiresProjectSecret, resolveSession, resolveZitadelRuntime, resolveZitadelRuntimeEnv, serializeSessionCookie, sessionCookieName, validateOpaqueSessionToken, verifyJwt };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["scope"],"sources":["../src/index.ts"],"sourcesContent":["export type {\n NextgenSession,\n AuthState,\n UnauthState,\n AuthResult,\n NextgenMiddlewareOptions,\n} from \"./middleware.js\";\nexport {\n HOP_BY_HOP,\n INTERNAL_HEADERS,\n matchesRoutes,\n filterResponseHeaders,\n requiresProjectSecret,\n checkOpaqueSessionToken,\n validateOpaqueSessionToken,\n jwtNamesProject,\n resolveSession,\n SESSION_PROJECT_MISMATCH,\n SERVER_SESSION_COOKIE,\n sessionCookieName,\n serializeSessionCookie,\n proxySessionCookies,\n clearSessionCheckCache,\n SESSION_COOKIE_CACHE_CONTROL,\n} from \"./middleware.js\";\nexport type {\n OpaqueSession,\n OpaqueSessionCheck,\n SessionRequest,\n SessionCookie,\n ResolveSessionOptions,\n ResolvedSession,\n ProxiedRequest,\n ProxiedAnswer,\n ProxiedSessionCookies,\n ProxyUpstream,\n BrowserRequest,\n} from \"./middleware.js\";\nexport {\n verifyJwt,\n decodeJwt,\n isJwtShaped,\n base64UrlDecode,\n JWKS_TTL_MS,\n JWKS_MIN_REFRESH_MS,\n clearJwksCache,\n} from \"./jwt.js\";\nexport type { JwtPayload, JwtHeader, DecodedJwt, VerifyJwtOptions } from \"./jwt.js\";\n\nexport type ZitadelEnvironment = \"development\" | \"preview\" | \"production\";\n\nexport type ZitadelRuntime = {\n projectId: string;\n environment: ZitadelEnvironment;\n issuer?: string;\n};\n\nexport type ZitadelRuntimeInput = {\n projectId?: string;\n environment?: ZitadelEnvironment;\n issuer?: string;\n};\n\nexport class ZitadelRuntimeError extends Error {\n readonly code: string;\n\n constructor(code: string, message: string) {\n super(message);\n this.name = \"ZitadelRuntimeError\";\n this.code = code;\n }\n}\n\nexport function resolveZitadelRuntimeEnv(\n env: Record<string, string | undefined> = currentEnv(),\n): ZitadelRuntime {\n return resolveZitadelRuntime({\n projectId: env.NEXT_PUBLIC_ZITADEL_PROJECT_ID ?? env.ZITADEL_PROJECT_ID,\n environment: parseEnvironment(env.NEXT_PUBLIC_ZITADEL_ENVIRONMENT ?? env.ZITADEL_ENVIRONMENT),\n issuer: env.NEXT_PUBLIC_ZITADEL_ISSUER ?? env.ZITADEL_ISSUER,\n });\n}\n\nexport function resolveZitadelRuntime(input: ZitadelRuntimeInput): ZitadelRuntime {\n const environment = input.environment ?? \"development\";\n if (!input.projectId) {\n throw new ZitadelRuntimeError(\n \"E_ZITADEL_CONFIG\",\n \"ZITADEL_PROJECT_ID is required for Zitadel runtime.\",\n );\n }\n if (environment === \"production\" && !input.issuer) {\n throw new ZitadelRuntimeError(\"E_ZITADEL_CONFIG\", \"ZITADEL_ISSUER is required in production.\");\n }\n return {\n projectId: input.projectId,\n environment,\n issuer: input.issuer,\n };\n}\n\nfunction parseEnvironment(value: string | undefined): ZitadelEnvironment {\n if (!value || value === \"development\") return \"development\";\n if (value === \"preview\" || value === \"production\") return value;\n throw new ZitadelRuntimeError(\"E_ZITADEL_CONFIG\", `Unsupported ZITADEL_ENVIRONMENT \"${value}\".`);\n}\n\nfunction currentEnv(): Record<string, string | undefined> {\n const scope = globalThis as { process?: { env?: Record<string, string | undefined> } };\n return scope.process?.env ?? {};\n}\n"],"mappings":";;;AA+DA,IAAa,sBAAb,cAAyC,MAAM;CAC7C;CAEA,YAAY,MAAc,SAAiB;EACzC,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,yBACd,MAA0C,WAAW,GACrC;CAChB,OAAO,sBAAsB;EAC3B,WAAW,IAAI,kCAAkC,IAAI;EACrD,aAAa,iBAAiB,IAAI,mCAAmC,IAAI,mBAAmB;EAC5F,QAAQ,IAAI,8BAA8B,IAAI;CAChD,CAAC;AACH;AAEA,SAAgB,sBAAsB,OAA4C;CAChF,MAAM,cAAc,MAAM,eAAe;CACzC,IAAI,CAAC,MAAM,WACT,MAAM,IAAI,oBACR,oBACA,qDACF;CAEF,IAAI,gBAAgB,gBAAgB,CAAC,MAAM,QACzC,MAAM,IAAI,oBAAoB,oBAAoB,2CAA2C;CAE/F,OAAO;EACL,WAAW,MAAM;EACjB;EACA,QAAQ,MAAM;CAChB;AACF;AAEA,SAAS,iBAAiB,OAA+C;CACvE,IAAI,CAAC,SAAS,UAAU,eAAe,OAAO;CAC9C,IAAI,UAAU,aAAa,UAAU,cAAc,OAAO;CAC1D,MAAM,IAAI,oBAAoB,oBAAoB,oCAAoC,MAAM,GAAG;AACjG;AAEA,SAAS,aAAiD;CAExD,OAAOA,WAAM,SAAS,OAAO,CAAC;AAChC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//#region src/jwt.d.ts
|
|
1
2
|
/**
|
|
2
3
|
* @file lib/jwt.ts
|
|
3
4
|
*
|
|
@@ -81,17 +82,16 @@
|
|
|
81
82
|
*
|
|
82
83
|
* ## JWKS caching
|
|
83
84
|
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
85
|
+
* Each issuer's whole JWKS document is cached in-process, keyed by its JWKS URI,
|
|
86
|
+
* for {@link JWKS_TTL_MS} milliseconds (default 5 minutes); keys are imported
|
|
87
|
+
* once and reused. Keying by the URI keeps two issuers that publish the same
|
|
88
|
+
* `kid` apart.
|
|
87
89
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* A cache miss — on first use or after TTL expiry — triggers a fresh fetch
|
|
94
|
-
* from `{issuerUrl}/auth/keys`.
|
|
90
|
+
* The document is fetched from `{issuerUrl}/auth/keys` on first use and after
|
|
91
|
+
* the TTL. A token naming a `kid` the cached document lacks refreshes it — how
|
|
92
|
+
* a rotated key is picked up — but no sooner than {@link JWKS_MIN_REFRESH_MS}
|
|
93
|
+
* after the last fetch. The `kid` header is attacker-controlled, so a per-`kid`
|
|
94
|
+
* cache would let any request with a made-up `kid` cost a JWKS fetch.
|
|
95
95
|
*
|
|
96
96
|
* > **Note (serverless):** the cache is module-level. In serverless environments
|
|
97
97
|
* > each cold start gets a fresh cache; warm instances share it across requests.
|
|
@@ -106,8 +106,15 @@
|
|
|
106
106
|
* The previous per-SDK copies (one using `atob`, one using `Buffer`) have
|
|
107
107
|
* been consolidated here.
|
|
108
108
|
*/
|
|
109
|
-
/** How long a cached JWKS
|
|
109
|
+
/** How long a cached JWKS document is considered fresh before re-fetching. */
|
|
110
110
|
export declare const JWKS_TTL_MS: number;
|
|
111
|
+
/**
|
|
112
|
+
* The least time between two JWKS fetches for one issuer. A token naming a
|
|
113
|
+
* `kid` the cached document lacks triggers a refresh — that is how a rotated
|
|
114
|
+
* key is picked up — but at most this often, so a stream of tokens with random
|
|
115
|
+
* `kid` values cannot turn every request into a JWKS fetch.
|
|
116
|
+
*/
|
|
117
|
+
export declare const JWKS_MIN_REFRESH_MS: number;
|
|
111
118
|
/**
|
|
112
119
|
* The subset of standard JWT payload claims this module reads or validates.
|
|
113
120
|
*
|
|
@@ -117,26 +124,26 @@ export declare const JWKS_TTL_MS: number;
|
|
|
117
124
|
* Refer to the module-level documentation for the full verification matrix.
|
|
118
125
|
*/
|
|
119
126
|
export interface JwtPayload {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
/** Issuer (`iss`) — identifies the auth server that created the token. */
|
|
128
|
+
readonly iss?: string;
|
|
129
|
+
/** Subject (`sub`) — the user's unique identifier. */
|
|
130
|
+
readonly sub?: string;
|
|
131
|
+
/**
|
|
132
|
+
* Audience (`aud`) — the intended recipient(s).
|
|
133
|
+
* A single string or an array of strings per RFC 7519 §4.1.3.
|
|
134
|
+
*/
|
|
135
|
+
readonly aud?: string | readonly string[];
|
|
136
|
+
/** Expiration time (`exp`) — seconds since Unix epoch. */
|
|
137
|
+
readonly exp?: number;
|
|
138
|
+
/** Not before (`nbf`) — seconds since Unix epoch. */
|
|
139
|
+
readonly nbf?: number;
|
|
140
|
+
/** Issued at (`iat`) — seconds since Unix epoch. */
|
|
141
|
+
readonly iat?: number;
|
|
142
|
+
/** User's email address (custom claim). */
|
|
143
|
+
readonly email?: string;
|
|
144
|
+
/** User's display name (custom claim). */
|
|
145
|
+
readonly name?: string;
|
|
146
|
+
readonly [key: string]: unknown;
|
|
140
147
|
}
|
|
141
148
|
/**
|
|
142
149
|
* The typed representation of a JWT header.
|
|
@@ -145,13 +152,13 @@ export interface JwtPayload {
|
|
|
145
152
|
* header parameters are preserved under the index signature.
|
|
146
153
|
*/
|
|
147
154
|
export interface JwtHeader {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
/** Signing algorithm (`alg`) — e.g. `"RS256"`, `"ES256"`. */
|
|
156
|
+
readonly alg?: string;
|
|
157
|
+
/** Token type (`typ`) — e.g. `"JWT"`, `"at+JWT"`. */
|
|
158
|
+
readonly typ?: string;
|
|
159
|
+
/** Key ID (`kid`) — selects the matching public key from the JWKS. */
|
|
160
|
+
readonly kid?: string;
|
|
161
|
+
readonly [key: string]: unknown;
|
|
155
162
|
}
|
|
156
163
|
/**
|
|
157
164
|
* The decoded, unverified parts of a compact-serialised JWT.
|
|
@@ -161,8 +168,8 @@ export interface JwtHeader {
|
|
|
161
168
|
* raw data into the next step.
|
|
162
169
|
*/
|
|
163
170
|
export interface DecodedJwt {
|
|
164
|
-
|
|
165
|
-
|
|
171
|
+
readonly header: JwtHeader;
|
|
172
|
+
readonly payload: JwtPayload;
|
|
166
173
|
}
|
|
167
174
|
/**
|
|
168
175
|
* Options for {@link verifyJwt}.
|
|
@@ -170,49 +177,55 @@ export interface DecodedJwt {
|
|
|
170
177
|
* All fields are `readonly` — this object is never mutated after construction.
|
|
171
178
|
*/
|
|
172
179
|
export interface VerifyJwtOptions {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Base URL of the auth backend.
|
|
182
|
+
*
|
|
183
|
+
* Serves two purposes:
|
|
184
|
+
* 1. Determines the JWKS endpoint: `{issuerUrl}/auth/keys`
|
|
185
|
+
* 2. Is compared against the `iss` claim when present in the token.
|
|
186
|
+
*/
|
|
187
|
+
readonly issuerUrl: string;
|
|
188
|
+
/**
|
|
189
|
+
* Restrict accepted `alg` header values. Tokens whose algorithm is not in
|
|
190
|
+
* this list are rejected before the JWKS is fetched.
|
|
191
|
+
* When `undefined`, all algorithms are accepted (the middleware layer
|
|
192
|
+
* defaults to `["RS256", "ES256"]` before passing options here).
|
|
193
|
+
*/
|
|
194
|
+
readonly allowedAlgorithms?: readonly string[];
|
|
195
|
+
/**
|
|
196
|
+
* Clock-skew tolerance in milliseconds, applied symmetrically to `exp`,
|
|
197
|
+
* `nbf`, and `iat` checks to accommodate minor differences between clocks.
|
|
198
|
+
*/
|
|
199
|
+
readonly clockSkewMs: number;
|
|
200
|
+
/**
|
|
201
|
+
* Expected `aud` claim value(s). When `undefined`, audience is not
|
|
202
|
+
* validated. Provide a string or array when the auth server sets `aud`
|
|
203
|
+
* and you want to ensure the token was issued specifically for your app.
|
|
204
|
+
*/
|
|
205
|
+
readonly audience?: string | readonly string[];
|
|
206
|
+
/**
|
|
207
|
+
* Accepted `typ` header values (case-insensitive).
|
|
208
|
+
*
|
|
209
|
+
* Guards against using a refresh token or ID token where an access token
|
|
210
|
+
* is expected. Set to an empty array (`[]`) to disable this check.
|
|
211
|
+
*
|
|
212
|
+
* @default ["JWT", "at+JWT"]
|
|
213
|
+
*/
|
|
214
|
+
readonly allowedTokenTypes: readonly string[];
|
|
215
|
+
/**
|
|
216
|
+
* Timeout in milliseconds for JWKS endpoint requests.
|
|
217
|
+
* Requests that do not complete within this window are aborted and the
|
|
218
|
+
* token is rejected (function returns `null`).
|
|
219
|
+
* @default 5000
|
|
220
|
+
*/
|
|
221
|
+
readonly jwksTimeoutMs?: number;
|
|
215
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Forgets every cached JWKS document, so the next verification fetches the key
|
|
225
|
+
* set anew. Use it to drop a compromised key at once instead of waiting out
|
|
226
|
+
* {@link JWKS_TTL_MS}, and in tests that publish different keys per case.
|
|
227
|
+
*/
|
|
228
|
+
export declare function clearJwksCache(): void;
|
|
216
229
|
/**
|
|
217
230
|
* Decodes a Base64URL-encoded string into a `Uint8Array`.
|
|
218
231
|
*
|
|
@@ -236,6 +249,24 @@ export declare function base64UrlDecode(input: string): Uint8Array<ArrayBuffer>;
|
|
|
236
249
|
* @throws {TypeError} When `token` has fewer than three dot-separated segments.
|
|
237
250
|
*/
|
|
238
251
|
export declare function decodeJwt(token: string): DecodedJwt;
|
|
252
|
+
/**
|
|
253
|
+
* Returns `true` when `token` looks structurally like a signed JWT (JWS) —
|
|
254
|
+
* NOT an encrypted token (JWE).
|
|
255
|
+
*
|
|
256
|
+
* JWS compact: 3 dot-separated segments; header has `alg` but NOT `enc`.
|
|
257
|
+
* JWE compact: 5 dot-separated segments; header has BOTH `alg` and `enc`.
|
|
258
|
+
*
|
|
259
|
+
* The Zitadel backend issues JWE tokens (AES-256-GCM via go-jose
|
|
260
|
+
* `A256GCMKW/A256GCM`), whose compact form is
|
|
261
|
+
* `header.encrypted_key.iv.ciphertext.tag`. The header decodes to JSON with
|
|
262
|
+
* `"alg":"A256GCMKW","enc":"A256GCM"`. Checking for the absence of `enc`
|
|
263
|
+
* correctly identifies signed JWTs without false-positives on JWE tokens.
|
|
264
|
+
*
|
|
265
|
+
* This is a structural check only — not a security check. Use it to route a
|
|
266
|
+
* token to the correct validation path ({@link verifyJwt} for JWS, a backend
|
|
267
|
+
* session lookup for everything else), never to trust one.
|
|
268
|
+
*/
|
|
269
|
+
export declare function isJwtShaped(token: string): boolean;
|
|
239
270
|
/**
|
|
240
271
|
* Verifies a compact-serialised JWT and returns its validated payload.
|
|
241
272
|
*
|
|
@@ -261,4 +292,5 @@ export declare function decodeJwt(token: string): DecodedJwt;
|
|
|
261
292
|
* @returns The verified {@link JwtPayload}, or `null` if verification fails.
|
|
262
293
|
*/
|
|
263
294
|
export declare function verifyJwt(token: string, opts: VerifyJwtOptions): Promise<JwtPayload | null>;
|
|
264
|
-
//#
|
|
295
|
+
//#endregion
|
|
296
|
+
//# sourceMappingURL=jwt.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.d.mts","names":[],"sources":["../src/jwt.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAiHa;;;;;;;qBAQA;;;;;;;;;iBAYI;;WAEN;;WAEA;;;;;WAKA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;YACC;;;;;;;;iBASK;;WAEN;;WAEA;;WAEA;YACC;;;;;;;;;iBAUK;WACN,QAAQ;WACR,SAAS;;;;;;;iBAuBH;;;;;;;;WAQN;;;;;;;WAQA;;;;;WAMA;;;;;;WAOA;;;;;;;;;WAUA;;;;;;;WAQA;;;;;;;wBAkCK;;;;;;;;;;wBAqBA,gBAAgB,gBAAgB,WAAW;;;;;;;;;;;;;wBA2C3C,UAAU,gBAAgB;;;;;;;;;;;;;;;;;;wBAiC1B,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;wBA8ON,UAAU,eAAe,MAAM,mBAAmB,QAAQ"}
|