Skip to content
/ jwt Public

A versatile JSON Web Token (JWT) library with cross-runtime compatibility

License

Notifications You must be signed in to change notification settings

cross-org/jwt

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

39 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@cross/jwt

JSR Version JSR Score

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.

Features

  • 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.

Installation

# For Deno
deno add @cross/jwt

# For Bun
bunx jsr add @cross/jwt

# For Node.js
npx jsr add @cross/jwt

API

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.

Supported algorithms

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

Usage

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

Issues or questions concerning the library can be raised at the github repository page.

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

A versatile JSON Web Token (JWT) library with cross-runtime compatibility

Resources

License

Stars

Watchers

Forks

Packages

No packages published