Skip to main content
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:
  • secret (string, optional) — The secret key used to sign the token. If not provided, Bun generates a random in-memory default secret (unique per thread).
  • options (object, optional):
OptionTypeDefaultDescription
expiresInnumber86400000Milliseconds until the token expires. Defaults to 24 hours.
encodingstring"base64url"Token encoding format: "base64", "base64url", or "hex".
algorithmstring"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:
  • token (string, required) — The token to verify.
  • options (object, optional):
OptionTypeDefaultDescription
secretstring(auto)The secret used to sign the token. If not provided, uses the same in-memory default as generate().
maxAgenumber86400000Maximum token age in milliseconds, independent of the token’s own expiresIn.
encodingstring"base64url"Must match the encoding used during generate().
algorithmstring"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;
}