A versatile JSON Web Token (JWT) library for Deno, Bun, and Node.js providing cross-runtime compatibility.
Part of the @cross suite - check out our growing collection of cross-runtime tools at github.com/cross-org.
- Secure Cryptography: Supports HMAC, RSA, RSA-PSS and ECDSA signing algorithms for robust JWT protection.
- Cross-Platform: Functions seamlessly across Deno, Bun, and Node.js environments.
- Intuitive API: Provides simple-to-use functions for JWT creation, parsing, signing, and verification.
- Key Management: Includes helpers for generating HMAC secret keys and RSA or ECDSA key pairs.
# For Deno
deno add @cross/jwt
# For Bun
bunx jsr add @cross/jwt
# For Node.js
npx jsr add @cross/jwt
See docs on jsr.io for details.
Sign and validate
signJWT(payload: JWTPayload, key: CryptoKey | string | false, options?: JWTOptions): Promise<string>
// Create and sign a JWT from a string directly, uses a HS256 key by default.
const jwt = await signJWT({ hello: "world" }, "mySuperSecretAtLeast32CharsLong!");
// Create and sign a JWT from a string directly, using a HS512 algorithm.
const jwt = await signJWT({ hello: "world" }, "Secret my Super Secret at least 64 bytes long for HS512 algorithm!!!!", {
algorithm: "HS512",
});
// Create and sign a JWT from a CryptoKey (secret based or private key), and opting out of writing IAT claim when signing.
// Using a cryptokey the library will parse algorithm from the supplied key
const jwt = await signJWT({ hello: "world" }, cryptoKey, { setIat: false });
// Create a JWT without signing it, essentially an unsecure JWT.
const jwt = await signJWT({ hello: "world" }, false);
validateJWT(jwt: string, key: CryptoKey | string | false, options?: JWTOptions): Promise<JWTPayload>
// Validate and parse JWT with a string secret directly, uses a HS256 key by default.
const data = await validateJWT(jwt, "mySuperSecretAtLeast32CharsLong!");
// Validate and parse JWT with a string secret directly, uses a HS256 key by default.
const data = await validateJWT(jwt, "Secret my Super Secret at least 64 bytes long for HS512 algorithm!!!!", {
algorithm: "HS512",
});
// Validate and parse JWT with a CryptoKey (secret based or public key).
// Using a cryptokey the library will parse algorithm from the supplied key.
const data = await validateJWT(jwt, cryptoKey);
// Parsing a unsecure JWT
const data = await validateJWT(jwt, false);
unsafeParseJWT(jwt: string): JWTPayload
// "unsafely" parse a JWT without cryptokey, will return the payload.
const unsafeData = unsafeParseJWT(jwt);
unsafeParseJOSEHeader(jwt: string): JOSEHeader
// "unsafely" parse the JOSE header of a JWT without cryptokey.
const unsafeData = unsafeParseJOSEHeader(jwt);
Helper Functions
generateKey(keyStr: string, optionsOrAlgorithm?: SupportedKeyAlgorithms | Options): Promise<CryptoKey>
// Generates a HS256 key by default
const key = await generateKey(stringSecret);
// Generates a HS512 key
const key = await generateKey(stringSecret, "HS512");
// Generates a HS256 key by using options object (see GenerateKeyOptions)
const key = await generateKey(stringSecret, { algorithm: "HS512" });
generateKeyPair(optionsOrAlgorithm?: KeyPairOptions): Promise<CryptoKeyPair>
// Generates a RS256 key pair by default.
const { privateKey, publicKey } = await generateKeyPair();
// Generates a HS512 key pair.
const { privateKey, publicKey } = await generateKeyPair("RS512");
// Generates a HS512 key pair by using options object (see GenerateKeyPairOptions).
const key = await generateKeyPair({ algorithm: "HS512" });
exportPEMKey(key: CryptoKey, filePathOrOptions?: string | ExportPEMKeyOptions): Promise<string>
(Experimental)importPEMKey(keyDataOrPath: string, algorithm: SupportedKeyPairAlgorithms): Promise<CryptoKey>
(Experimental)
// Experimental.
// Generate and export RS512 keys in PEM-format. (filePath and write mode can be supplied as optional second parameter at export)
const { privateKey, publicKey } = await generateKeyPair("RS512");
await exportPEMKey(privateKey, "./private_key_RS512.pem");
await exportPEMKey(publicKey, "./public_key_RS512.pem");
// Import RS512 keys from PEM-format.
const importedPrivateKey = await importPEMKey("./private_key_RS512.pem", "RS512");
const importedPublicKey = await importPEMKey("./public_key_RS512.pem", "RS512");
GenerateKeyOptions Object
The GenerateKeyOptions
object can be used to provide flexibility when generating HMAC keys:
/**
* Options for key generation
*/
interface GenerateKeyOptions {
//The HMAC algorithm to use for key generation. Defaults to 'HS256'.
algorithm?: SupportedKeyAlgorithms;
// Use with caution, as shorter keys are less secure.
allowInsecureKeyLengths?: boolean;
}
GenerateKeyPairOptions Object
The GenerateKeyPairOptions
object can be used to provide flexibility when generating RSA key pairs:
/**
* Options for key pair generation.
*/
interface GenerateKeyPairOptions {
//The algorithm to use for key pair generation. Defaults to 'RS256'.
algorithm?: SupportedKeyPairAlgorithms;
// The desired length of the RSA modulus in bits. Larger values offer greater
// security, but impact performance. A common default is 2048.
modulusLength?: number;
// If true, allows generation of key pairs with modulus length shorter than recommended security guidelines.
// Use with caution, as shorter lengths are less secure.
allowInsecureModulusLengths?: boolean;
}
JWTOptions Object
The JWTOptions
object can be used to provide flexibility when creating JWTs:
/**
* Options for customizing JWT creation and parsing behavior.
*/
interface JWTOptions {
// Algorithm to use.
// Algorithm for signing should be the same as key algorithm, used to enforce the use of a specific algorithm (can be useful for security reasons). (default: parsed from the supplied key)
algorithm?: string;
// If true, the 'iat' (issued at) claim will be automatically added to the JWT payload during creation. (default: true)
setIat?: boolean;
// If true, the 'exp' (expiration time) claim will be validated during creation and parsing.
validateExp?: boolean;
// If true, the 'nbf' (not before) claim will be validated during creation and parsing.
validateNbf?: boolean;
//The number of seconds of leeway to allow for clock skew during expiration validation. (default: 60)
clockSkewLeewaySeconds?: number;
//Salt length for RSA-PSS sign and verify (default: 32).
saltLength?: number;
// A duration string (e.g., "1h", "30m") specifying the expiration time claim relative to the current time.
// Cannot be used if the `exp` claim is explicitly set in the payload.
expiresIn?: string;
//A duration string (e.g., "5m") specifying the "not before" time claim relative to the current time.
//Cannot be used if the `nbf` claim is explicitly set in the payload.
notBefore?: string;
// Additional claims to include as part of the JWT's JOSE header.
additionalHeaderClaims?: JOSEHeader;
}
Working with JWT Headers
Some usage scenarios, such as interoperating with OIDC providers that set key identifier (kid
) header claims in the
JWTs they issue, require JWT header introspection. Similarly, it is sometimes necessary to create tokens with additional
header claims or override existing claims (e.g., the typ
claim).
The additionalHeaderClaims
property in the JWTOptions
provide the means to set/override header claims in tokens
created through signJWT
. Conversely, the unsafeParseJOSEHeader
function reads the header claims of a token without
validating it.
Algorithm | Description |
---|---|
HS256 | HMAC using SHA-256 |
HS384 | HMAC using SHA-384 |
HS512 | HMAC using SHA-512 |
RS256 | RSASSA-PKCS1-v1_5 using SHA-256 |
RS384 | RSASSA-PKCS1-v1_5 using SHA-384 |
RS512 | RSASSA-PKCS1-v1_5 using SHA-512 |
ES256 | ECDSA using P-256 and SHA-256 |
ES384 | ECDSA using P-384 and SHA-384 |
PS256 | RSASSA-PSS using SHA-256 and MGF1 with SHA-256 |
PS384 | RSASSA-PSS using SHA-384 and MGF1 with SHA-384 |
PS512 | RSASSA-PSS using SHA-512 and MGF1 with SHA-512 |
none | Unsecured JWT |
The most common way to sign and verify JWTs is using HMAC.
import { signJWT, validateJWT } from "@cross/jwt";
// Signing the JWT with HS256 by default, here with a string secret used to generate a key.
const secret = "mySuperSecretAtLeast32CharsLong!";
const jwt = await signJWT({ hello: "world" }, secret);
// Verifying and parsing the content of the JWT.
const data = await validateJWT(jwt, secret);
console.log(data);
//Outputs: { hello: "world", iat: 1712516617 }
Here is how you can use it with a RSA key pair.
import { generateKeyPair, signJWT, validateJWT } from "@cross/jwt";
// Signing the JWT with a RSA private key. You can generate a key pair with the generateKeyPair() helper function.
const { privateKey, publicKey } = await generateKeyPair();
const jwt = await signJWT({ userId: 123 }, privateKey);
// Verifying and parsing the content of the JWT with the public key.
const data = await validateJWT(jwt, publicKey);
console.log(data);
//Outputs: { userId: 123, iat: 1712516617 }
Usage with custom options to disable writing the 'iat' (issued at) claim to the JWT payload during creation.
import { signJWT, validateJWT } from "@cross/jwt";
const options = {
setIat: false,
};
const secret = "mySuperSecretAtLeast32CharsLong!";
const jwt = await signJWT({ hello: "world" }, secret, options);
const data = await validateJWT(jwt, secret);
console.log(data);
//Outputs: { hello: "world"}
Full example with standard JWT claims. See RFC 7519
import { generateKey, signJWT, validateJWT } from "@cross/jwt";
import type { GenerateKeyOptions, JWTPayload } from "@cross/jwt";
// Optional key generation, you could have a key already.
// HS512 suggests 64 byte secret. Can be omitted with allowInsecureKeyLengths option.
const keyOptions: GenerateKeyOptions = { algorithm: "HS512" };
const secret = "mySuperSecretAtLeast64CharsLongmySuperSecretAtLeast64CharsLong!!";
const key = await generateKey(secret, keyOptions);
// JWT content with standard claims.
const data: JWTPayload = {
// Standard Claims
iss: "https://your-api.com", // Issuer
sub: "user12345", // Subject
aud: ["clientApp1", "clientApp2"], // Audience (array in this case)
exp: Math.floor(Date.now() / 1000) + (60 * 60), // Expires in 1 hour
nbf: Math.floor(Date.now() / 1000), // Not Before (effective now)
iat: Math.floor(Date.now() / 1000), // Issued At
// Custom Properties
userId: 12345,
roles: ["admin", "editor"],
};
// Sign the JWT
const jwt = await signJWT(data, key);
// Validate the JWT
const validatedData = await validateJWT(jwt, key);
Using a unsecured JWT. Cases in which the JWT content is secured by a means other than a signature and/or encryption contained within the JWT.
//Supply false or "none" instead of key for unsecured JWT.
const jwt = await signJWT({ hello: "world" }, false);
const data = await validateJWT(jwt, "none");
Generate a RSA key pair with custom Modulus length. (only key generation)
const keyPairOptions: GenerateKeyPairOptions = { algorithm: "RS256", modulusLength: 4096 };
const { privateKey, publicKey } = await generateKeyPair(keyPairOptions);
Generate a HMAC key with short insecure secret, not recommended. (only key generation)
const keyOptions: GenerateKeyOptions = { algorithm: "HS512", allowInsecureKeyLengths: true };
const insecureString = "shortString";
const key = await generateKey(insecureString, keyOptions);
Export/import a key pair to and from local files. (Experimental)
// Generate and export RS512 keys in PEM-format.
const { privateKey, publicKey } = await generateKeyPair("RS512");
await exportPEMKey(privateKey, "./private_key_RS512a.pem");
await exportPEMKey(publicKey, "./public_key_RS512a.pem");
// Import RS512 keys from PEM-format.
const importedPrivateKey = await importPEMKey("./private_key_RS512.pem", "RS512");
const importedPublicKey = await importPEMKey("./public_key_RS512.pem", "RS512");
Issues or questions concerning the library can be raised at the github repository page.
This project is licensed under the MIT License - see the LICENSE file for details.