A collection of low-level JWT (RFC 7519) utilities using the Web Crypto API. Supports:
-
JWS (JSON Web Signature, RFC 7515): sign and verify tokens using HMAC, RSA (RSASSA-PKCS1-v1_5 & RSA-PSS), and ECDSA algorithms.
-
JWE (JSON Web Encryption, RFC 7516): 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).
-
JWK (JSON Web Key, RFC 7517): generate, import, export, wrap, and unwrap keys in JWK format or as
CryptoKeyobjects. -
Adapters for popular frameworks (PRs welcome for more!):
- H3 v1 (Nuxt v4, Nitro v2):
useJWSSession(),useJWESession() - H3 v2 (Nuxt v5, Nitro v3):
useJWSSession(),useJWESession()
- H3 v1 (Nuxt v4, Nitro v2):
Install the package:
# โจ Auto-detect (supports npm, yarn, pnpm, deno and bun)
npx nypm install unjwtImport:
ESM (Node.js, Bun, Deno)
import { jws, jwe, jwk } from "unjwt";
// JWS functions
import { sign, verify } from "unjwt/jws";
// JWE functions
import { encrypt, decrypt } from "unjwt/jwe";
// JWK functions
import {
generateKey,
generateJWK,
importJWKFromPEM,
exportJWKToPEM,
deriveKeyFromPassword,
deriveJWKFromPassword,
} from "unjwt/jwk";
// Utility functions
import {
isJWK,
isJWKSet,
isSymmetricJWK,
isAsymmetricJWK,
isPrivateJWK,
isPublicJWK,
} from "unjwt/utils";CDN (Deno, Bun and Browsers)
import { jws, jwe, jwk } from "https://esm.sh/unjwt";
// JWS functions
import { sign, verify } from "https://esm.sh/unjwt/jws";
// JWE functions
import { encrypt, decrypt } from "https://esm.sh/unjwt/jwe";
// JWK functions
import {
generateKey,
generateJWK,
importJWKFromPEM,
exportJWKToPEM,
deriveKeyFromPassword,
deriveJWKFromPassword,
} from "https://esm.sh/unjwt/jwk";
// Utility functions
import {
isJWK,
isJWKSet,
isSymmetricJWK,
isAsymmetricJWK,
isPrivateJWK,
isPublicJWK,
} from "https://esm.sh/unjwt/utils";JWS (JSON Web Signature, RFC 7515)
Functions to sign and verify data according to the JWS specification.
Creates a JWS token.
payload: The data to sign (string,Uint8Array, or a JSON-serializableobject).key: The signing key (CryptoKey,JWK, orUint8Arrayfor symmetric keys).options:alg: (Required) The JWS algorithm (e.g.,"HS256","RS256","ES256","PS256").protectedHeader: An object for additional JWS Protected Header parameters (e.g.,kid,typ,cty,crit,b64). Ifpayloadis an object andtypis not set, it defaults to"JWT". Theb64parameter (RFC7797 section-3) controls payload encoding (defaults totrue, meaning Base64URL encoded).expiresIn: Sets an expiration time in seconds (e.g.,3600for 1 hour). Ifiatis missing it will be set to the current time. Ifexpis missing it will be set toiat + expiresIn. This is only applied ifpayloadis a JWT.currentDate: The current date for computingexpiresInoption. Defaults tonew Date().
Returns a Promise<string> resolving to the JWS token in Compact Serialization format.
Example (HS256 with string secret):
import { sign } from "unjwt/jws";
const payload = { message: "My important data" }; // Object payload
const key = await generateKey("HS256", { toJWK: true }); // Generates a JWK_oct
const token = await sign(payload, key);
console.log(token);
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Example (RS256 with CryptoKey):
import { sign } from "unjwt/jws";
import { generateKey } from "unjwt/jwk";
const payload = { userId: 123, permissions: ["read"] };
const { privateKey } = await generateKey("RS256"); // Generates a CryptoKeyPair
const token = await sign(payload, privateKey, { alg: "RS256" });
console.log(token);
// eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...Verifies a JWS token.
jws: The JWS token string.key: The verification key (CryptoKey,JWK,JWKSet,Uint8Array, or aKeyLookupFunction). AKeyLookupFunctionhas the signature(header: JWSProtectedHeader, jws: string) => Promise<CryptoKey | JWK | JWKSet | Uint8Array> | CryptoKey | JWK | JWKSet | Uint8Array.options(optional):algorithms: An array of allowed JWSalgvalues. If not provided, thealgfrom the JWS header is used.validateJWT: Unless false, will parse payload as JWT and validate claims if applicable (typ includes "jwt", case insensitive). Defaultundefined.critical: An array of JWS header parameter names that the application understands and processes.
Returns a Promise<JWSVerifyResult<T>> which is an object { payload: T, protectedHeader: JWSProtectedHeader }.
The payload type T can be JWTClaims (object), string, or Uint8Array depending on the JWS content and headers.
Example (HS256):
import { verify } from "unjwt/jws";
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // From HS256 sign example
const secret = "supersecretkey";
const { payload, protectedHeader } = await verify(token, secret);
console.log(payload); // { message: "My important data" }
console.log(protectedHeader); // { alg: "HS256", typ: "JWT" }Example (RS256 with key lookup):
import { verify } from "unjwt/jws";
import { generateKey } from "unjwt/jwk";
const token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."; // From RS256 sign example
// Example: using a key lookup function
const { publicKey: rsaPublicKey } = await generateKey("RS256"); // For example purposes, assume publicKey is stored/fetched during lookup
const keyLookup = async (header) => {
if (header.alg === "RS256" /* && header.kid === 'expected-kid' */) {
return rsaPublicKey;
}
throw new Error("Unsupported algorithm or key not found");
};
const { payload } = await verify(token, keyLookup, { algorithms: ["RS256"] });
console.log(payload); // { userId: 123, permissions: ["read"] }JWE (JSON Web Encryption, RFC 7516)
Functions to encrypt and decrypt data according to the JWE specification.
Encrypts data to produce a JWE token.
payload: The data to encrypt (string,Uint8Array, or a JSON-serializableobject).key: The Key Encryption Key (KEK) or password (CryptoKey,JWK,string, orUint8Array).options: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.enc: (Required) The JWE Content Encryption algorithm (e.g.,"A128GCM","A256CBC-HS512"), defaults depends on the key provided.protectedHeader: An object for JWE Protected Header parameters (e.g.,kid,typ,cty,crit,apu,apv,p2s,p2c). Ifpayloadis an object andtypis not set, it defaults to"JWT".cek: (Optional) Provide your own Content Encryption Key (CryptoKeyorUint8Array).contentEncryptionIV: (Optional) Provide your own Initialization Vector for content encryption (Uint8Array).- Other algorithm-specific options like
p2s,p2c(for PBES2),keyManagementIV,ecdhPartyUInfo,ecdhPartyVInfo.
Returns a Promise<string> resolving to the JWE token in Compact Serialization format.
Example (PBES2 password-based encryption):
import { encrypt } from "unjwt/jwe";
const plaintext = "Secret message for password protection";
const password = "myVeryStrongPassword123!";
// Fallback to PBES2-HS256+A128KW and A128GCM if no `alg` and `end` are provided
const jweToken = await encrypt(plaintext, password);
console.log(jweToken);
// JWE token string...Example (A128KW with A128GCM):
import { encrypt } from "unjwt/jwe";
import { generateKey } from "unjwt/jwk";
const payload = { data: "sensitive information" };
const kek = await generateKey("A128KW"); // AES Key Wrap key
const jweToken = await encrypt(payload, kek, {
alg: "A128KW",
enc: "A128GCM",
protectedHeader: { kid: "aes-key-1" },
});
console.log(jweToken);
// eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwia2lkIjoiYWVzLWtleS0xIn0...Decrypts a JWE token.
jwe: The JWE token string.key: The Key Decryption Key (KDK) or password (CryptoKey,JWK,string,Uint8Array, or aJWEKeyLookupFunction). AJWEKeyLookupFunctionhas the signature(header: JWEHeaderParameters) => Promise<CryptoKey | JWK | string | Uint8Array> | CryptoKey | JWK | string | Uint8Array.options(optional):algorithms: Array of allowed JWE Key Managementalgvalues.validateJWT: Unless false, will parse payload as JWT and validate claims if applicable (typ includes "jwt", case insensitive). Defaultundefined.encryptionAlgorithms: Array of allowed JWE Content Encryptionencvalues.critical: Array of JWE header parameter names that the application understands.unwrappedKeyAlgorithm: (ForunwrapKeyinternally) Algorithm details for the CEK after unwrapping.keyUsage: (ForunwrapKeyinternally) Intended usages for the unwrapped CEK.
Returns a Promise<JWEDecryptResult<T>> which is an object { payload: T, protectedHeader: JWEHeaderParameters, cek: Uint8Array, aad: Uint8Array }.
The payload type T can be JWTClaims (object) or string.
Example (PBES2 password-based decryption):
import { decrypt } from "unjwt/jwe";
// const jweToken = ...; // From PBES2 encrypt example
// const password = "myVeryStrongPassword123!";
const { payload } = await decrypt(jweToken, password);Example (A128KW with A128GCM):
import { decrypt } from "unjwt/jwe";
// const jweToken = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwia2lkIjoiYWVzLWtleS0xIn0...";
// const kek = ...; // The same AES Key Wrap key used for encryption
async function decryptData(jweToken: string, kek: CryptoKey) {
try {
const { payload, protectedHeader, cek } = await decrypt(jweToken, kek, {
algorithms: ["A128KW"],
encryptionAlgorithms: ["A128GCM"],
});
console.log("Decrypted Plaintext:", payload);
console.log("Protected Header:", protectedHeader);
// console.log("CEK (Content Encryption Key):", cek);
} catch (error) {
console.error("Decryption failed:", error);
}
}JWK (JSON Web Key, RFC 7517)
Utilities for working with JSON Web Keys.
Generates a cryptographic key.
alg: The JWA algorithm identifier for the key to be generated (e.g.,"HS256","RS256","ES256","A128KW","A128GCM","A128CBC-HS256").options(optional):toJWK: Iftrue, returns the key(s) in JWK format. Otherwise, returnsCryptoKey(s) orUint8Array(for composite keys like AES-CBC-HS*). Defaultfalse.extractable: Boolean, whether the generatedCryptoKeycan be exported. Defaulttrue.keyUsage: Array ofKeyUsagestrings. Defaults are algorithm-specific.modulusLength: For RSA keys (e.g.,2048,4096). Default2048.publicExponent: For RSA keys. Defaultnew Uint8Array([0x01, 0x00, 0x01]).
Returns a Promise resolving to CryptoKey, CryptoKeyPair, Uint8Array (for composite keys), JWK, or { privateKey: JWK, publicKey: JWK } depending on alg and options.toJWK.
Examples:
import { generateKey } from "unjwt/jwk";
// Generate an HS256 CryptoKey
const hmacKey = await generateKey("HS256");
console.log(hmacKey); // CryptoKey
// Generate an RS256 CryptoKeyPair
const rsaKeyPair = await generateKey("RS256", { modulusLength: 2048 });
console.log(rsaKeyPair.publicKey); // CryptoKey
console.log(rsaKeyPair.privateKey); // CryptoKey
// Generate an ES384 key pair as JWKs
const ecJwks = await generateKey("ES384", { toJWK: true });
console.log(ecJwks.publicKey); // JWK
console.log(ecJwks.privateKey); // JWK
// Generate a composite key for A128CBC-HS256 as Uint8Array
const aesCbcHsKeyBytes = await generateKey("A128CBC-HS256");
console.log(aesCbcHsKeyBytes); // Uint8Array (32 bytes: 16 for AES, 16 for HMAC)
// Generate an A256GCM key as a JWK
const aesGcmJwk = await generateKey("A256GCM", { toJWK: true });
console.log(aesGcmJwk); // JWKDerives a key from a password using PBKDF2 for PBES2 algorithms.
password: The password (stringorUint8Array).alg: The PBES2 algorithm (e.g.,"PBES2-HS256+A128KW").options:salt: The salt (Uint8Array, at least 8 octets).iterations: The iteration count (positive integer).toJWK: Iftrue, returns aJWK_oct. Otherwise,CryptoKey. Defaultfalse.extractable: Boolean forCryptoKey. DefaultfalseunlesstoJWKis true.keyUsage: ForCryptoKey. Default["wrapKey", "unwrapKey"].
Returns a Promise resolving to CryptoKey or JWK_oct.
Example:
import { deriveKeyFromPassword } from "unjwt/jwk";
import { randomBytes, textEncoder } from "unjwt/utils";
const password = "mySecretPassword";
const salt = randomBytes(16);
const iterations = 4096;
const derivedKey = await deriveKeyFromPassword(password, "PBES2-HS384+A192KW", {
salt,
iterations,
});
console.log(derivedKey); // CryptoKey for AES-KW (192-bit)
const derivedJwk = await deriveKeyFromPassword(password, "PBES2-HS512+A256KW", {
salt,
iterations,
toJWK: true,
});
console.log(derivedJwk); // JWK_oct { kty: "oct", k: "...", alg: "A256KW" }Imports a key from various formats. This is a flexible wrapper.
keyMaterial: The key to import. Can be:CryptoKey: Returned directly.Uint8Array: Returned directly (treated as raw symmetric key bytes).string: Encoded toUint8Arrayand returned.JWK_oct(symmetric JWK withkproperty): Thekvalue is Base64URL decoded and returned asUint8Array.- Other
JWKtypes (asymmetric): Imported into aCryptoKey.
alg(optional): The JWA algorithm string. Required when importing asymmetric JWKs (e.g., RSA, EC) to provide context forcrypto.subtle.importKey.
Returns a Promise resolving to CryptoKey or Uint8Array.
Examples:
import { importKey } from "unjwt/jwk";
import { textEncoder, base64UrlDecode } from "unjwt/utils";
// Import raw symmetric key bytes
const rawBytes = textEncoder.encode("a-32-byte-long-secret-key-123"); // 32 bytes for AES-256 or HS256
const symmetricKeyBytes = await importKey(rawBytes);
console.log(symmetricKeyBytes); // Uint8Array
// Import a symmetric JWK (kty: "oct")
const octJwk = {
kty: "oct",
k: "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr0", // Example key
};
const importedOctBytes = await importKey(octJwk); // Returns Uint8Array
console.log(importedOctBytes); // Uint8Array (decoded from k)
// Import an RSA Public Key JWK
const rsaPublicJwk = {
kty: "RSA",
n: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3ok92YEnjsADC4Ue87zwRzH2J-TCwlcQrY3E9gGZJZL2g_2_5QjLhL0gR0xYj04_N4M",
e: "AQAB",
alg: "RS256",
kid: "rsa-pub-1",
};
const rsaPublicKey = await importKey(rsaPublicJwk, "RS256"); // 'alg' is crucial here
console.log(rsaPublicKey); // CryptoKeyExports a CryptoKey to JWK format.
key: TheCryptoKeyto export (must beextractable).jwk(optional): A partialJWKobject to merge with the exported properties (e.g., to addkid,use, or overridealg).
Returns a Promise<JWK>.
Example:
import { generateKey, exportKey } from "unjwt/jwk";
const { publicKey } = await generateKey("ES256"); // Generates an extractable CryptoKey
const jwk = await exportKey(publicKey, { kid: "ec-key-001", use: "sig" });
console.log(jwk);
// {
// kty: 'EC',
// crv: 'P-256',
// x: '...',
// y: '...',
// ext: true,
// key_ops: [ 'verify' ], // or as per generation
// kid: 'ec-key-001',
// use: 'sig'
// }Wraps a Content Encryption Key (CEK).
alg: The JWA key management algorithm (e.g.,"A128KW","RSA-OAEP").keyToWrap: The CEK to wrap (CryptoKeyorUint8Array).wrappingKey: The Key Encryption Key (KEK) (CryptoKey,JWK, or passwordstring/Uint8Arrayfor PBES2).options(optional): Algorithm-specific options (e.g.,p2s,p2cfor PBES2;ivfor AES-GCMKW).
Returns a Promise<WrapKeyResult> containing encryptedKey and other parameters like iv, tag, epk, p2s, p2c as needed by the algorithm.
Example (AES Key Wrap):
import { wrapKey, generateKey } from "unjwt/jwk";
import { randomBytes } from "unjwt/utils";
const cekToWrap = randomBytes(32); // e.g., a 256-bit AES key as Uint8Array
const kek = await generateKey("A128KW"); // 128-bit AES Key Wrap key
const { encryptedKey } = await wrapKey("A128KW", cekToWrap, kek);
console.log("Wrapped CEK:", encryptedKey); // Uint8ArrayUnwraps a Content Encryption Key (CEK).
alg: The JWA key management algorithm.wrappedKey: The encrypted CEK (Uint8Array).unwrappingKey: The Key Decryption Key (KDK).options(optional):returnAs: Iffalse, returnsUint8Array. Iftrue(default) or undefined, returnsCryptoKey.unwrappedKeyAlgorithm:AlgorithmIdentifierfor the imported CEK ifreturnAsistrue.keyUsage:KeyUsage[]for the imported CEK ifreturnAsistrue.extractable: Boolean for the imported CEK.- Other algorithm-specific options (e.g.,
p2s,p2c,iv,tag,epk).
Returns a Promise resolving to the unwrapped CEK as CryptoKey or Uint8Array.
Example (AES Key Unwrap):
import { unwrapKey, generateKey } from "unjwt/jwk";
// const encryptedKey = ...; // From wrapKey example
// const kdk = ...; // Same KEK used for wrapping
async function unwrapMyKey(encryptedKey: Uint8Array, kdk: CryptoKey) {
const unwrappedCekBytes = await unwrapKey("A128KW", encryptedKey, kdk, {
returnAs: false, // Get raw bytes
});
console.log("Unwrapped CEK (bytes):", unwrappedCekBytes); // Uint8Array
const unwrappedCekCryptoKey = await unwrapKey("A128KW", encryptedKey, kdk, {
returnAs: true, // Get CryptoKey
unwrappedKeyAlgorithm: { name: "AES-GCM", length: 256 }, // Specify CEK's intended alg
keyUsage: ["encrypt", "decrypt"],
});
console.log("Unwrapped CEK (CryptoKey):", unwrappedCekCryptoKey); // CryptoKey
}Imports a key from a PEM-encoded string and converts it to a JWK.
pem: The PEM-encoded string (including-----BEGIN ...-----and-----END ...-----markers).pemType: The type of PEM encoding:"pkcs8": For private keys in PKCS#8 format."spki": For public keys in SPKI format."x509": For X.509 certificates (extracts the public key).
alg: The JWA algorithm identifier (e.g.,"RS256","ES256"). This is crucial forcrypto.subtle.importKeyto understand the key's intended algorithm and for setting the'alg'field in the resulting JWK.importOptions(optional): Options for the underlyingcrypto.subtle.importKeycall:extractable: Boolean, whether the importedCryptoKeyshould be extractable. Defaults totrue.keyUsage: Array ofKeyUsagestrings for the importedCryptoKey.
jwkExtras(optional): An object containing additional properties to merge into the resulting JWK (e.g.,"kid","use").
Returns a Promise<JWK> resolving to the imported key as a JWK.
Example:
import { importJWKFromPEM } from "unjwt/jwk";
const rsaPublicJwk = await importJWKFromPEM(
provess.env.RSA_PEM_SPKI, // PEM string
"spki",
"RS256",
{ extractable: false },
{ kid: "my-rsa-key" }, // Additional properties to add to the JWK
);
console.log(rsaPublicJwk);
// {
// kty: 'RSA',
// alg: 'RS256',
// kid: 'my-rsa-key',
// n: '...',
// e: 'AQAB',
// ext: false,
// key_ops: [ 'verify' ]
// }Exports a JWK to a PEM-encoded string.
- jwk: The JWK to export.
- pemFormat: The desired PEM format:
- "pkcs8": For private keys in PKCS#8 format.
- "spki": For public keys in SPKI format.
- 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.
Returns a Promise<string> resolving to the PEM-encoded key string.
Example:
import { exportJWKToPEM } from "unjwt/jwk";
import { rsaJWK } from "./keys"; // Assuming you have JWKs in keys.ts
const rsaPrivatePem = await exportJWKToPEM(rsaJWK.private, "pkcs8");
console.log(rsaPrivatePem);
// -----BEGIN PRIVATE KEY-----
// MII...
// -----END PRIVATE KEY-----
const rsaPublicSpki = await exportJWKToPEM(
rsaJWK.public,
"spki",
"RS256", // this is required if `rsaJWK.public.alg` is undefined
);
console.log(rsaPublicSpki);
// -----BEGIN PUBLIC KEY-----
// MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQE...
// -----END PUBLIC KEY-----Warning
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).
The h3v1 adapter bundles session helpers that store data inside signed or encrypted JWTs.
useJWESession(event, config)encrypts the session payload with the providedsecret(password string or private/symmetric JWK). Use this when session data must remain confidential. (cookie'shttpOnly: trueby default)useJWSSession(event, config)signs, but does not encrypt, the session payload withconfig.key. Use this when clients may read the session content but you still need tamper protection. (cookie'shttpOnly: falseby default)
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).
import { defineEventHandler } from "h3";
import { useJWESession, useJWSSession, generateJWK } from "unjwt/adapters/h3v1";
const keys = await generateJWK("RS256"); // Make sure to persist these keys somewhere!
export default defineEventHandler(async (event) => {
const privateSession = await useJWESession(event, {
name: "app-session",
secret: process.env.SESSION_SECRET!, // or symmetric or asymmetric keypair
});
await privateSession.update((data) => ({
visits: (data.visits ?? 0) + 1,
}));
const publicSession = await useJWSSession(event, {
name: "app-session-public",
key: keys, // you can directly pass symmetric or asymmetric keypairs
maxAge: 60 * 60, // seconds
});
return {
encryptedSession: privateSession.data,
signedSession: publicSession.data,
};
});The h3v2 adapter bundles session helpers that store data inside signed or encrypted JWTs.
useJWESession(event, config)encrypts the session payload with the providedsecret(password string or private/symmetric JWK). Use this when session data must remain confidential. (cookie'shttpOnly: trueby default)useJWSSession(event, config)signs, but does not encrypt, the session payload withconfig.key. Use this when clients may read the session content but you still need tamper protection. (cookie'shttpOnly: falseby default)
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).
import { H3, HTTPError, serve } from "h3v2";
import {
type SessionConfigJWE,
type SessionConfigJWS,
useJWESession,
useJWSSession,
getJWESession,
updateJWSSession,
generateJWK,
} from "unjwt/adapters/h3v2";
const atJwk = await generateJWK("RS256"); // Make sure to persist these keys somewhere!
const jweOptions = {
key: "refresh_token_secret",
name: "refresh_token",
} satisfies SessionConfigJWE;
const jwsOptions = {
key: atJwk,
name: "access_token",
maxAge: 15 * 60, // 15 minutes
hooks: {
async onExpire({ event, config }) {
const refreshSession = await getJWESession(event, jweOptions);
if (!refreshSession.data.sub) {
// no valid refresh session, nothing to do
return;
}
console.log("Access token expired, refreshing...");
// refresh the access token
await updateJWSSession(event, config, {
sub: refreshSession.data.sub,
scope: refreshSession.data.scope,
});
},
},
} satisfies SessionConfigJWS;
const app = new H3();
app.post("/login", async (event) => {
const refreshSession = await useJWESession(event, jweOptions);
const accessSession = await useJWSSession(event, jwsOptions);
if (accessSession.data.sub) {
// user already logged in, return existing info
return {
accessToken: {
id: accessSession.id,
createdAt: accessSession.createdAt,
expiresAt: accessSession.expiresAt,
data: accessSession.data,
},
refreshSession: {
id: refreshSession.id,
createdAt: refreshSession.createdAt,
expiresAt: refreshSession.expiresAt,
data: refreshSession.data,
},
};
}
const data = (await event.req.json()) as {
username?: string;
password?: string;
};
if (!data.username || !data.password) {
throw new HTTPError("Username and password are required", { status: 400 });
}
// validate user credentials here
const claims = {
sub: data.username,
scope: ["read:profile"],
};
await accessSession.update(claims);
await refreshSession.update(claims);
return {
accessToken: {
id: accessSession.id,
createdAt: accessSession.createdAt,
expiresAt: accessSession.expiresAt,
data: accessSession.data,
},
refreshSession: {
id: refreshSession.id,
createdAt: refreshSession.createdAt,
expiresAt: refreshSession.expiresAt,
data: refreshSession.data,
},
};
});
serve(app);unjwt/utils exports several helpful functions:
base64UrlEncode(data: Uint8Array | string): stringbase64UrlDecode(str?: string, toString?: boolean): Uint8Array | string(Decodes to string by default, orUint8ArrayiftoStringisfalse)randomBytes(length: number): Uint8ArraytextEncoder: TextEncodertextDecoder: TextDecoder- Type guards:
isJWK(key),isCryptoKey(key),isCryptoKeyPair(keyPair)
local development
Originally developed by Johann Schopplich. Heavily inspired by Filip Skokan's work.
Published under the MIT license.
Made by community ๐
๐ค auto updated with automd