Suggestions
← TIL
~2 min read
#astro#edge#middleware#auth#performance

Edge auth in Astro 6: JWT verification without a server round-trip

A client came in with 300ms TTFB on /dashboard. Diagnosis: every request was firing a SELECT to the DB to verify the session.

src/middleware.ts in Astro 6 deploys to the edge and intercepts every request before it hits the origin. JWT verification is CPU-only; you verify against the secret already in the runtime's environment variables, without touching the network:

src/middleware.ts
import { defineMiddleware } from "astro:middleware";
import { verifyJWT } from "./lib/auth";

export const onRequest = defineMiddleware(async (context, next) => {
  const token = context.cookies.get("session")?.value;

  if (!token && context.url.pathname.startsWith("/dashboard")) {
    return context.redirect("/login");
  }

  if (token) {
    const payload = verifyJWT(token); // synchronous, no await
    if (!payload) return context.redirect("/login");
    context.locals.user = payload;
  }

  return next();
});

verifyJWT makes no network calls. With jsonwebtoken, verify() without a callback is synchronous. With jose, jwtVerify() returns a Promise; you can await it anyway because it's pure CPU with no I/O. Either way the result comes back in ~1ms.

For context.locals.user to have correct types in your pages, add this to src/env.d.ts:

src/env.d.ts
declare namespace App {
  interface Locals {
    user?: { id: string; email: string; role: string };
  }
}

When edge doesn't help

If you're using opaque session tokens (a UUID mapped to a DB record), your middleware ends up looking like this:

src/middleware.ts: the anti-pattern
// This gives you the same 200ms, just at the edge now
const sessionId = context.cookies.get("session")?.value;
const user = await db.findBySession(sessionId); // 80-200ms DB round-trip

To keep the advantage, the lookup needs to go to a KV store on the same runtime. In Astro 6 with the Cloudflare adapter, binding access uses the native module:

With Cloudflare Workers KV
import { env } from "cloudflare:workers";

const sessionId = context.cookies.get("session")?.value;
const user = await env.KV.get(`session:${sessionId}`);
// 2-5ms instead of 80-200ms

On Vercel Edge the pattern differs (Edge Config SDK). The logic is the same; the store access API changes.

The difference isn't network speed — it's dependency type. JWT: the secret is already in environment variables, no network call. Opaque session ID: always needs to look something up externally. If that place is a DB in Virginia and you're in Asunción, the edge won't save you.

Link copied to clipboard