Securing LLM API routes at the edge saves backend load and cuts latency. This guide shows how to build vercel edge middleware llm authentication that validates tokens before a request hits your model provider, using Vercel Edge Functions.
Step 1: Scaffold the Edge Middleware
Create a Next.js project (or add middleware to an existing one). Vercel Edge Middleware executes in the Edge Runtime, not Node.js, so you cannot use fs or native TCP. Place a middleware.ts at the project root or inside src/.
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'
export const config = {
matcher: '/api/llm/:path*',
}
export async function middleware(req: NextRequest) {
// auth logic injected here
return NextResponse.next()
}
The matcher restricts the middleware to your LLM path. Requests to /api/llm and any subpath run the code; everything else bypasses it. This keeps edge compute billing low and avoids adding latency to static assets.
Run vercel dev locally to test. The Edge Runtime is simulated via @vercel/edge in the CLI, but some Web Crypto APIs behave identically to production.
Step 2: Choose an Authentication Primitive
For vercel edge middleware llm authentication you have two practical patterns: opaque bearer tokens stored in Vercel KV, or signed JWTs verified locally with Web Crypto. Opaque tokens need a KV read on every call; JWTs shift trust to asymmetric signature verification and stay stateless.
I prefer short-lived JWTs (15–60 minute expiry) because they remove a network round-trip and degrade gracefully when KV is throttled. If you need immediate revocation, use KV with a deny list, but accept the latency cost.
Read the header consistently:
const authHeader = req.headers.get('authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new NextResponse('Unauthorized', { status: 401 })
}
const token = authHeader.slice(7).trim()
Never accept the token from query parameters—it leaks into logs.
Step 3: Verify a JWT at the Edge
Use the jose library; it is edge-safe and supports HS256/RS256. Install with npm i jose.
import { jwtVerify, createLocalJWKSet } from 'jose'
const JWKS = createLocalJWKSet({
keys: [JSON.parse(process.env.JWT_PUBLIC_KEY!)],
})
async function verifyToken(token: string) {
try {
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'auth.your-domain.com',
audience: 'llm-api',
clockTolerance: '30s',
})
return payload
} catch (err) {
return null
}
}
Drop this into middleware. If it returns null, respond 401. The verification uses Web Crypto subtly; no Node crypto imports allowed.
Key rotation
Store multiple JWKs in the keys array. jose picks the matching kid automatically. Rotate weekly and keep the previous key active for one cycle.
Step 4: Inject Identity and Routing Headers
After verification, attach the subject to a trusted header. The downstream route must not trust client-supplied x-user-id.
const payload = await verifyToken(token)
if (!payload?.sub) {
return new NextResponse('Unauthorized', { status: 401 })
}
const res = NextResponse.next()
res.headers.set('x-user-id', payload.sub)
res.headers.set('x-request-id', crypto.randomUUID())
// optional routing hint for gateway
res.headers.set('x-model-preference', 'gpt-4o-mini')
return res
If you route through a gateway like n4n.ai, the edge middleware can pass provider cache-control hints and routing directives via headers. The gateway honors client routing directives and forwards provider cache-control hints, so the x-model-preference above influences which of 240+ models answers without extra code in your route.
Step 5: Proxy to the LLM Endpoint
Your API route (app/api/llm/route.ts) forwards to the model provider. Mark it edge runtime.
// app/api/llm/route.ts
export const runtime = 'edge'
export async function POST(req: Request) {
const body = await req.json()
const upstream = await fetch(
'https://api.openai.com/v1/chat/completions',
{
method: 'POST',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${process.env.UPSTREAM_KEY}`,
},
body: JSON.stringify(body),
}
)
return new Response(upstream.body, {
headers: {
'content-type': 'text/event-stream',
'cache-control': 'no-cache',
},
})
}
For an OpenAI-compatible endpoint that addresses 240+ models with automatic fallback when a provider is rate-limited or degraded, swap the URL. The middleware already authenticated the caller; the route only bridges streams.
Error handling
If upstream returns non-200, forward the status and a truncated error:
if (!upstream.ok) {
const text = await upstream.text()
return new NextResponse(text.slice(0, 500), { status: upstream.status })
}
Step 6: Handle CORS and Streaming
Browser clients calling your LLM route need CORS. Handle preflight in middleware before auth:
if (req.method === 'OPTIONS') {
return new NextResponse(null, {
headers: {
'access-control-allow-origin': 'https://your-app.com',
'access-control-allow-headers': 'authorization, content-type',
'access-control-max-age': '86400',
},
})
}
Streaming must not be buffered. Returning upstream.body directly keeps the ReadableStream flowing. Do not await upstream.json() in the middleware—it breaks the stream and doubles memory.
Step 7: Deploy and Verify
Push to Git; Vercel builds and detects middleware.ts automatically. No vercel.json needed for basic middleware.
Verification checklist:
curl -i https://your-app.vercel.app/api/llm -H "Authorization: Bearer invalid"→ expect401 Unauthorized.- Generate a valid JWT (use
josesign or your auth service) and run:
Expectcurl -i https://your-app.vercel.app/api/llm \ -H "Authorization: Bearer $VALID_JWT" \ -H "content-type: application/json" \ -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'200and an SSE stream starting withdata:. - Check Vercel function logs: the
x-user-idshould appear if you logreq.headersin the route. - Preflight:
curl -X OPTIONS -i ...→ expect CORS headers and204.
If valid tokens get 401, verify JWT_PUBLIC_KEY is a single JWK JSON string and that issuer/audience match exactly.
Step 8: Operational Hardening
Edge middleware counts against invocation quota but runs in single-digit milliseconds. Keep the check lean: no JSON schema validation of the body, no writes. Use KV only for revocation checks, and cache deny lists in EdgeKV with a 60s TTL.
For per-token usage metering downstream, the route or gateway can read x-user-id and record counts. n4n.ai provides per-token usage metering on its endpoint, which removes the need to build your own counters if you resell access.
Set a timeout on fetch to the upstream (e.g., signal: AbortSignal.timeout(30000)) so a stalled provider does not hang the edge function. Edge functions have a 30s max duration on Pro plans; align your timeout accordingly.
Finally, add a health route /api/llm/health excluded from the matcher to let monitoring bypass auth. That completes a production-grade vercel edge middleware llm authentication flow.