Skip to content

Proposal: Native JWT API in Bun. #27727

@nickshiro

Description

@nickshiro

What is the problem this feature would solve?

Native JWT API for Bun

Summary

Add a built-in, zero-dependency JWT implementation directly into the Bun runtime, supporting signing, verification, and safe decoding.

Supported algorithms (minimum):

  • Symmetric: HS256, HS384, HS512
  • Asymmetric: RS256, RS384, RS512, ES256, ES384, ES512, EdDSA (Ed25519)

Goals:

  • dramatically better cold-start and runtime performance.
  • eliminate the need for third-party libraries (jsonwebtoken, jose, @elysiajs/jwt, etc.).
  • tight integration with Bun’s native crypto primitives (Web Crypto API + bun:crypto).

Motivation

JWT is one of the most widely used authentication mechanisms in HTTP servers, API gateways, edge functions, and microservices.

Today, virtually every Bun-based project is forced to pull in an external dependency:

  • jsonwebtoken slow, high allocation pressure, outdated API.
  • jose modern but still JavaScript-level -> significant overhead.
  • @elysiajs/jwt, bun-jwt, etc. wrappers that don’t solve GC and cold-start issues.

Bun already provides native high-performance APIs for very similar use cases:

  • Bun.password native password hashing (argon2id, bcrypt).
  • crypto.subtle low-level cryptography.
  • Bun.sign, Bun.verify convenient high-level wrappers.

Adding native JWT support is a natural next step in this philosophy: the most commonly needed crypto operations should be built-in.

Key benefits of a native implementation

  • fewer allocations and GC pauses.
  • significantly better cold starts (critical for serverless / edge environments).
  • fewer dependencies -> smaller attack surface, no node_modules bloat in cryptographic algorithm.
  • consistent API across all Bun projects (no more variations of verify(token, secret).

What is the feature you are proposing to solve the problem?

Proposed API Bun.jwt

declare namespace Bun.jwt {
  interface SignOptions {
    /* Signing key — string/Uint8Array for symmetric, CryptoKey for asymmetric */
    key: string | Uint8Array | CryptoKey;
    /* Algorithm. Defaults to "HS256" when key is string/Uint8Array */
    alg?: "HS256" | "HS384" | "HS512" | "RS256" | "RS384" | "RS512" | "ES256" | "ES384" | "ES512" | "EdDSA";
    /* Numeric seconds or duration string ("2h", "7d", "30m") */
    expiresIn?: number | string;
    notBefore?: number | string;
    /* Custom JOSE header fields (typ, kid, jku, etc.) */
    header?: Record<string, unknown>;
    /* `iat` is set automatically unless explicitly provided */
    iat?: number;
  }

  function sign(
    payload: object | string | Uint8Array,
    options: SignOptions
  ): Promise<string>;

  interface VerifyOptions {
    key: string | Uint8Array | CryptoKey;
    /* If provided, only these algorithms are accepted */
    algorithms?: string[];
    /* Allowed clock skew in seconds */
    clockTolerance?: number;
    /* If true — skip exp, nbf, iat validation (mostly for testing) */
    ignoreExpiration?: boolean;
  }

  interface JwtResult {
    payload: unknown;
    header: Record<string, unknown>;
    /* Raw token parts (useful for advanced use-cases) */
    token?: { header: string; payload: string; signature: string };
  }

  function verify(token: string, options: VerifyOptions): Promise<JwtResult>;

  interface DecodeOptions {
    complete?: boolean; // include header + payload + signature parts
  }

  function decode(token: string, options?: DecodeOptions): JwtResult | { payload: unknown };
}

What alternatives have you considered?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions