@saurbit/oauth2-jwt 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 saurbit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # @saurbit/oauth2-jwt
2
+
3
+ JWT utilities and JWKS authority for [`@saurbit/oauth2`](https://jsr.io/@saurbit/oauth2). Wraps
4
+ [jose](https://github.com/panva/jose) to provide ready-made implementations of the JWT-related
5
+ interfaces required by `@saurbit/oauth2`.
6
+
7
+ ## Installation
8
+
9
+ **Node.js / Bun**
10
+
11
+ ```sh
12
+ npm install @saurbit/oauth2-jwt
13
+ # or
14
+ yarn add @saurbit/oauth2-jwt
15
+ # or
16
+ pnpm add @saurbit/oauth2-jwt
17
+ # or
18
+ bun add @saurbit/oauth2-jwt
19
+ ```
20
+
21
+ **Deno / JSR**
22
+
23
+ ```sh
24
+ deno add jsr:@saurbit/oauth2-jwt
25
+ ```
26
+
27
+ ## JWT Utilities
28
+
29
+ `@saurbit/oauth2-jwt` provides three ready-made functions that satisfy the `JwtVerify`, `JwtDecode`,
30
+ and `JwkVerify` interfaces expected by `@saurbit/oauth2`.
31
+
32
+ ### `verifyJwt` and `decodeJwt`
33
+
34
+ Used by the `ClientSecretJwt` and `PrivateKeyJwt` client authentication methods.
35
+
36
+ **`ClientSecretJwt`**
37
+
38
+ ```ts
39
+ import { ClientSecretJwt } from "@saurbit/oauth2";
40
+ import { decodeJwt, verifyJwt } from "@saurbit/oauth2-jwt";
41
+
42
+ const clientSecretJwt = new ClientSecretJwt(decodeJwt, verifyJwt);
43
+ ```
44
+
45
+ **`PrivateKeyJwt`**
46
+
47
+ ```ts
48
+ import { PrivateKeyJwt } from "@saurbit/oauth2";
49
+ import { decodeJwt, verifyJwt } from "@saurbit/oauth2-jwt";
50
+
51
+ const privateKeyJwt = new PrivateKeyJwt(decodeJwt, verifyJwt);
52
+ ```
53
+
54
+ ### `verifyJwk`
55
+
56
+ Used by `DPoPTokenType` for Demonstration of Proof-of-Possession token validation.
57
+
58
+ ```ts
59
+ import { createInMemoryReplayStore, DPoPTokenType } from "@saurbit/oauth2";
60
+ import { verifyJwk } from "@saurbit/oauth2-jwt";
61
+
62
+ const dpop = new DPoPTokenType(verifyJwk, createInMemoryReplayStore());
63
+ ```
64
+
65
+ ## JWKS Authority
66
+
67
+ `JoseJwksAuthority` manages RS256 signing key pairs, signs and verifies JWTs, and returns the JWKS
68
+ endpoint payload. Keys are stored in a `JwksKeyStore` and generated automatically on first use.
69
+
70
+ ### Setup
71
+
72
+ ```ts
73
+ import { createInMemoryKeyStore, JoseJwksAuthority } from "@saurbit/oauth2-jwt";
74
+
75
+ const store = createInMemoryKeyStore();
76
+ const authority = new JoseJwksAuthority(store, 8.64e+6); // public keys valid for 100 days
77
+ ```
78
+
79
+ ### Sign a JWT
80
+
81
+ ```ts
82
+ const { token } = await authority.sign({
83
+ sub: "user-123",
84
+ iss: "https://auth.example.com",
85
+ aud: "my-client",
86
+ iat: Math.floor(Date.now() / 1000),
87
+ exp: Math.floor(Date.now() / 1000) + 3600,
88
+ jti: crypto.randomUUID(),
89
+ });
90
+ ```
91
+
92
+ ### Verify a JWT
93
+
94
+ ```ts
95
+ const payload = await authority.verify(token);
96
+ console.log(payload.sub); // "user-123"
97
+ ```
98
+
99
+ ### JWKS endpoint
100
+
101
+ `getJwksEndpointResponse()` returns the set of current public keys in JWKS format, ready to be
102
+ served at a well-known endpoint (e.g. `/.well-known/jwks.json`).
103
+
104
+ ```ts
105
+ // Example with Hono
106
+ app.get("/jwks", async (c) => {
107
+ return c.json(await authority.getJwksEndpointResponse());
108
+ });
109
+ ```
110
+
111
+ The response has the shape `{ keys: RawKey[] }` and is safe to return directly as JSON.
112
+
113
+ ## Key Rotation
114
+
115
+ `JwksRotator` manages scheduled key rotation. It compares the current time against the last rotation
116
+ timestamp and generates a new key pair only when the configured interval has elapsed.
117
+
118
+ Call `checkAndRotateKeys()` at service startup and/or on a recurring schedule (e.g. every hour). If
119
+ a rotation is due a new key pair is generated; otherwise the call is a no-op. During rotation the
120
+ previous public key remains available in the JWKS until its TTL expires, so in-flight tokens
121
+ continue to verify correctly.
122
+
123
+ ### Setup
124
+
125
+ ```ts
126
+ import { createInMemoryKeyStore, JoseJwksAuthority, JwksRotator } from "@saurbit/oauth2-jwt";
127
+
128
+ const store = createInMemoryKeyStore();
129
+ const authority = new JoseJwksAuthority(store, 8.64e+6); // 100 days key TTL
130
+
131
+ const rotator = new JwksRotator({
132
+ keyGenerator: authority,
133
+ rotatorKeyStore: store,
134
+ rotationIntervalMs: 7.884e9, // 91 days
135
+ });
136
+ ```
137
+
138
+ ### Usage
139
+
140
+ ```ts
141
+ // At startup
142
+ await rotator.checkAndRotateKeys();
143
+
144
+ // Or on a recurring schedule
145
+ setInterval(async () => {
146
+ await rotator.checkAndRotateKeys();
147
+ }, 60 * 60 * 1000); // every hour
148
+ ```
149
+
150
+ ## License
151
+
152
+ [MIT](./LICENSE)
@@ -0,0 +1,90 @@
1
+ import { JWTPayload } from "jose";
2
+ import { JwksKeyStore, JwtAuthority, RawKey, RSA } from "./types.js";
3
+ /**
4
+ * JWT authority backed by [jose](https://github.com/panva/jose).
5
+ *
6
+ * Manages RS256 signing key pairs, signs and verifies JWTs, and returns the
7
+ * JWKS endpoint payload. Keys are stored in a {@link JwksKeyStore} and are
8
+ * generated automatically on first use.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { createInMemoryKeyStore, JoseJwksAuthority } from "@saurbit/oauth2-jwt";
13
+ *
14
+ * const store = createInMemoryKeyStore();
15
+ * const authority = new JoseJwksAuthority(store, 86400); // keys valid for 24 h
16
+ *
17
+ * const { token } = await authority.sign({ sub: "user-123", exp: Math.floor(Date.now() / 1000) + 3600 });
18
+ * const payload = await authority.verify(token);
19
+ * ```
20
+ */
21
+ export declare class JoseJwksAuthority implements JwtAuthority {
22
+ #private;
23
+ /**
24
+ * @param store The key store used to manage JWKS keys.
25
+ * @param ttl Time-to-live for the keys in seconds.
26
+ */
27
+ constructor(store: JwksKeyStore, ttl?: number);
28
+ /**
29
+ * Retrieves the current set of public keys from the key store.
30
+ * @returns An object containing the array of public keys.
31
+ */
32
+ getPublicKeys(): Promise<{
33
+ keys: RawKey[];
34
+ }>;
35
+ /**
36
+ * Get current kid for observability/debugging
37
+ */
38
+ getCurrentKid(): Promise<string | undefined>;
39
+ /**
40
+ * Helps implement the JWKS endpoint by returning the current set of public keys in the expected format.
41
+ * @returns The current set of public keys in the JWKS format.
42
+ */
43
+ getJwksEndpointResponse(): Promise<{
44
+ keys: RawKey[];
45
+ }>;
46
+ /**
47
+ * Retrieves the public key corresponding to the given "kid" from the JWKS.
48
+ * @param kid The key ID of the desired public key.
49
+ * @returns The RSA public key if found, otherwise undefined.
50
+ */
51
+ getPublicKey(kid: string): Promise<RSA | undefined>;
52
+ /**
53
+ * Generates a new RSA key pair, stores it in the key store with the appropriate metadata
54
+ * (kid, alg, use), and ensures that the public key is available for JWKS exposure.
55
+ */
56
+ generateKeyPair(): Promise<void>;
57
+ /**
58
+ * Signs the given payload as a JWT using the current private key.
59
+ * The "kid" of the signing key is included in the JWT header to allow verifiers
60
+ * to select the correct public key from the JWKS for verification.
61
+ * This method ensures that the JWT is signed with a valid key and that
62
+ * the corresponding public key is available in the JWKS for future verification.
63
+ * @param payload The payload to be signed as a JWT.
64
+ * @returns An object containing the signed JWT and the key ID used for signing.
65
+ */
66
+ sign(payload: JWTPayload): Promise<{
67
+ token: string;
68
+ kid: string;
69
+ }>;
70
+ /**
71
+ * Verifies the given JWT using the appropriate public key from the JWKS based on the "kid" in the JWT header.
72
+ * It performs additional checks to ensure the integrity and expected structure of the JWT,
73
+ * such as validating the algorithm and ensuring no unexpected JWK is present in the header.
74
+ * @param token
75
+ * @returns
76
+ */
77
+ verify<P extends JWTPayload = JWTPayload>(token: string): Promise<P>;
78
+ /**
79
+ * Signs multiple payloads with the same private key, returning an array of tokens and their corresponding kids.
80
+ * This is useful for batch operations where multiple JWTs need to be issued at once,
81
+ * ensuring they all use the same key and can be verified against the same public key in the JWKS.
82
+ * @param payloads The array of payloads to be signed as JWTs.
83
+ * @returns An array of objects containing the signed JWTs and their corresponding key IDs.
84
+ */
85
+ signMany(payloads: JWTPayload[]): Promise<{
86
+ token: string;
87
+ kid: string;
88
+ }[]>;
89
+ }
90
+ //# sourceMappingURL=jose_jwks_authority.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jose_jwks_authority.d.ts","sourceRoot":"","sources":["../src/jose_jwks_authority.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,UAAU,EAGX,MAAM,MAAM,CAAC;AACd,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AA0BrE;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,iBAAkB,YAAW,YAAY;;IAOpD;;;OAGG;gBACS,KAAK,EAAE,YAAY,EAAE,GAAG,GAAE,MAAc;IAsCpD;;;OAGG;IACG,aAAa,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IASlD;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAKlD;;;OAGG;IACH,uBAAuB,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAItD;;;;OAIG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC;IAQzD;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAItC;;;;;;;;OAQG;IACG,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAoBxE;;;;;;OAMG;IACG,MAAM,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IA+B1E;;;;;;OAMG;IACG,QAAQ,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAyBlF"}
@@ -0,0 +1,214 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _JoseJwksAuthority_instances, _JoseJwksAuthority_store, _JoseJwksAuthority_ttl, _JoseJwksAuthority_generateAndStoreKeyPair, _JoseJwksAuthority_getPrivateKey;
13
+ import { exportJWK, generateKeyPair, importJWK, jwtVerify, SignJWT, } from "jose";
14
+ function base64UrlToString(b64url) {
15
+ // Fast path for Node/Bun
16
+ if (typeof Buffer !== "undefined") {
17
+ return Buffer.from(b64url, "base64url").toString();
18
+ }
19
+ // Convert Base64URL → standard Base64
20
+ const base64 = b64url.replace(/-/g, "+").replace(/_/g, "/");
21
+ // Add padding if missing
22
+ const padded = base64.padEnd(base64.length + (4 - (base64.length % 4)) % 4, "=");
23
+ // Decode
24
+ const binary = atob(padded);
25
+ const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
26
+ return new TextDecoder().decode(bytes);
27
+ }
28
+ /**
29
+ * JWT authority backed by [jose](https://github.com/panva/jose).
30
+ *
31
+ * Manages RS256 signing key pairs, signs and verifies JWTs, and returns the
32
+ * JWKS endpoint payload. Keys are stored in a {@link JwksKeyStore} and are
33
+ * generated automatically on first use.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import { createInMemoryKeyStore, JoseJwksAuthority } from "@saurbit/oauth2-jwt";
38
+ *
39
+ * const store = createInMemoryKeyStore();
40
+ * const authority = new JoseJwksAuthority(store, 86400); // keys valid for 24 h
41
+ *
42
+ * const { token } = await authority.sign({ sub: "user-123", exp: Math.floor(Date.now() / 1000) + 3600 });
43
+ * const payload = await authority.verify(token);
44
+ * ```
45
+ */
46
+ export class JoseJwksAuthority {
47
+ /**
48
+ * @param store The key store used to manage JWKS keys.
49
+ * @param ttl Time-to-live for the keys in seconds.
50
+ */
51
+ constructor(store, ttl = 36000) {
52
+ _JoseJwksAuthority_instances.add(this);
53
+ _JoseJwksAuthority_store.set(this, void 0);
54
+ /**
55
+ * seconds
56
+ */
57
+ _JoseJwksAuthority_ttl.set(this, void 0);
58
+ __classPrivateFieldSet(this, _JoseJwksAuthority_store, store, "f");
59
+ __classPrivateFieldSet(this, _JoseJwksAuthority_ttl, ttl, "f");
60
+ }
61
+ /**
62
+ * Retrieves the current set of public keys from the key store.
63
+ * @returns An object containing the array of public keys.
64
+ */
65
+ async getPublicKeys() {
66
+ const publicJwks = await __classPrivateFieldGet(this, _JoseJwksAuthority_store, "f").getPublicKeys();
67
+ const json = { keys: publicJwks };
68
+ if (json && "keys" in json && Array.isArray(json.keys)) {
69
+ json.keys = [...json.keys].reverse();
70
+ }
71
+ return json;
72
+ }
73
+ /**
74
+ * Get current kid for observability/debugging
75
+ */
76
+ async getCurrentKid() {
77
+ const key = await __classPrivateFieldGet(this, _JoseJwksAuthority_instances, "m", _JoseJwksAuthority_getPrivateKey).call(this);
78
+ return key?.kid;
79
+ }
80
+ /**
81
+ * Helps implement the JWKS endpoint by returning the current set of public keys in the expected format.
82
+ * @returns The current set of public keys in the JWKS format.
83
+ */
84
+ getJwksEndpointResponse() {
85
+ return this.getPublicKeys();
86
+ }
87
+ /**
88
+ * Retrieves the public key corresponding to the given "kid" from the JWKS.
89
+ * @param kid The key ID of the desired public key.
90
+ * @returns The RSA public key if found, otherwise undefined.
91
+ */
92
+ async getPublicKey(kid) {
93
+ const keyStore = await this.getPublicKeys();
94
+ const key = keyStore.keys.find((k) => k.kid === kid);
95
+ if (!key)
96
+ return undefined;
97
+ if (key.kty !== "RSA")
98
+ return undefined;
99
+ return key;
100
+ }
101
+ /**
102
+ * Generates a new RSA key pair, stores it in the key store with the appropriate metadata
103
+ * (kid, alg, use), and ensures that the public key is available for JWKS exposure.
104
+ */
105
+ async generateKeyPair() {
106
+ await __classPrivateFieldGet(this, _JoseJwksAuthority_instances, "m", _JoseJwksAuthority_generateAndStoreKeyPair).call(this);
107
+ }
108
+ /**
109
+ * Signs the given payload as a JWT using the current private key.
110
+ * The "kid" of the signing key is included in the JWT header to allow verifiers
111
+ * to select the correct public key from the JWKS for verification.
112
+ * This method ensures that the JWT is signed with a valid key and that
113
+ * the corresponding public key is available in the JWKS for future verification.
114
+ * @param payload The payload to be signed as a JWT.
115
+ * @returns An object containing the signed JWT and the key ID used for signing.
116
+ */
117
+ async sign(payload) {
118
+ const key = await __classPrivateFieldGet(this, _JoseJwksAuthority_instances, "m", _JoseJwksAuthority_getPrivateKey).call(this);
119
+ if (key.kty !== "RSA") {
120
+ throw new Error("Invalid key type, expected RSA");
121
+ }
122
+ if (!key.kid) {
123
+ throw new Error('Key is missing "kid" property');
124
+ }
125
+ const privateKey = await importJWK(key, "RS256");
126
+ const token = await new SignJWT(payload)
127
+ .setProtectedHeader({ typ: "jwt", alg: "RS256", kid: key.kid })
128
+ .sign(privateKey);
129
+ return { token, kid: key.kid };
130
+ }
131
+ /**
132
+ * Verifies the given JWT using the appropriate public key from the JWKS based on the "kid" in the JWT header.
133
+ * It performs additional checks to ensure the integrity and expected structure of the JWT,
134
+ * such as validating the algorithm and ensuring no unexpected JWK is present in the header.
135
+ * @param token
136
+ * @returns
137
+ */
138
+ async verify(token) {
139
+ const [header] = token.split(".");
140
+ if (!header)
141
+ throw new Error("Invalid JWT format");
142
+ const parsedHeader = JSON.parse(base64UrlToString(header));
143
+ const kid = parsedHeader.kid;
144
+ if (!kid || typeof kid !== "string")
145
+ throw new Error('Invalid or missing "kid" in JWT header');
146
+ const key = await this.getPublicKey(kid);
147
+ if (!key)
148
+ throw new Error(`Key with kid "${kid}" not found`);
149
+ const { payload, protectedHeader } = await jwtVerify(token, key);
150
+ if (protectedHeader.alg !== "RS256") {
151
+ throw new Error(`Unexpected algorithm: ${protectedHeader.alg}`);
152
+ }
153
+ // additional checks hardening security
154
+ if ("jwk" in protectedHeader) {
155
+ throw new Error("Unexpected JWK in header - potential forgery attempt");
156
+ }
157
+ if (protectedHeader.typ && protectedHeader.typ.toLowerCase() !== "jwt") {
158
+ throw new Error(`Unexpected typ: ${protectedHeader.typ}`);
159
+ }
160
+ return payload;
161
+ }
162
+ /**
163
+ * Signs multiple payloads with the same private key, returning an array of tokens and their corresponding kids.
164
+ * This is useful for batch operations where multiple JWTs need to be issued at once,
165
+ * ensuring they all use the same key and can be verified against the same public key in the JWKS.
166
+ * @param payloads The array of payloads to be signed as JWTs.
167
+ * @returns An array of objects containing the signed JWTs and their corresponding key IDs.
168
+ */
169
+ async signMany(payloads) {
170
+ const key = await __classPrivateFieldGet(this, _JoseJwksAuthority_instances, "m", _JoseJwksAuthority_getPrivateKey).call(this);
171
+ if (key.kty !== "RSA") {
172
+ throw new Error("Invalid key type, expected RSA");
173
+ }
174
+ if (!key.kid) {
175
+ throw new Error('Key is missing "kid" property');
176
+ }
177
+ const privateKey = await importJWK(key, "RS256");
178
+ const result = [];
179
+ for (const payload of payloads) {
180
+ const token = await new SignJWT(payload)
181
+ .setProtectedHeader({ typ: "jwt", alg: "RS256", kid: key.kid })
182
+ .sign(privateKey);
183
+ result.push({ token, kid: key.kid });
184
+ }
185
+ return result;
186
+ }
187
+ }
188
+ _JoseJwksAuthority_store = new WeakMap(), _JoseJwksAuthority_ttl = new WeakMap(), _JoseJwksAuthority_instances = new WeakSet(), _JoseJwksAuthority_generateAndStoreKeyPair = async function _JoseJwksAuthority_generateAndStoreKeyPair() {
189
+ const { publicKey, privateKey } = await generateKeyPair("RS256", {
190
+ modulusLength: 2048,
191
+ extractable: true,
192
+ });
193
+ // Convert to JWK format and add necessary properties
194
+ const privateJwk = await exportJWK(privateKey);
195
+ const publicJwk = await exportJWK(publicKey);
196
+ const kid = crypto.randomUUID();
197
+ privateJwk.kid = kid;
198
+ privateJwk.alg = "RS256";
199
+ privateJwk.use = "sig";
200
+ publicJwk.kid = kid;
201
+ publicJwk.alg = "RS256";
202
+ publicJwk.use = "sig";
203
+ await __classPrivateFieldGet(this, _JoseJwksAuthority_store, "f").storeKeyPair(kid, privateJwk, publicJwk, __classPrivateFieldGet(this, _JoseJwksAuthority_ttl, "f"));
204
+ return { privateJwk, publicJwk };
205
+ }, _JoseJwksAuthority_getPrivateKey = async function _JoseJwksAuthority_getPrivateKey() {
206
+ const storedPrivateJwk = await __classPrivateFieldGet(this, _JoseJwksAuthority_store, "f").getPrivateKey();
207
+ if (storedPrivateJwk) {
208
+ return storedPrivateJwk;
209
+ }
210
+ else {
211
+ const { privateJwk } = await __classPrivateFieldGet(this, _JoseJwksAuthority_instances, "m", _JoseJwksAuthority_generateAndStoreKeyPair).call(this);
212
+ return privateJwk;
213
+ }
214
+ };
@@ -0,0 +1,53 @@
1
+ import type { JwksKeyStore, JwksRotationTimestampStore } from "./types.js";
2
+ /**
3
+ * In-memory implementation of {@link JwksKeyStore} and {@link JwksRotationTimestampStore}.
4
+ *
5
+ * Suitable for single-process deployments and testing. State is not shared
6
+ * across processes and is lost on restart.
7
+ *
8
+ * Use {@link createInMemoryKeyStore} as a convenience factory.
9
+ */
10
+ export declare class InMemoryKeyStore implements JwksKeyStore, JwksRotationTimestampStore {
11
+ private privateKey?;
12
+ private publicKeys;
13
+ private lastRotation;
14
+ /**
15
+ * Stores the current signing key pair. The private key replaces the existing one;
16
+ * the public key is appended to the list with a computed expiry based on `ttl`.
17
+ * @param _kid - The key ID (unused by this implementation).
18
+ * @param privateKey - The private key object to store as the active signing key.
19
+ * @param publicKey - The public key object to expose in the JWKS.
20
+ * @param ttl - Time-to-live in seconds for the public key.
21
+ */
22
+ storeKeyPair(_kid: string, privateKey: object, publicKey: object, ttl: number): Promise<void>;
23
+ /**
24
+ * Retrieves the current private signing key.
25
+ * @returns The private key object, or `undefined` if no key has been stored yet.
26
+ */
27
+ getPrivateKey(): Promise<object | undefined>;
28
+ /**
29
+ * Retrieves all public keys that have not yet expired.
30
+ * Expired keys are automatically pruned from the in-memory list on each call.
31
+ * @returns An array of non-expired public key objects.
32
+ */
33
+ getPublicKeys(): Promise<object[]>;
34
+ /**
35
+ * Retrieves the Unix timestamp (in milliseconds) of the last key rotation.
36
+ * @returns The timestamp of the last rotation, or `0` if no rotation has occurred.
37
+ */
38
+ getLastRotationTimestamp(): Promise<number>;
39
+ /**
40
+ * Stores the Unix timestamp (in milliseconds) of the most recent key rotation.
41
+ * @param msDate - The timestamp to persist.
42
+ */
43
+ setLastRotationTimestamp(msDate: number): Promise<void>;
44
+ }
45
+ /**
46
+ * Creates a new {@link InMemoryKeyStore} instance.
47
+ *
48
+ * Convenience factory for use with {@link JoseJwksAuthority} and {@link JwksRotator}.
49
+ *
50
+ * @returns A fresh in-memory key store with no keys stored.
51
+ */
52
+ export declare function createInMemoryKeyStore(): InMemoryKeyStore;
53
+ //# sourceMappingURL=jwks_key_store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwks_key_store.d.ts","sourceRoot":"","sources":["../src/jwks_key_store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAE3E;;;;;;;GAOG;AACH,qBAAa,gBAAiB,YAAW,YAAY,EAAE,0BAA0B;IAC/E,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAsC;IACxD,OAAO,CAAC,YAAY,CAAa;IAEjC;;;;;;;OAOG;IACG,YAAY,CAChB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC;IAOhB;;;OAGG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAIlD;;;;OAIG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAMxC;;;OAGG;IACG,wBAAwB,IAAI,OAAO,CAAC,MAAM,CAAC;IAIjD;;;OAGG;IACG,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAI9D;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,gBAAgB,CAEzD"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * In-memory implementation of {@link JwksKeyStore} and {@link JwksRotationTimestampStore}.
3
+ *
4
+ * Suitable for single-process deployments and testing. State is not shared
5
+ * across processes and is lost on restart.
6
+ *
7
+ * Use {@link createInMemoryKeyStore} as a convenience factory.
8
+ */
9
+ export class InMemoryKeyStore {
10
+ constructor() {
11
+ Object.defineProperty(this, "privateKey", {
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ value: void 0
16
+ });
17
+ Object.defineProperty(this, "publicKeys", {
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true,
21
+ value: []
22
+ });
23
+ Object.defineProperty(this, "lastRotation", {
24
+ enumerable: true,
25
+ configurable: true,
26
+ writable: true,
27
+ value: 0
28
+ });
29
+ }
30
+ /**
31
+ * Stores the current signing key pair. The private key replaces the existing one;
32
+ * the public key is appended to the list with a computed expiry based on `ttl`.
33
+ * @param _kid - The key ID (unused by this implementation).
34
+ * @param privateKey - The private key object to store as the active signing key.
35
+ * @param publicKey - The public key object to expose in the JWKS.
36
+ * @param ttl - Time-to-live in seconds for the public key.
37
+ */
38
+ async storeKeyPair(_kid, privateKey, publicKey, ttl) {
39
+ this.privateKey = privateKey;
40
+ const exp = Date.now() + ttl * 1000;
41
+ this.publicKeys.push({ key: publicKey, exp });
42
+ return await Promise.resolve();
43
+ }
44
+ /**
45
+ * Retrieves the current private signing key.
46
+ * @returns The private key object, or `undefined` if no key has been stored yet.
47
+ */
48
+ async getPrivateKey() {
49
+ return await Promise.resolve(this.privateKey);
50
+ }
51
+ /**
52
+ * Retrieves all public keys that have not yet expired.
53
+ * Expired keys are automatically pruned from the in-memory list on each call.
54
+ * @returns An array of non-expired public key objects.
55
+ */
56
+ async getPublicKeys() {
57
+ const now = Date.now();
58
+ this.publicKeys = this.publicKeys.filter((k) => k.exp > now);
59
+ return await Promise.resolve(this.publicKeys.map((k) => k.key));
60
+ }
61
+ /**
62
+ * Retrieves the Unix timestamp (in milliseconds) of the last key rotation.
63
+ * @returns The timestamp of the last rotation, or `0` if no rotation has occurred.
64
+ */
65
+ async getLastRotationTimestamp() {
66
+ return await Promise.resolve(this.lastRotation);
67
+ }
68
+ /**
69
+ * Stores the Unix timestamp (in milliseconds) of the most recent key rotation.
70
+ * @param msDate - The timestamp to persist.
71
+ */
72
+ async setLastRotationTimestamp(msDate) {
73
+ this.lastRotation = msDate;
74
+ return await Promise.resolve();
75
+ }
76
+ }
77
+ /**
78
+ * Creates a new {@link InMemoryKeyStore} instance.
79
+ *
80
+ * Convenience factory for use with {@link JoseJwksAuthority} and {@link JwksRotator}.
81
+ *
82
+ * @returns A fresh in-memory key store with no keys stored.
83
+ */
84
+ export function createInMemoryKeyStore() {
85
+ return new InMemoryKeyStore();
86
+ }
@@ -0,0 +1,36 @@
1
+ import { JwksRotationTimestampStore, KeyGenerator } from "./types.js";
2
+ /**
3
+ * Configuration options for {@link JwksRotator}.
4
+ */
5
+ export interface JwksRotatorOptions {
6
+ /** The key generator used to produce new signing key pairs during rotation. */
7
+ keyGenerator: KeyGenerator;
8
+ /** The store used to read and persist the last rotation timestamp. */
9
+ rotatorKeyStore: JwksRotationTimestampStore;
10
+ /** How often (in milliseconds) new keys should be generated. For example, `7.884e9` for 91 days. */
11
+ rotationIntervalMs: number;
12
+ }
13
+ /**
14
+ * Manages automatic JWKS key rotation based on a configurable interval.
15
+ *
16
+ * Call {@link JwksRotator.checkAndRotateKeys} at service startup and/or on a
17
+ * recurring schedule (e.g. every hour) to ensure that signing keys are rotated
18
+ * before they expire. During rotation the previous public key remains available
19
+ * in the JWKS until its TTL expires, so in-flight tokens continue to verify correctly.
20
+ */
21
+ export declare class JwksRotator {
22
+ private readonly keyGenerator;
23
+ private readonly rotatorKeyStore;
24
+ private readonly rotationIntervalMs;
25
+ /**
26
+ * @param options - Rotation configuration: key generator, timestamp store, and interval.
27
+ */
28
+ constructor({ keyGenerator, rotationIntervalMs, rotatorKeyStore }: JwksRotatorOptions);
29
+ /**
30
+ * Checks if rotation is due, and performs rotation if necessary.
31
+ * Should be called at service startup or on a schedule (e.g. every hour).
32
+ */
33
+ checkAndRotateKeys(): Promise<void>;
34
+ private rotateKeys;
35
+ }
36
+ //# sourceMappingURL=jwks_rotator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwks_rotator.d.ts","sourceRoot":"","sources":["../src/jwks_rotator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,YAAY,EAAE,YAAY,CAAC;IAC3B,sEAAsE;IACtE,eAAe,EAAE,0BAA0B,CAAC;IAC5C,oGAAoG;IACpG,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAC7D,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAE5C;;OAEG;gBACS,EAAE,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,EAAE,kBAAkB;IAMrF;;;OAGG;IACU,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;YAYlC,UAAU;CAGzB"}