Middleware & Sessions
Middleware: a gate in front of every request
Section titled “Middleware: a gate in front of every request”Middleware is a single function that runs before every request is handled. It lives in src/middleware.ts and exports onRequest(context, next) — you do work, then call next() to continue to the page or endpoint (or short-circuit with your own Response).
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware(async (context, next) => { // Runs for every request, before the route. const token = context.cookies.get('session')?.value; context.locals.user = token ? await getUser(token) : null; // share data downstream
// Guard a section of the site. if (context.url.pathname.startsWith('/admin') && !context.locals.user) { return context.redirect('/login'); // short-circuit — never reach the page }
return next(); // continue to the route});sequenceDiagram
participant B as Browser
participant M as onRequest middleware
participant R as Route (page/endpoint)
B->>M: request
M->>M: read cookies, set context.locals
alt not authorized
M-->>B: redirect / 403 (short-circuit)
else authorized
M->>R: next()
R-->>B: rendered response
end context.locals: per-request shared data
Section titled “context.locals: per-request shared data”The key to middleware is context.locals — an object that lives for the duration of one request. Whatever you set there (the current user, a request id, a feature flag) is readable in every page, endpoint, and action for that request via Astro.locals / the action’s context.locals. It’s how middleware hands computed data to the rest of the request without global state.
---// In any on-demand page — read what middleware put there.const user = Astro.locals.user;---{user ? <p>Hi, {user.name}</p> : <a href="/login">Sign in</a>}Sessions: per-user state across requests
Section titled “Sessions: per-user state across requests”context.locals lasts one request. To remember a user across requests — a shopping cart, a login — you use sessions. Astro’s session support stores data server-side, keyed by a cookie, backed by a configured driver (memory in dev, a KV/Redis/database store in production).
---// Read/write session data in a component script:const cart = (await Astro.session?.get('cart')) ?? [];---<p>{cart.length} items in your cart</p>// In an action handler, context.session is the same session:handler: async (input, context) => { const cart = (await context.session?.get('cart')) ?? []; cart.push(input.productId); await context.session?.set('cart', cart); return cart;}Sessions expose get, set, and destroy. On logout you clear everything with Astro.session?.destroy(), which deletes the session cookie and its server-side data. Unlike cookies you set by hand, session data lives on the server — the cookie only holds an opaque id — so it’s safe for sensitive state.
The auth pattern
Section titled “The auth pattern”These three pieces compose into the standard auth flow: middleware reads the session cookie and puts the user on context.locals; pages read Astro.locals.user to render signed-in vs signed-out UI; and a guard in middleware redirects unauthenticated requests away from protected routes. Login writes the session; logout calls destroy().