Auth middleware in Bun.js: JWT, sessions, API keys, and multi-tenant context without chaos
A practical deep dive into auth middleware in Bun.js: how to build JWT, session, and API-key checks, where to keep tenant context, how to separate 401/403, what to cache, how to avoid token leaks, and which best practices to borrow from OWASP, Hono, and Elysia.

Bun.js Middleware Production Guide 2026
A series about production middleware in Bun.js: overview, security, performance, observability, rate limiting, body parsing, WebSocket/SSE, and request pipeline testing.
All articles in this guide
01
Bun.js middleware in 2026: overview, best practices, and anti-patterns
The base mental model for middleware in native Bun, Hono, and Elysia, with examples, optimization, and a roadmap for the next deep dive articles.
02
Auth middleware in Bun.js: JWT, sessions, API keys, and multi-tenant context
How to build auth middleware in Bun correctly: check order, cache, token rotation, tenant context, 401/403 errors, and testing.
03
Rate limiting in Bun.js: in-memory, Redis, sliding window, and edge cases
A detailed breakdown of rate limiting for Bun APIs: algorithms, Redis, distributed limits, abuse protection, and graceful degradation.
04
Observability middleware in Bun.js: logs, request id, tracing, and latency budgets
How to add request id, structured logs, timing headers, an OpenTelemetry-like flow, and avoid turning logging into a bottleneck.
05
Body parsing and validation in Bun.js: JSON, uploads, streams, and payload limits
How to safely read request bodies in Bun, where to place limits, and how not to break streams, uploads, idempotency, and schema validation.
The most dangerous auth bugs rarely look like “a user without a token got into the system”. More often, the token is valid, the session exists, the API key is real, but the middleware attached the request to the wrong tenant, skipped a scope check, mixed up 401 and 403, or logged a secret.
Bun gives you a fast HTTP layer, but auth is not a runtime trick. It is a contract: which credentials are accepted, where they are stored, how they are validated, who creates user context, where authorization starts, and how all of it is tested.
In this article, we will not build “another login tutorial”. We will break down production patterns for auth middleware in Bun APIs.
This is the second chapter in the series about Bun middleware. The first one covered the general pipeline model; this one focuses on the auth layer: where it should stay thin, where it needs storage, and where it stops being middleware.
decode without verify is not auth. [4]There is no single “correct” auth mechanism. There are trade-offs: stateless verification, controlled logout, machine-to-machine access, rotation, revocation, and UX.
| Mechanism | When it fits | What to verify | Main risk |
|---|---|---|---|
| JWT access token | Mobile/API clients, service-to-service, distributed verification | exp, iss, aud, signature, algorithm, subject, scopes | Difficult revocation model and the temptation to create long-lived tokens |
| Server-side session | Browser apps, dashboards, SaaS products that need controlled logout | Session id in a secure cookie, session store, expiry, device/risk metadata | Session fixation, insecure cookies, weak store cleanup |
| API key | Integrations, webhooks, internal automations, machine clients | Hashed key lookup, scope, owner, environment, last used, rotation | Plaintext key storage and keys without scopes or expiry policy |
| Hybrid | BFF, enterprise SaaS, admin panels plus public API | Session for browser, JWT/API keys for services | Different auth models without unified audit and error contracts |
JWT, sessions, and API keys solve different jobs: stateless access, browser logout control, and machine-to-machine access.
Section credential-map screenshotSummary
For browser SaaS, usually start with sessions. For external APIs, use API keys or short-lived JWT. For distributed systems, use JWT plus revocation or introspection where the risk is high.
In native Bun.serve, you can build an auth layer without a framework. The important part is not mutating global state and not hiding context in module-level variables. Pass context explicitly: either through a wrapper or through your own AppRequestContext.
One simple pattern looks like this:
type AuthContext = {
subjectId: string;
tenantId: string;
scopes: string[];
method: "jwt" | "session" | "api-key";
};
type AppContext = { auth: AuthContext | null; requestId: string };
type Handler = (req: Request, ctx: AppContext) => Promise<Response> | Response;
type Middleware = (next: Handler) => Handler;
const unauthorized = () =>
Response.json({ error: "unauthorized" }, { status: 401 });
const withAuth: Middleware = (next) => async (req, ctx) => {
const header = req.headers.get("authorization");
if (!header?.startsWith("Bearer ")) return unauthorized();
const token = header.slice("Bearer ".length);
const auth = await verifyAccessToken(token);
if (!auth) return unauthorized();
return next(req, { ...ctx, auth });
};
Bun.serve({
fetch: withAuth(async (_req, ctx) => {
return Response.json({ subjectId: ctx.auth?.subjectId });
}),
});This example intentionally does not show the implementation of verifyAccessToken, because it depends on your JWT/session/API-key design. The important shape is this: middleware does not read business resources, does not perform permissions on invoices/projects/orders, and does not create side effects. It creates auth context or returns 401.
What matters here
Context must be request-scoped and explicit. If auth state lives in a global variable, singleton, or mutable module object, you create a risk of leakage between requests.
If you do not want to write the entire auth layer by hand, Hono and Elysia provide ready-made primitives. But ready-made middleware does not remove your responsibility for issuer, audience, scopes, tenant, and error contracts.
Honojwt()verifies the token and puts the payload into context viac.get('jwtPayload'); the middleware looks for theAuthorizationheader unless a cookie option is configured. [2]
HonobearerAuth()works well for API tokens and providesverifyToken, custom errors, realm, prefix, and headerName. [3]
Summary
Built-in middleware is useful for parsing and basic verification. But tenant resolution, scopes, audit, and business authorization remain application design decisions.
In SaaS, the most common auth mistake does not look like “there is no token”. It looks like “a valid user was able to work inside someone else's tenant”. The reason is simple: the middleware took x-tenant-id from the request and did not verify that the subject actually has access to that tenant.
Tenant context must be derived from the credential or checked against server-side membership. If the user can switch tenants, a header or path param can be an input, but not an authority.
The correct flow is: verify credential, get subject, get allowed tenants/scopes, compare requested tenant against the allowed list, and attach the resolved tenant to context. If the tenant is not allowed, that is 403, not 401.
x-tenant-id as the source of truth.Tenant must be resolved and verified by the server. A header or path param can be the user's request, but it is not proof of access.
Section multi-tenant-context screenshotIncorrect auth errors create problems for clients, logs, and security reviews. In a Bun pipeline, it is better to define the contract upfront.
Credential is missing or invalid
Missing Authorization, malformed token, invalid signature, expired session, API key not found. The client should understand that authentication is required.
Credential is valid, but the action is forbidden
The subject is authenticated, but does not have tenant access, scope, role, or permission for the resource.
Auth abuse or rate limit
Brute force, too many invalid token attempts, API key abuse. Do not mix this with 401, because the client and monitoring should react differently.
Auth infrastructure failure
Session store, JWKS endpoint, Redis, or DB is unavailable. For some routes, fail closed is better; for public degraded flows, another policy may be needed.
Summary
A unified error format matters as much as the correct status code. The client, monitoring, and security tooling must see a stable contract.
Auth often becomes the heaviest middleware because it pulls in crypto, storage, tenant resolution, and audit. Optimization does not start with the algorithm; it starts with routing and caching policy.
Good: public/system routes outside the auth pipeline
/health, /ready, static assets, and public docs should not do token parsing or session lookup.
Careful: DB lookup for every JWT
If every JWT still goes to the DB for verification, ask whether JWT gives you any benefit over a session id. You may need a session/introspection model instead.
Good: short cache for stable auth metadata
Tenant membership, API key owner, and JWKS keys can be cached with a clear TTL and revocation story.
Careful: long-lived permissions cache
Permissions change. If the cache lives longer than the security expectation, a user may retain access after revoke.
Summary
Auth performance should not conflict with revocation. If you cache, define TTL, invalidation, and high-risk fallback immediately.
These mistakes are not specific to Bun only, but in Bun projects they often appear under the label of “we just quickly wrote thin middleware”.
Using jwt.decode() or reading the payload without signature verification.
Not checking exp, iss, aud, and algorithm for JWT.
Storing an access token in localStorage for a browser app without a clear XSS threat model.
Logging Authorization, cookies, refresh token, or the full API key.
Storing API keys in plaintext instead of hash + prefix lookup.
Issuing an API key without scopes, owner, environment, and rotation policy.
Treating x-tenant-id as an authoritative source of tenant context.
Returning 200 with { error: ... } for auth failures.
Mixing authentication, tenant membership, and resource authorization into one global middleware.
Review rule
If the middleware cannot be explained as a small state machine with 401/403/next, it is already too complex or taking responsibility for the wrong layer.
Before staging or a security review, go through this list. It is intentionally written as an engineering checklist, not generic advice.
Credential extraction happens once
One layer reads Authorization/cookies/API key and passes the result forward as typed context.
JWT verification is complete
Signature, algorithm, expiry, issuer, audience, subject, and clock skew policy are verified.
Session cookies are protected
httpOnly, secure, sameSite, narrow path, TTL, and HTTPS policy are defined.
API keys are not stored openly
The database stores only hash, key prefix, owner, scopes, last used, created/revoked metadata.
Tenant context is verified
Requested tenant is checked against subject membership/scopes instead of being accepted from a header as fact.
401 and 403 are separated
Missing/invalid credential returns 401; valid credential without permission returns 403.
Secrets are redacted
Logs, tracing, error reports, and analytics do not contain tokens, cookies, API keys, or PII.
A revocation story exists
JWT, sessions, and API keys have a clear model for revocation, rotation, and forced logout.
In Bun, it is easy to write a fast auth wrapper. It is harder to write auth middleware that does not mix authentication and authorization, does not leak between tenants, does not log secrets, does not create an unnecessary DB lookup on every request, and has a proper error contract.
JWT, sessions, and API keys are not competing as “best” and “worst” options. They solve different jobs. JWT is convenient for stateless access, sessions are better for browser UX and controlled logout, and API keys are built for integrations. In production, you often need a hybrid model, but with one unified audit and policy layer.
A good Bun auth pipeline looks simple: extract, verify, resolve subject, resolve tenant, attach context, continue or reject. Everything else should be explicitly designed, tested, and measured.
Not always. JWT is useful for stateless access and service-to-service scenarios, but revocation is harder. Sessions are a better fit for browser SaaS where you need controlled logout, secure cookies, and a session store. The choice depends on the client, threat model, and revocation requirements. [4][5]
Yes. Native `Bun.serve` lets you write auth as function composition around `Request`/`Response`. But you are responsible for context, errors, order, tests, and security policy.
`401` is for missing/invalid credentials: no token, expired session, invalid signature, API key not found. `403` is for a valid credential without permission, tenant access, or the required scope.
In request-scoped auth context after credential and membership verification. Do not trust `x-tenant-id` as the source of truth. A header or path param can be the requested tenant, but middleware must verify the subject has access to it.
No. Authorization headers, cookies, refresh tokens, and API keys should be redacted. Log request id, subject id, tenant id, auth method, and error class, but not the credential.
Show the full API key only once during creation, store a hash, keep a short prefix for lookup, and add owner, scopes, environment, created/last used/revoked metadata, and a rotation policy.
These sources confirm the behavior of the Bun Cookie API, Hono/Elysia auth middleware/plugins, and the security baseline for JWT and session management.
PAS7 Studio can help design auth middleware for Bun, Hono, or Elysia: JWT/session/API-key model, tenant context, scopes, secure cookies, rotation, observability, and security review.
This is especially useful for SaaS products, internal platforms, B2B APIs, and migrations from Express/Fastify where auth behavior has already spread across middleware, guards, and helpers.
Auth middleware in Bun.js: JWT, sessions, API keys, and multi-tenant context
Related Articles
AI Assistant Development Cost in 2026: RAG Chatbots, CRM Integrations, Guardrails, and Support
A practical buyer guide to AI assistant development cost in 2026: prototypes, RAG chatbots, knowledge-base assistants, CRM and website integrations, guardrails, evaluations, monitoring, and support.
AI for landing page development: where it speeds up launches and where it hurts conversion
A practical research piece on using AI for landing page development: v0, Webflow AI, Builder.io, Framer-like builders, UX generation, copy, SEO, personalization, A/B testing, template risk, accessibility, security and technical debt.
AI SEO / GEO in 2026: Your Next Customers Aren’t Humans — They’re Agents
Search is shifting from clicks to answers. Bots and AI agents crawl, cite, recommend, and increasingly buy. Learn what AI SEO / GEO means, why classic SEO is no longer enough, and how PAS7 Studio helps brands win visibility in the agentic web.
The most powerful Apple chip yet? M5 Pro and M5 Max are breaking records
A data-backed March 2026 analysis of Apple M5 Pro and M5 Max. We break down why these chips can credibly be called Apple's most powerful pro laptop silicon, how they compare with M4 Pro, M4 Max, M1 Pro, M1 Max, and how they stack up against Intel and AMD laptop rivals.
Professional development for your business
We create modern web solutions and bots for businesses. Learn how we can help you achieve your goals.