unjwt 0.5.38 → 0.5.40

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 (41) hide show
  1. package/README.md +148 -670
  2. package/dist/_chunks/chunk.mjs +13 -0
  3. package/dist/_chunks/index.d.mts +558 -0
  4. package/dist/_chunks/index2.d.mts +108 -0
  5. package/dist/_chunks/jwe.d.mts +43 -0
  6. package/dist/_chunks/jwe.mjs +132 -0
  7. package/dist/_chunks/jwk.d.mts +611 -0
  8. package/dist/_chunks/jwk.mjs +1592 -0
  9. package/dist/_chunks/jws.d.mts +242 -0
  10. package/dist/_chunks/jws.mjs +2070 -0
  11. package/dist/_chunks/jws2.mjs +598 -0
  12. package/dist/_chunks/utils.mjs +375 -0
  13. package/dist/adapters/h3.d.mts +3 -267
  14. package/dist/adapters/h3.mjs +3 -704
  15. package/dist/adapters/h3v1.d.mts +3 -8
  16. package/dist/adapters/h3v1.mjs +3 -7
  17. package/dist/adapters/h3v2.d.mts +145 -147
  18. package/dist/adapters/h3v2.mjs +501 -594
  19. package/dist/index.d.mts +6 -8
  20. package/dist/index.mjs +5 -4
  21. package/dist/jwe.d.mts +3 -4
  22. package/dist/jwe.mjs +2 -3
  23. package/dist/jwk.d.mts +3 -2
  24. package/dist/jwk.mjs +2 -2
  25. package/dist/jws.d.mts +3 -3
  26. package/dist/jws.mjs +2 -3
  27. package/dist/utils.d.mts +3 -3
  28. package/dist/utils.mjs +2 -1
  29. package/package.json +33 -31
  30. package/dist/shared/unjwt.4JQCoID_.d.mts +0 -161
  31. package/dist/shared/unjwt.B-v62plj.mjs +0 -249
  32. package/dist/shared/unjwt.BCi3FEWr.d.mts +0 -328
  33. package/dist/shared/unjwt.BYDTEWa-.d.mts +0 -269
  34. package/dist/shared/unjwt.Be8NxwFK.mjs +0 -438
  35. package/dist/shared/unjwt.BqdNUKDr.d.mts +0 -64
  36. package/dist/shared/unjwt.CMNtw8ek.d.mts +0 -155
  37. package/dist/shared/unjwt.Cy2MlkUe.d.mts +0 -174
  38. package/dist/shared/unjwt.D0_ccT7g.d.mts +0 -166
  39. package/dist/shared/unjwt.DdLN9vwI.mjs +0 -163
  40. package/dist/shared/unjwt.Dpl__ZAR.mjs +0 -2121
  41. package/dist/shared/unjwt.JdKqVANs.d.mts +0 -5
package/README.md CHANGED
@@ -1,663 +1,222 @@
1
1
  # unjwt
2
2
 
3
- <!-- automd:badges bundlephobia style="flat" color="FFDC3B" -->
4
-
5
- [![npm version](https://img.shields.io/npm/v/unjwt?color=FFDC3B)](https://npmjs.com/package/unjwt)
6
- [![npm downloads](https://img.shields.io/npm/dm/unjwt?color=FFDC3B)](https://npm.chart.dev/unjwt)
7
- [![bundle size](https://img.shields.io/bundlephobia/minzip/unjwt?color=FFDC3B)](https://bundlephobia.com/package/unjwt)
8
-
9
- <!-- /automd -->
3
+ [![npm version](https://npmx.dev/api/registry/badge/version/unjwt?name=true)](https://npmx.dev/package/unjwt)
4
+ [![npm downloads](https://npmx.dev/api/registry/badge/downloads/unjwt)](https://npmx.dev/package/unjwt)
5
+ [![bundle size](https://npmx.dev/api/registry/badge/size/unjwt)](https://npmx.dev/package/unjwt)
10
6
 
11
7
  A collection of low-level JWT ([RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)) utilities using the Web Crypto API. Supports:
12
8
 
13
- - **JWS (JSON Web Signature, [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515))**: sign and verify tokens using HMAC, RSA (RSASSA-PKCS1-v1_5 & RSA-PSS), and ECDSA algorithms.
14
- - **JWE (JSON Web Encryption, [RFC 7516](https://datatracker.ietf.org/doc/html/rfc7516))**: encrypt and decrypt data using various key management algorithms (AES Key Wrap, AES-GCM Key Wrap, RSA-OAEP, PBES2, ECDH-ES) and content encryption algorithms (AES-GCM, AES-CBC HMAC-SHA2).
15
- - **JWK (JSON Web Key, [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517))**: generate, import, export, wrap, and unwrap keys in JWK format or as `CryptoKey` objects.
16
-
17
- - **Adapters** for popular frameworks (PRs welcome for more!):
18
- - [H3 v1 (Nuxt v4, Nitro v2)](./src/adapters/h3v1/): `useJWSSession()`, `useJWESession()`
19
- - [H3 v2 (Nuxt v5, Nitro v3)](./src/adapters/h3v2/): `useJWSSession()`, `useJWESession()`
9
+ - **JWS** ([RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515)) — sign and verify tokens using HMAC, RSA, RSA-PSS, ECDSA, and EdDSA algorithms
10
+ - **JWE** ([RFC 7516](https://datatracker.ietf.org/doc/html/rfc7516)) — encrypt and decrypt data using AES Key Wrap, AES-GCM KW, RSA-OAEP, PBES2, and ECDH-ES key management with AES-GCM or AES-CBC+HMAC-SHA2 content encryption
11
+ - **JWK** ([RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)) — generate, import, export, wrap, and unwrap keys (CryptoKey, JWK, PEM)
12
+ - **Framework adapters** for cookie-based JWT sessions:
13
+ - [H3 v1](./skills/unjwt/references/adapters-h3.md) (Nuxt v4, Nitro v2)
14
+ - [H3 v2](./skills/unjwt/references/adapters-h3.md) (Nuxt v5, Nitro v3)
20
15
 
21
- ## Table of Contents
22
-
23
- - [Usage](#usage)
24
- - [JWS](#jws-json-web-signature-rfc-7515)
25
- - [JWE](#jwe-json-web-encryption-rfc-7516)
26
- - [JWK](#jwk-json-web-key-rfc-7517)
27
- - [Utility Functions](#utility-functions)
28
- - [Adapters](#adapters)
29
- - [H3 v1 (Nuxt up to v4, Nitro up to v2)](#h3-v1-nuxt-v4-nitro-v2)
30
- - [H3 v2 (Nuxt v5, Nitro v3)](#h3-v2-nuxt-v5-nitro-v3)
31
- - [Development](#development)
32
- - [Credits](#credits)
33
- - [License](#license)
34
-
35
- ## Usage
16
+ Zero runtime dependencies for core. Optional peer deps for adapters (`h3`, `cookie-es`, `rou3`).
36
17
 
37
- Install the package:
18
+ ## Install
38
19
 
39
20
  ```sh
40
- # ✨ Auto-detect (supports npm, yarn, pnpm, deno and bun)
21
+ # Auto-detect package manager (npm, yarn, pnpm, deno, bun)
41
22
  npx nypm install unjwt
42
23
  ```
43
24
 
44
- Import:
45
-
46
- **ESM** (Node.js, Bun, Deno)
47
-
48
- ```js
49
- import { jws, jwe, jwk } from "unjwt";
50
- // JWS functions
51
- import { sign, verify } from "unjwt/jws";
52
- // JWE functions
53
- import { encrypt, decrypt } from "unjwt/jwe";
54
- // JWK functions
55
- import {
56
- generateKey,
57
- generateJWK,
58
- importJWKFromPEM,
59
- exportJWKToPEM,
60
- deriveKeyFromPassword,
61
- deriveJWKFromPassword,
62
- } from "unjwt/jwk";
63
- // Utility functions
64
- import {
65
- isJWK,
66
- isJWKSet,
67
- isSymmetricJWK,
68
- isAsymmetricJWK,
69
- isPrivateJWK,
70
- isPublicJWK,
71
- } from "unjwt/utils";
72
- ```
25
+ If you would like to install the agent skill you can run:
73
26
 
74
- **CDN** (Deno, Bun and Browsers)
75
-
76
- ```js
77
- import { jws, jwe, jwk } from "https://esm.sh/unjwt";
78
- // JWS functions
79
- import { sign, verify } from "https://esm.sh/unjwt/jws";
80
- // JWE functions
81
- import { encrypt, decrypt } from "https://esm.sh/unjwt/jwe";
82
- // JWK functions
83
- import {
84
- generateKey,
85
- generateJWK,
86
- importJWKFromPEM,
87
- exportJWKToPEM,
88
- deriveKeyFromPassword,
89
- deriveJWKFromPassword,
90
- } from "https://esm.sh/unjwt/jwk";
91
- // Utility functions
92
- import {
93
- isJWK,
94
- isJWKSet,
95
- isSymmetricJWK,
96
- isAsymmetricJWK,
97
- isPrivateJWK,
98
- isPublicJWK,
99
- } from "https://esm.sh/unjwt/utils";
100
- ```
101
-
102
- ---
103
-
104
- ### JWS (JSON Web Signature, [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515))
105
-
106
- Functions to sign and verify data according to the JWS specification.
107
-
108
- #### `sign(payload, key, options)`
109
-
110
- Creates a JWS token.
111
-
112
- - `payload`: The data to sign (`string`, `Uint8Array`, or a JSON-serializable `object`).
113
- - `key`: The signing key (`CryptoKey`, `JWK`, or `Uint8Array` for symmetric keys).
114
- - `options`:
115
- - `alg`: (Required) The JWS algorithm (e.g., `"HS256"`, `"RS256"`, `"ES256"`, `"PS256"`).
116
- - `protectedHeader`: An object for additional JWS Protected Header parameters (e.g., `kid`, `typ`, `cty`, `crit`, `b64`). If `payload` is an object and `typ` is not set, it defaults to `"JWT"`. The `b64` parameter ([RFC7797 section-3](https://datatracker.ietf.org/doc/html/rfc7797#section-3)) controls payload encoding (defaults to `true`, meaning Base64URL encoded).
117
- - `expiresIn`: Sets an expiration time in seconds (e.g., `3600` for 1 hour). If `iat` is missing it will be set to the current time. If `exp` is missing it will be set to `iat + expiresIn`. This is only applied if `payload` is a JWT.
118
- - `currentDate`: The current date for computing `expiresIn` option. Defaults to `new Date()`.
119
-
120
- Returns a `Promise<string>` resolving to the JWS token in Compact Serialization format.
121
-
122
- **Example (HS256 with string secret):**
123
-
124
- ```ts
125
- import { sign } from "unjwt/jws";
126
-
127
- const payload = { message: "My important data" }; // Object payload
128
- const key = await generateKey("HS256", { toJWK: true }); // Generates a JWK_oct
129
-
130
- const token = await sign(payload, key);
131
-
132
- console.log(token);
133
- // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
134
- ```
135
-
136
- **Example (RS256 with CryptoKey):**
137
-
138
- ```ts
139
- import { sign } from "unjwt/jws";
140
- import { generateKey } from "unjwt/jwk";
141
-
142
- const payload = { userId: 123, permissions: ["read"] };
143
- const { privateKey } = await generateKey("RS256"); // Generates a CryptoKeyPair
144
-
145
- const token = await sign(payload, privateKey, { alg: "RS256" });
146
-
147
- console.log(token);
148
- // eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
149
- ```
150
-
151
- #### `verify(jws, key, options?)`
152
-
153
- Verifies a JWS token.
154
-
155
- - `jws`: The JWS token string.
156
- - `key`: The verification key (`CryptoKey`, `JWK`, `JWKSet`, `Uint8Array`, or a `KeyLookupFunction`).
157
- A `KeyLookupFunction` has the signature `(header: JWSProtectedHeader, jws: string) => Promise<CryptoKey | JWK | JWKSet | Uint8Array> | CryptoKey | JWK | JWKSet | Uint8Array`.
158
- - `options` (optional):
159
- - `algorithms`: An array of allowed JWS `alg` values. If not provided, the `alg` from the JWS header is used.
160
- - `validateJWT`: Unless false, will parse payload as JWT and validate claims if applicable (typ includes "jwt", case insensitive). Default `undefined`.
161
- - `critical`: An array of JWS header parameter names that the application understands and processes.
162
-
163
- Returns a `Promise<JWSVerifyResult<T>>` which is an object `{ payload: T, protectedHeader: JWSProtectedHeader }`.
164
- The `payload` type `T` can be `JWTClaims` (object), `string`, or `Uint8Array` depending on the JWS content and headers.
165
-
166
- **Example (HS256):**
167
-
168
- ```ts
169
- import { verify } from "unjwt/jws";
170
-
171
- const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // From HS256 sign example
172
- const secret = "supersecretkey";
173
-
174
- const { payload, protectedHeader } = await verify(token, secret);
175
- console.log(payload); // { message: "My important data" }
176
- console.log(protectedHeader); // { alg: "HS256", typ: "JWT" }
177
- ```
178
-
179
- **Example (RS256 with key lookup):**
180
-
181
- ```ts
182
- import { verify } from "unjwt/jws";
183
- import { generateKey } from "unjwt/jwk";
184
-
185
- const token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."; // From RS256 sign example
186
-
187
- // Example: using a key lookup function
188
- const { publicKey: rsaPublicKey } = await generateKey("RS256"); // For example purposes, assume publicKey is stored/fetched during lookup
189
-
190
- const keyLookup = async (header) => {
191
- if (header.alg === "RS256" /* && header.kid === 'expected-kid' */) {
192
- return rsaPublicKey;
193
- }
194
- throw new Error("Unsupported algorithm or key not found");
195
- };
196
-
197
- const { payload } = await verify(token, keyLookup, { algorithms: ["RS256"] });
198
- console.log(payload); // { userId: 123, permissions: ["read"] }
27
+ ```sh
28
+ npx skills add sandros94/unjwt
199
29
  ```
200
30
 
201
- ---
202
-
203
- ### JWE (JSON Web Encryption, [RFC 7516](https://datatracker.ietf.org/doc/html/rfc7516))
204
-
205
- Functions to encrypt and decrypt data according to the JWE specification.
206
-
207
- #### `encrypt(payload, key, options)`
208
-
209
- Encrypts data to produce a JWE token.
210
-
211
- - `payload`: The data to encrypt (`string`, `Uint8Array`, or a JSON-serializable `object`).
212
- - `key`: The Key Encryption Key (KEK) or password (`CryptoKey`, `JWK`, `string`, or `Uint8Array`).
213
- - `options`:
214
- - `alg`: (Required) The JWE Key Management algorithm (e.g., `"A128KW"`, `"RSA-OAEP-256"`, `"PBES2-HS256+A128KW"`, `"ECDH-ES+A128KW"`), defaults depends on the key provided.
215
- - `enc`: (Required) The JWE Content Encryption algorithm (e.g., `"A128GCM"`, `"A256CBC-HS512"`), defaults depends on the key provided.
216
- - `protectedHeader`: An object for JWE Protected Header parameters (e.g., `kid`, `typ`, `cty`, `crit`, `apu`, `apv`, `p2s`, `p2c`). If `payload` is an object and `typ` is not set, it defaults to `"JWT"`.
217
- - `cek`: (Optional) Provide your own Content Encryption Key (`CryptoKey` or `Uint8Array`).
218
- - `contentEncryptionIV`: (Optional) Provide your own Initialization Vector for content encryption (`Uint8Array`).
219
- - Other algorithm-specific options like `p2s`, `p2c` (for PBES2), `keyManagementIV`, `ecdhPartyUInfo`, `ecdhPartyVInfo`.
220
-
221
- Returns a `Promise<string>` resolving to the JWE token in Compact Serialization format.
222
-
223
- **Example (PBES2 password-based encryption):**
224
-
225
- ```ts
226
- import { encrypt } from "unjwt/jwe";
227
-
228
- const plaintext = "Secret message for password protection";
229
- const password = "myVeryStrongPassword123!";
31
+ ## Usage
230
32
 
231
- // Fallback to PBES2-HS256+A128KW and A128GCM if no `alg` and `end` are provided
232
- const jweToken = await encrypt(plaintext, password);
233
- console.log(jweToken);
234
- // JWE token string...
235
- ```
33
+ ### JWS — Sign & Verify
236
34
 
237
- **Example (A128KW with A128GCM):**
35
+ > Full reference: [skills/unjwt/references/jws.md](./skills/unjwt/references/jws.md)
238
36
 
239
37
  ```ts
240
- import { encrypt } from "unjwt/jwe";
241
- import { generateKey } from "unjwt/jwk";
38
+ import { sign, verify } from "unjwt/jws";
39
+ import { generateJWK } from "unjwt/jwk";
242
40
 
243
- const payload = { data: "sensitive information" };
244
- const kek = await generateKey("A128KW"); // AES Key Wrap key
41
+ // Generate a key (HMAC, RSA, ECDSA, EdDSA, etc.)
42
+ const key = await generateJWK("HS256");
245
43
 
246
- const jweToken = await encrypt(payload, kek, {
247
- alg: "A128KW",
248
- enc: "A128GCM",
249
- protectedHeader: { kid: "aes-key-1" },
250
- });
44
+ // Sign a JWT
45
+ const token = await sign(
46
+ { sub: "user123", role: "admin" },
47
+ key,
48
+ { expiresIn: "1h" }, // supports: number (seconds), "30s", "10m", "2h", "7D", "1Y"
49
+ );
251
50
 
252
- console.log(jweToken);
253
- // eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwia2lkIjoiYWVzLWtleS0xIn0...
51
+ // Verify and extract payload
52
+ const { payload, protectedHeader } = await verify(token, key);
53
+ console.log(payload); // { sub: "user123", role: "admin", iat: ..., exp: ... }
254
54
  ```
255
55
 
256
- #### `decrypt(jwe, key, options?)`
257
-
258
- Decrypts a JWE token.
259
-
260
- - `jwe`: The JWE token string.
261
- - `key`: The Key Decryption Key (KDK) or password (`CryptoKey`, `JWK`, `string`, `Uint8Array`, or a `JWEKeyLookupFunction`).
262
- A `JWEKeyLookupFunction` has the signature `(header: JWEHeaderParameters) => Promise<CryptoKey | JWK | string | Uint8Array> | CryptoKey | JWK | string | Uint8Array`.
263
- - `options` (optional):
264
- - `algorithms`: Array of allowed JWE Key Management `alg` values.
265
- - `validateJWT`: Unless false, will parse payload as JWT and validate claims if applicable (typ includes "jwt", case insensitive). Default `undefined`.
266
- - `encryptionAlgorithms`: Array of allowed JWE Content Encryption `enc` values.
267
- - `critical`: Array of JWE header parameter names that the application understands.
268
- - `unwrappedKeyAlgorithm`: (For `unwrapKey` internally) Algorithm details for the CEK after unwrapping.
269
- - `keyUsage`: (For `unwrapKey` internally) Intended usages for the unwrapped CEK.
270
-
271
- Returns a `Promise<JWEDecryptResult<T>>` which is an object `{ payload: T, protectedHeader: JWEHeaderParameters, cek: Uint8Array, aad: Uint8Array }`.
272
- The `payload` type `T` can be `JWTClaims` (object) or `string`.
273
-
274
- **Example (PBES2 password-based decryption):**
56
+ **Asymmetric keys (RS256, ES256, PS256, Ed25519, etc.):**
275
57
 
276
58
  ```ts
277
- import { decrypt } from "unjwt/jwe";
278
- // const jweToken = ...; // From PBES2 encrypt example
279
- // const password = "myVeryStrongPassword123!";
280
-
281
- const { payload } = await decrypt(jweToken, password);
59
+ const keys = await generateJWK("RS256");
60
+ const token = await sign({ sub: "user123" }, keys.privateKey);
61
+ const { payload } = await verify(token, keys.publicKey);
282
62
  ```
283
63
 
284
- **Example (A128KW with A128GCM):**
64
+ **Key lookup function for dynamic key resolution:**
285
65
 
286
66
  ```ts
287
- import { decrypt } from "unjwt/jwe";
288
- // const jweToken = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwia2lkIjoiYWVzLWtleS0xIn0...";
289
- // const kek = ...; // The same AES Key Wrap key used for encryption
290
-
291
- async function decryptData(jweToken: string, kek: CryptoKey) {
292
- try {
293
- const { payload, protectedHeader, cek } = await decrypt(jweToken, kek, {
294
- algorithms: ["A128KW"],
295
- encryptionAlgorithms: ["A128GCM"],
296
- });
297
- console.log("Decrypted Plaintext:", payload);
298
- console.log("Protected Header:", protectedHeader);
299
- // console.log("CEK (Content Encryption Key):", cek);
300
- } catch (error) {
301
- console.error("Decryption failed:", error);
302
- }
303
- }
67
+ const { payload } = await verify(
68
+ token,
69
+ async (header) => {
70
+ // Resolve key by kid, alg, or any header field
71
+ return await fetchPublicKeyByKid(header.kid);
72
+ },
73
+ { algorithms: ["RS256"] },
74
+ );
304
75
  ```
305
76
 
306
- ---
307
-
308
- ### JWK (JSON Web Key, [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517))
309
-
310
- Utilities for working with JSON Web Keys.
77
+ ### JWE — Encrypt & Decrypt
311
78
 
312
- #### `generateKey(alg, options?)`
313
-
314
- Generates a cryptographic key.
315
-
316
- - `alg`: The JWA algorithm identifier for the key to be generated (e.g., `"HS256"`, `"RS256"`, `"ES256"`, `"A128KW"`, `"A128GCM"`, `"A128CBC-HS256"`).
317
- - `options` (optional):
318
- - `toJWK`: If `true`, returns the key(s) in JWK format. Otherwise, returns `CryptoKey`(s) or `Uint8Array` (for composite keys like AES-CBC-HS\*). Default `false`.
319
- - `extractable`: Boolean, whether the generated `CryptoKey` can be exported. Default `true`.
320
- - `keyUsage`: Array of `KeyUsage` strings. Defaults are algorithm-specific.
321
- - `modulusLength`: For RSA keys (e.g., `2048`, `4096`). Default `2048`.
322
- - `publicExponent`: For RSA keys. Default `new Uint8Array([0x01, 0x00, 0x01])`.
323
-
324
- Returns a `Promise` resolving to `CryptoKey`, `CryptoKeyPair`, `Uint8Array` (for composite keys), `JWK`, or `{ privateKey: JWK, publicKey: JWK }` depending on `alg` and `options.toJWK`.
325
-
326
- **Examples:**
79
+ > Full reference: [skills/unjwt/references/jwe.md](./skills/unjwt/references/jwe.md)
327
80
 
328
81
  ```ts
329
- import { generateKey } from "unjwt/jwk";
330
-
331
- // Generate an HS256 CryptoKey
332
- const hmacKey = await generateKey("HS256");
333
- console.log(hmacKey); // CryptoKey
334
-
335
- // Generate an RS256 CryptoKeyPair
336
- const rsaKeyPair = await generateKey("RS256", { modulusLength: 2048 });
337
- console.log(rsaKeyPair.publicKey); // CryptoKey
338
- console.log(rsaKeyPair.privateKey); // CryptoKey
339
-
340
- // Generate an ES384 key pair as JWKs
341
- const ecJwks = await generateKey("ES384", { toJWK: true });
342
- console.log(ecJwks.publicKey); // JWK
343
- console.log(ecJwks.privateKey); // JWK
344
-
345
- // Generate a composite key for A128CBC-HS256 as Uint8Array
346
- const aesCbcHsKeyBytes = await generateKey("A128CBC-HS256");
347
- console.log(aesCbcHsKeyBytes); // Uint8Array (32 bytes: 16 for AES, 16 for HMAC)
82
+ import { encrypt, decrypt } from "unjwt/jwe";
348
83
 
349
- // Generate an A256GCM key as a JWK
350
- const aesGcmJwk = await generateKey("A256GCM", { toJWK: true });
351
- console.log(aesGcmJwk); // JWK
84
+ // Password-based encryption (simplest — uses PBES2)
85
+ const token = await encrypt({ secret: "sensitive data" }, "my-password");
86
+ const { payload } = await decrypt(token, "my-password");
352
87
  ```
353
88
 
354
- #### `deriveKeyFromPassword(password, alg, options)`
355
-
356
- Derives a key from a password using PBKDF2 for PBES2 algorithms.
357
-
358
- - `password`: The password (`string` or `Uint8Array`).
359
- - `alg`: The PBES2 algorithm (e.g., `"PBES2-HS256+A128KW"`).
360
- - `options`:
361
- - `salt`: The salt (`Uint8Array`, at least 8 octets).
362
- - `iterations`: The iteration count (positive integer).
363
- - `toJWK`: If `true`, returns a `JWK_oct`. Otherwise, `CryptoKey`. Default `false`.
364
- - `extractable`: Boolean for `CryptoKey`. Default `false` unless `toJWK` is true.
365
- - `keyUsage`: For `CryptoKey`. Default `["wrapKey", "unwrapKey"]`.
366
-
367
- Returns a `Promise` resolving to `CryptoKey` or `JWK_oct`.
368
-
369
- **Example:**
89
+ **With explicit key management:**
370
90
 
371
91
  ```ts
372
- import { deriveKeyFromPassword } from "unjwt/jwk";
373
- import { randomBytes, textEncoder } from "unjwt/utils";
92
+ import { generateJWK } from "unjwt/jwk";
374
93
 
375
- const password = "mySecretPassword";
376
- const salt = randomBytes(16);
377
- const iterations = 4096;
94
+ // AES Key Wrap
95
+ const aesKey = await generateJWK("A256KW");
96
+ const token = await encrypt({ data: "secret" }, aesKey);
378
97
 
379
- const derivedKey = await deriveKeyFromPassword(password, "PBES2-HS384+A192KW", {
380
- salt,
381
- iterations,
382
- });
383
- console.log(derivedKey); // CryptoKey for AES-KW (192-bit)
98
+ // RSA-OAEP
99
+ const rsaKeys = await generateJWK("RSA-OAEP-256");
100
+ const token2 = await encrypt({ data: "secret" }, rsaKeys.publicKey);
101
+ const { payload } = await decrypt(token2, rsaKeys.privateKey);
384
102
 
385
- const derivedJwk = await deriveKeyFromPassword(password, "PBES2-HS512+A256KW", {
386
- salt,
387
- iterations,
388
- toJWK: true,
389
- });
390
- console.log(derivedJwk); // JWK_oct { kty: "oct", k: "...", alg: "A256KW" }
103
+ // ECDH-ES
104
+ const ecKeys = await generateJWK("ECDH-ES+A256KW");
105
+ const token3 = await encrypt({ data: "secret" }, ecKeys.publicKey);
391
106
  ```
392
107
 
393
- #### `importKey(keyMaterial, alg?)`
394
-
395
- Imports a key from various formats. This is a flexible wrapper.
108
+ ### JWK — Key Management
396
109
 
397
- - `keyMaterial`: The key to import. Can be:
398
- - `CryptoKey`: Returned directly.
399
- - `Uint8Array`: Returned directly (treated as raw symmetric key bytes).
400
- - `string`: Encoded to `Uint8Array` and returned.
401
- - `JWK_oct` (symmetric JWK with `k` property): The `k` value is Base64URL decoded and returned as `Uint8Array`.
402
- - Other `JWK` types (asymmetric): Imported into a `CryptoKey`.
403
- - `alg` (optional): The JWA algorithm string. **Required** when importing asymmetric JWKs (e.g., RSA, EC) to provide context for `crypto.subtle.importKey`.
404
-
405
- Returns a `Promise` resolving to `CryptoKey` or `Uint8Array`.
406
-
407
- **Examples:**
110
+ > Full reference: [skills/unjwt/references/jwk.md](./skills/unjwt/references/jwk.md)
408
111
 
409
112
  ```ts
410
- import { importKey } from "unjwt/jwk";
411
- import { textEncoder, base64UrlDecode } from "unjwt/utils";
412
-
413
- // Import raw symmetric key bytes
414
- const rawBytes = textEncoder.encode("a-32-byte-long-secret-key-123"); // 32 bytes for AES-256 or HS256
415
- const symmetricKeyBytes = await importKey(rawBytes);
416
- console.log(symmetricKeyBytes); // Uint8Array
417
-
418
- // Import a symmetric JWK (kty: "oct")
419
- const octJwk = {
420
- kty: "oct",
421
- k: "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr0", // Example key
422
- };
423
- const importedOctBytes = await importKey(octJwk); // Returns Uint8Array
424
- console.log(importedOctBytes); // Uint8Array (decoded from k)
425
-
426
- // Import an RSA Public Key JWK
427
- const rsaPublicJwk = {
428
- kty: "RSA",
429
- n: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3ok92YEnjsADC4Ue87zwRzH2J-TCwlcQrY3E9gGZJZL2g_2_5QjLhL0gR0xYj04_N4M",
430
- e: "AQAB",
431
- alg: "RS256",
432
- kid: "rsa-pub-1",
433
- };
434
- const rsaPublicKey = await importKey(rsaPublicJwk, "RS256"); // 'alg' is crucial here
435
- console.log(rsaPublicKey); // CryptoKey
436
- ```
437
-
438
- #### `exportKey(key, jwk?)`
439
-
440
- Exports a `CryptoKey` to JWK format.
441
-
442
- - `key`: The `CryptoKey` to export (must be `extractable`).
443
- - `jwk` (optional): A partial `JWK` object to merge with the exported properties (e.g., to add `kid`, `use`, or override `alg`).
113
+ import {
114
+ generateKey,
115
+ generateJWK,
116
+ importKey,
117
+ exportKey,
118
+ importJWKFromPEM,
119
+ exportJWKToPEM,
120
+ wrapKey,
121
+ unwrapKey,
122
+ deriveKeyFromPassword,
123
+ } from "unjwt/jwk";
444
124
 
445
- Returns a `Promise<JWK>`.
125
+ // Generate keys as CryptoKey or JWK
126
+ const hmacKey = await generateKey("HS256"); // CryptoKey
127
+ const rsaPair = await generateKey("RS256"); // CryptoKeyPair
128
+ const ecJwk = await generateJWK("ES256", { kid: "k1" }); // { privateKey: JWK, publicKey: JWK }
446
129
 
447
- **Example:**
130
+ // PEM conversion
131
+ const jwk = await importJWKFromPEM(pemString, "spki", "RS256", undefined, { kid: "rsa-1" });
132
+ const pem = await exportJWKToPEM(jwk, "spki");
448
133
 
449
- ```ts
450
- import { generateKey, exportKey } from "unjwt/jwk";
451
-
452
- const { publicKey } = await generateKey("ES256"); // Generates an extractable CryptoKey
453
-
454
- const jwk = await exportKey(publicKey, { kid: "ec-key-001", use: "sig" });
455
- console.log(jwk);
456
- // {
457
- // kty: 'EC',
458
- // crv: 'P-256',
459
- // x: '...',
460
- // y: '...',
461
- // ext: true,
462
- // key_ops: [ 'verify' ], // or as per generation
463
- // kid: 'ec-key-001',
464
- // use: 'sig'
465
- // }
134
+ // Key wrapping
135
+ const cek = crypto.getRandomValues(new Uint8Array(32));
136
+ const { encryptedKey } = await wrapKey("A256KW", cek, aesKey);
137
+ const unwrapped = await unwrapKey("A256KW", encryptedKey, aesKey, { returnAs: false });
466
138
  ```
467
139
 
468
- #### `wrapKey(alg, keyToWrap, wrappingKey, options?)`
469
-
470
- Wraps a Content Encryption Key (CEK).
471
-
472
- - `alg`: The JWA key management algorithm (e.g., `"A128KW"`, `"RSA-OAEP"`).
473
- - `keyToWrap`: The CEK to wrap (`CryptoKey` or `Uint8Array`).
474
- - `wrappingKey`: The Key Encryption Key (KEK) (`CryptoKey`, `JWK`, or password `string`/`Uint8Array` for PBES2).
475
- - `options` (optional): Algorithm-specific options (e.g., `p2s`, `p2c` for PBES2; `iv` for AES-GCMKW).
476
-
477
- Returns a `Promise<WrapKeyResult>` containing `encryptedKey` and other parameters like `iv`, `tag`, `epk`, `p2s`, `p2c` as needed by the algorithm.
140
+ ### Utility Functions
478
141
 
479
- **Example (AES Key Wrap):**
142
+ > Full reference: [skills/unjwt/references/utils.md](./skills/unjwt/references/utils.md)
480
143
 
481
144
  ```ts
482
- import { wrapKey, generateKey } from "unjwt/jwk";
483
- import { randomBytes } from "unjwt/utils";
484
-
485
- const cekToWrap = randomBytes(32); // e.g., a 256-bit AES key as Uint8Array
486
- const kek = await generateKey("A128KW"); // 128-bit AES Key Wrap key
145
+ import {
146
+ base64UrlEncode,
147
+ base64UrlDecode,
148
+ randomBytes,
149
+ isJWK,
150
+ isJWKSet,
151
+ isSymmetricJWK,
152
+ isPrivateJWK,
153
+ isPublicJWK,
154
+ isCryptoKey,
155
+ textEncoder,
156
+ textDecoder,
157
+ } from "unjwt/utils";
487
158
 
488
- const { encryptedKey } = await wrapKey("A128KW", cekToWrap, kek);
489
- console.log("Wrapped CEK:", encryptedKey); // Uint8Array
159
+ const bytes = randomBytes(32);
160
+ const encoded = base64UrlEncode(bytes);
161
+ const decoded = base64UrlDecode(encoded, false); // Uint8Array
490
162
  ```
491
163
 
492
- #### `unwrapKey(alg, wrappedKey, unwrappingKey, options?)`
164
+ ### H3 Session Adapters
493
165
 
494
- Unwraps a Content Encryption Key (CEK).
166
+ > Full reference: [skills/unjwt/references/adapters-h3.md](./skills/unjwt/references/adapters-h3.md)
495
167
 
496
- - `alg`: The JWA key management algorithm.
497
- - `wrappedKey`: The encrypted CEK (`Uint8Array`).
498
- - `unwrappingKey`: The Key Decryption Key (KDK).
499
- - `options` (optional):
500
- - `returnAs`: If `false`, returns `Uint8Array`. If `true` (default) or undefined, returns `CryptoKey`.
501
- - `unwrappedKeyAlgorithm`: `AlgorithmIdentifier` for the imported CEK if `returnAs` is `true`.
502
- - `keyUsage`: `KeyUsage[]` for the imported CEK if `returnAs` is `true`.
503
- - `extractable`: Boolean for the imported CEK.
504
- - Other algorithm-specific options (e.g., `p2s`, `p2c`, `iv`, `tag`, `epk`).
168
+ Cookie-based JWT session management for H3 applications. Sessions are stored as encrypted (JWE) or signed (JWS) tokens in chunked cookies.
505
169
 
506
- Returns a `Promise` resolving to the unwrapped CEK as `CryptoKey` or `Uint8Array`.
170
+ > **Note:** Sessions are lazy — `session.id` is `undefined` until `session.update()` is called. This is intentional for OAuth/spec-compliant flows where sessions should only be created upon valid operations.
507
171
 
508
- **Example (AES Key Unwrap):**
172
+ #### JWE Session (encrypted, recommended for sensitive data)
509
173
 
510
174
  ```ts
511
- import { unwrapKey, generateKey } from "unjwt/jwk";
512
- // const encryptedKey = ...; // From wrapKey example
513
- // const kdk = ...; // Same KEK used for wrapping
175
+ import { useJWESession, generateJWK } from "unjwt/adapters/h3v2";
514
176
 
515
- async function unwrapMyKey(encryptedKey: Uint8Array, kdk: CryptoKey) {
516
- const unwrappedCekBytes = await unwrapKey("A128KW", encryptedKey, kdk, {
517
- returnAs: false, // Get raw bytes
177
+ app.get("/profile", async (event) => {
178
+ const session = await useJWESession(event, {
179
+ key: process.env.SESSION_SECRET!, // password string, symmetric JWK, or asymmetric keypair
180
+ maxAge: "7D",
518
181
  });
519
- console.log("Unwrapped CEK (bytes):", unwrappedCekBytes); // Uint8Array
520
182
 
521
- const unwrappedCekCryptoKey = await unwrapKey("A128KW", encryptedKey, kdk, {
522
- returnAs: true, // Get CryptoKey
523
- unwrappedKeyAlgorithm: { name: "AES-GCM", length: 256 }, // Specify CEK's intended alg
524
- keyUsage: ["encrypt", "decrypt"],
525
- });
526
- console.log("Unwrapped CEK (CryptoKey):", unwrappedCekCryptoKey); // CryptoKey
527
- }
528
- ```
529
-
530
- #### `importJWKFromPEM(pem, pemType, alg, importOptions?, jwkExtras?)`
531
-
532
- Imports a key from a PEM-encoded string and converts it to a JWK.
533
-
534
- - `pem`: The PEM-encoded string (including `-----BEGIN ...-----` and `-----END ...-----` markers).
535
- - `pemType`: The type of PEM encoding:
536
- - `"pkcs8"`: For private keys in PKCS#8 format.
537
- - `"spki"`: For public keys in SPKI format.
538
- - `"x509"`: For X.509 certificates (extracts the public key).
539
- - `alg`: The JWA algorithm identifier (e.g., `"RS256"`, `"ES256"`). This is crucial for `crypto.subtle.importKey` to understand the key's intended algorithm and for setting the `'alg'` field in the resulting JWK.
540
- - `importOptions` (optional): Options for the underlying `crypto.subtle.importKey` call:
541
- - `extractable`: Boolean, whether the imported `CryptoKey` should be extractable. Defaults to `true`.
542
- - `keyUsage`: Array of `KeyUsage` strings for the imported `CryptoKey`.
543
- - `jwkExtras` (optional): An object containing additional properties to merge into the resulting JWK (e.g., `"kid"`, `"use"`).
544
-
545
- Returns a `Promise<JWK>` resolving to the imported key as a JWK.
546
-
547
- **Example:**
548
-
549
- ```ts
550
- import { importJWKFromPEM } from "unjwt/jwk";
551
-
552
- const rsaPublicJwk = await importJWKFromPEM(
553
- provess.env.RSA_PEM_SPKI, // PEM string
554
- "spki",
555
- "RS256",
556
- { extractable: false },
557
- { kid: "my-rsa-key" }, // Additional properties to add to the JWK
558
- );
559
- console.log(rsaPublicJwk);
560
- // {
561
- // kty: 'RSA',
562
- // alg: 'RS256',
563
- // kid: 'my-rsa-key',
564
- // n: '...',
565
- // e: 'AQAB',
566
- // ext: false,
567
- // key_ops: [ 'verify' ]
568
- // }
569
- ```
570
-
571
- #### exportJWKToPEM(jwk, pemFormat, algForCryptoKeyImport?)
572
-
573
- Exports a JWK to a PEM-encoded string.
574
-
575
- - jwk: The JWK to export.
576
- - pemFormat: The desired PEM format:
577
- - "pkcs8": For private keys in PKCS#8 format.
578
- - "spki": For public keys in SPKI format.
579
- - algForCryptoKeyImport (optional): If the JWK does not have an 'alg' property, this algorithm hint is required to correctly convert it to a CryptoKey first. This is only needed if the JWK lacks an alg property.
580
-
581
- Returns a `Promise<string>` resolving to the PEM-encoded key string.
582
-
583
- Example:
183
+ if (!session.id) {
184
+ // No active session — create one
185
+ await session.update({ userId: "123", email: "user@example.com" });
186
+ }
584
187
 
585
- ```ts
586
- import { exportJWKToPEM } from "unjwt/jwk";
587
- import { rsaJWK } from "./keys"; // Assuming you have JWKs in keys.ts
588
-
589
- const rsaPrivatePem = await exportJWKToPEM(rsaJWK.private, "pkcs8");
590
- console.log(rsaPrivatePem);
591
- // -----BEGIN PRIVATE KEY-----
592
- // MII...
593
- // -----END PRIVATE KEY-----
594
-
595
- const rsaPublicSpki = await exportJWKToPEM(
596
- rsaJWK.public,
597
- "spki",
598
- "RS256", // this is required if `rsaJWK.public.alg` is undefined
599
- );
600
- console.log(rsaPublicSpki);
601
- // -----BEGIN PUBLIC KEY-----
602
- // MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQE...
603
- // -----END PUBLIC KEY-----
188
+ return { user: session.data };
189
+ });
604
190
  ```
605
191
 
606
- ### Adapters
607
-
608
- > [!WARNING]
609
- > While these adapters try to be compatible with h3's `useSession` API, the key difference is that new sessions are not automatically created when calling `useJWESession` or `useJWSSession`, marking their `id` as `undefined` until you explicitly call `session.update()`. This is to comply with various specs that do integrate JWT one (such as OAuth) where sessions are only created upon valid operations (like user authorization).
610
-
611
- #### H3 v1 (Nuxt v4, Nitro v2)
612
-
613
- The `h3v1` adapter bundles session helpers that store data inside signed or encrypted JWTs.
614
-
615
- - `useJWESession(event, config)` encrypts the session payload with the provided `secret` (password string or private/symmetric JWK). Use this when session data must remain confidential. (cookie's `httpOnly: true` by default)
616
- - `useJWSSession(event, config)` signs, but does not encrypt, the session payload with `config.key`. Use this when clients may read the session content but you still need tamper protection. (cookie's `httpOnly: false` by default)
617
-
618
- Both helpers expose the same API: read `session.id` / `session.data`, call `session.update()` to patch values, and `session.clear()` to invalidate the cookie. When setting `maxAge`, `exp` claim is automatically managed and validated. They also support a number of `hooks` in the config for custom logic (e.g. logging, refreshing, etc).
192
+ #### JWS Session (signed, readable by client)
619
193
 
620
194
  ```ts
621
- import { defineEventHandler } from "h3";
622
- import { useJWESession, useJWSSession, generateJWK } from "unjwt/adapters/h3v1";
623
-
624
- const keys = await generateJWK("RS256"); // Make sure to persist these keys somewhere!
625
-
626
- export default defineEventHandler(async (event) => {
627
- const privateSession = await useJWESession(event, {
628
- name: "app-session",
629
- secret: process.env.SESSION_SECRET!, // or symmetric or asymmetric keypair
630
- });
195
+ import { useJWSSession, generateJWK } from "unjwt/adapters/h3v2";
631
196
 
632
- await privateSession.update((data) => ({
633
- visits: (data.visits ?? 0) + 1,
634
- }));
197
+ const keys = await generateJWK("RS256"); // persist these!
635
198
 
636
- const publicSession = await useJWSSession(event, {
637
- name: "app-session-public",
638
- key: keys, // you can directly pass symmetric or asymmetric keypairs
639
- maxAge: 60 * 60, // seconds
199
+ app.get("/preferences", async (event) => {
200
+ const session = await useJWSSession(event, {
201
+ key: keys,
202
+ maxAge: "24h",
640
203
  });
641
204
 
642
- return {
643
- encryptedSession: privateSession.data,
644
- signedSession: publicSession.data,
645
- };
205
+ return { theme: session.data.theme };
646
206
  });
647
207
  ```
648
208
 
649
- #### H3 v2 (Nuxt v5, Nitro v3)
650
-
651
- The `h3v2` adapter bundles session helpers that store data inside signed or encrypted JWTs.
209
+ #### Session features
652
210
 
653
- - `useJWESession(event, config)` encrypts the session payload with the provided `secret` (password string or private/symmetric JWK). Use this when session data must remain confidential. (cookie's `httpOnly: true` by default)
654
- - `useJWSSession(event, config)` signs, but does not encrypt, the session payload with `config.key`. Use this when clients may read the session content but you still need tamper protection. (cookie's `httpOnly: false` by default)
211
+ - **Cookie chunking** — large tokens are automatically split across multiple cookies
212
+ - **Header-based tokens** — read from `Authorization: Bearer <token>` or custom headers via `sessionHeader`
213
+ - **Lifecycle hooks** — `onRead`, `onUpdate`, `onClear`, `onExpire`, `onError` for logging, refresh logic, etc.
214
+ - **Key lookup hooks** — `onUnsealKeyLookup` (JWE) / `onVerifyKeyLookup` (JWS) for key rotation
215
+ - **TypeScript generics** — strongly-typed session data via `useJWESession<MyData>(event, config)`
655
216
 
656
- Both helpers expose the same API: read `session.id` / `session.data`, call `session.update()` to patch values, and `session.clear()` to invalidate the cookie. When setting `maxAge`, `exp` claim is automatically managed and validated. They also support a number of `hooks` in the config for custom logic (e.g. logging, refreshing, etc).
217
+ #### Refresh token pattern
657
218
 
658
219
  ```ts
659
- import { H3, HTTPError, serve } from "h3v2";
660
-
661
220
  import {
662
221
  type SessionConfigJWE,
663
222
  type SessionConfigJWS,
@@ -668,112 +227,31 @@ import {
668
227
  generateJWK,
669
228
  } from "unjwt/adapters/h3v2";
670
229
 
671
- const atJwk = await generateJWK("RS256"); // Make sure to persist these keys somewhere!
230
+ const atKeys = await generateJWK("RS256");
672
231
 
673
- const jweOptions = {
674
- key: "refresh_token_secret",
232
+ const refreshConfig = {
233
+ key: process.env.REFRESH_SECRET!,
675
234
  name: "refresh_token",
676
235
  } satisfies SessionConfigJWE;
677
236
 
678
- const jwsOptions = {
679
- key: atJwk,
237
+ const accessConfig = {
238
+ key: atKeys,
680
239
  name: "access_token",
681
- maxAge: 15 * 60, // 15 minutes
240
+ maxAge: "15m",
682
241
  hooks: {
683
242
  async onExpire({ event, config }) {
684
- const refreshSession = await getJWESession(event, jweOptions);
685
- if (!refreshSession.data.sub) {
686
- // no valid refresh session, nothing to do
687
- return;
243
+ const refresh = await getJWESession(event, refreshConfig);
244
+ if (refresh.data.sub) {
245
+ await updateJWSSession(event, config, {
246
+ sub: refresh.data.sub,
247
+ scope: refresh.data.scope,
248
+ });
688
249
  }
689
-
690
- console.log("Access token expired, refreshing...");
691
-
692
- // refresh the access token
693
- await updateJWSSession(event, config, {
694
- sub: refreshSession.data.sub,
695
- scope: refreshSession.data.scope,
696
- });
697
250
  },
698
251
  },
699
252
  } satisfies SessionConfigJWS;
700
-
701
- const app = new H3();
702
-
703
- app.post("/login", async (event) => {
704
- const refreshSession = await useJWESession(event, jweOptions);
705
- const accessSession = await useJWSSession(event, jwsOptions);
706
-
707
- if (accessSession.data.sub) {
708
- // user already logged in, return existing info
709
- return {
710
- accessToken: {
711
- id: accessSession.id,
712
- createdAt: accessSession.createdAt,
713
- expiresAt: accessSession.expiresAt,
714
- data: accessSession.data,
715
- },
716
- refreshSession: {
717
- id: refreshSession.id,
718
- createdAt: refreshSession.createdAt,
719
- expiresAt: refreshSession.expiresAt,
720
- data: refreshSession.data,
721
- },
722
- };
723
- }
724
-
725
- const data = (await event.req.json()) as {
726
- username?: string;
727
- password?: string;
728
- };
729
-
730
- if (!data.username || !data.password) {
731
- throw new HTTPError("Username and password are required", { status: 400 });
732
- }
733
-
734
- // validate user credentials here
735
-
736
- const claims = {
737
- sub: data.username,
738
- scope: ["read:profile"],
739
- };
740
-
741
- await accessSession.update(claims);
742
-
743
- await refreshSession.update(claims);
744
-
745
- return {
746
- accessToken: {
747
- id: accessSession.id,
748
- createdAt: accessSession.createdAt,
749
- expiresAt: accessSession.expiresAt,
750
- data: accessSession.data,
751
- },
752
- refreshSession: {
753
- id: refreshSession.id,
754
- createdAt: refreshSession.createdAt,
755
- expiresAt: refreshSession.expiresAt,
756
- data: refreshSession.data,
757
- },
758
- };
759
- });
760
-
761
- serve(app);
762
253
  ```
763
254
 
764
- ---
765
-
766
- ### Utility Functions
767
-
768
- `unjwt/utils` exports several helpful functions:
769
-
770
- - `base64UrlEncode(data: Uint8Array | string): string`
771
- - `base64UrlDecode(str?: string, toString?: boolean): Uint8Array | string` (Decodes to string by default, or `Uint8Array` if `toString` is `false`)
772
- - `randomBytes(length: number): Uint8Array`
773
- - `textEncoder: TextEncoder`
774
- - `textDecoder: TextDecoder`
775
- - Type guards: `isJWK(key)`, `isCryptoKey(key)`, `isCryptoKeyPair(keyPair)`
776
-
777
255
  ## Development
778
256
 
779
257
  <details>
@@ -784,7 +262,7 @@ serve(app);
784
262
  - Install latest LTS version of [Node.js](https://nodejs.org/en/)
785
263
  - Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable`
786
264
  - Install dependencies using `pnpm install`
787
- - Run interactive tests using `pnpm dev`
265
+ - Run tests using `pnpm test`
788
266
 
789
267
  </details>
790
268