rembrembdocs

Bun provides a built-in API for generating and verifying CSRF (Cross-Site Request Forgery) tokens through Bun.CSRF. Tokens are signed with HMAC and include expiration timestamps to limit the token validity window.

https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bcsrf.ts

// Generate a token
const token = Bun.CSRF.generate("my-secret");

// Verify it
const isValid = Bun.CSRF.verify(token, { secret: "my-secret" });
console.log(isValid); // true

Bun.CSRF.generate()

Generate a CSRF token. The token contains a cryptographic nonce, a timestamp, and an HMAC signature, encoded as a string.

https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bgenerate.ts

const token = Bun.CSRF.generate("my-secret-key");

Parameters:

Option

Type

Default

Description

expiresIn

number

86400000

Milliseconds until the token expires. Defaults to 24 hours.

encoding

string

"base64url"

Token encoding format: "base64", "base64url", or "hex".

algorithm

string

"sha256"

HMAC algorithm: "sha256", "sha384", "sha512", "sha512-256", "blake2b256", or "blake2b512".

Returns: string — the encoded token.

https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bgenerate-options.ts

// Token that expires in 1 hour, encoded as hex
const token = Bun.CSRF.generate("my-secret", {
  expiresIn: 60 * 60 * 1000,
  encoding: "hex",
});

// Using a different algorithm
const token2 = Bun.CSRF.generate("my-secret", {
  algorithm: "sha512",
});

Bun.CSRF.verify()

Verify a CSRF token. Returns true if the token is valid and has not expired, false otherwise.

https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bverify.ts

const isValid = Bun.CSRF.verify(token, { secret: "my-secret-key" });

Parameters:

Option

Type

Default

Description

secret

string

(auto)

The secret used to sign the token. If not provided, uses the same in-memory default as generate().

maxAge

number

86400000

Maximum token age in milliseconds, independent of the token’s own expiresIn.

encoding

string

"base64url"

Must match the encoding used during generate().

algorithm

string

"sha256"

Must match the algorithm used during generate().

Returns: boolean

https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bverify-options.ts

// Verify a hex-encoded token
const isValid = Bun.CSRF.verify(hexToken, {
  secret: "my-secret",
  encoding: "hex",
});

// Enforce a shorter max age than what the token was generated with
const isValid2 = Bun.CSRF.verify(token, {
  secret: "my-secret",
  maxAge: 60 * 1000, // reject tokens older than 1 minute
});

Using with Bun.serve()

A typical pattern is to generate a token when rendering a form, embed it in a hidden field, and verify it when the form is submitted.

https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bserver.ts

const SECRET = process.env.CSRF_SECRET || "my-secret";

const server = Bun.serve({
  routes: {
    "/form": () => {
      const token = Bun.CSRF.generate(SECRET);

      return new Response(
        `<form method="POST" action="/submit">
          <input type="hidden" name="_csrf" value="${token}" />
          <input type="text" name="message" />
          <button type="submit">Send</button>
        </form>`,
        { headers: { "Content-Type": "text/html" } },
      );
    },

    "/submit": {
      POST: async req => {
        const formData = await req.formData();
        const csrfToken = formData.get("_csrf");

        if (typeof csrfToken !== "string" || !Bun.CSRF.verify(csrfToken, { secret: SECRET })) {
          return new Response("Invalid CSRF token", { status: 403 });
        }

        return new Response("OK");
      },
    },
  },
});

console.log(`Listening on ${server.url}`);

Default secret

If you omit the secret parameter in both generate() and verify(), Bun uses a random secret generated once per thread. This is convenient for single-thread applications but won’t work across multiple servers, workers, or after a restart.

https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bdefault-secret.ts

// Both calls use the same per-thread default secret within this runtime context.
const token = Bun.CSRF.generate();
const isValid = Bun.CSRF.verify(token); // true

For production use, always provide an explicit secret shared across your infrastructure.


TypeScript

https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35btypes.ts

type CSRFAlgorithm = "blake2b256" | "blake2b512" | "sha256" | "sha384" | "sha512" | "sha512-256";

interface CSRFGenerateOptions {
  expiresIn?: number;
  encoding?: "base64" | "base64url" | "hex";
  algorithm?: CSRFAlgorithm;
}

interface CSRFVerifyOptions {
  secret?: string;
  encoding?: "base64" | "base64url" | "hex";
  algorithm?: CSRFAlgorithm;
  maxAge?: number;
}

namespace Bun.CSRF {
  function generate(secret?: string, options?: CSRFGenerateOptions): string;
  function verify(token: string, options?: CSRFVerifyOptions): boolean;
}