n4nAI

Vercel Edge Middleware for LLM request authentication

Learn vercel edge middleware llm authentication to protect LLM endpoints. Step-by-step how-to with runnable Vercel Edge code and verification.

n4n Team3 min read695 words

Audio narration

Coming soon — every post will get a voice note here.

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:

  1. curl -i https://your-app.vercel.app/api/llm -H "Authorization: Bearer invalid" → expect 401 Unauthorized.
  2. Generate a valid JWT (use jose sign or your auth service) and run:
    curl -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"}]}'
    Expect 200 and an SSE stream starting with data:.
  3. Check Vercel function logs: the x-user-id should appear if you log req.headers in the route.
  4. Preflight: curl -X OPTIONS -i ... → expect CORS headers and 204.

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.

Tagsverceledge-middlewareauthenticationllm-api

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All vercel edge functions llm streaming posts →