Skip to main content
Articles

Authentication for Serverless and Edge Deployments

Author: Roy Anger
Published: (last updated )

How does authentication work for serverless and edge deployments?

This is Part 1 of a three-part series on authentication for serverless and edge deployments. It covers why serverless environments require a different approach to authentication, core concepts like JWTs and stateless sessions, five architectural placement patterns, and a high-level overview of solution options.

Authentication for serverless and edge runtimes uses short-lived, stateless JWTs verified against a JWKS endpoint with local caching, because ephemeral compute cannot rely on long-lived session stores or in-memory caches. Managed providers handle key issuance, rotation, and edge-compatible SDKs; self-built options typically reach for jose with crypto.subtle.

Why Authentication Is Different at the Edge and in Serverless

The serverless and edge runtime model

Serverless compute is ephemeral: functions spin up per invocation, run briefly, and disappear. Examples include AWS Lambda, Vercel, Netlify, Azure, and Google Cloud Functions. The platform handles scaling, and you pay only for execution time.

"Edge" has two distinct meanings, and confusing them often breaks auth code.

  • Architectural edge: compute placed geographically close to the user, but still running a familiar Node.js-style runtime. Next.js 16 proxy.ts is the canonical example — it sits in front of origin requests, but it runs on Node.js, not a V8 isolate (Next.js proxy.ts API reference, Next.js 16 release blog).
  • Runtime edge: code that executes inside a V8 isolate or WASM runtime with Web Standards APIs only. Examples include Cloudflare Workers, Netlify Edge Functions (Deno), Deno Deploy, and Fastly Compute (WASM). fs, net, and most of the Node standard library are either absent or gated behind compatibility flags (Cloudflare Workers Node.js compatibility).

Note

Next.js 16 proxy.ts sits in an "architectural edge" position but runs exclusively on the Node.js runtime. The runtime Route Segment Config option is not supported inside proxy.ts and setting it throws a build-time error (Next.js proxy.ts reference).

A side-by-side comparison highlights the constraints:

ConstraintNode.js serverlessV8 isolate / runtime edge
Cold startAWS documents Lambda cold starts spanning "less than 100ms to well over 1 second" (AWS Lambda cold start remediation); Node.js 22 arm64 p50 ~294ms optimized in an independent benchmark (Node.js 22 Lambda benchmarks)Cloudflare Workers start in <5ms (ByteByteGo Workers cold starts); Deno Deploy ~3ms, 10ms p50 (Deno Deploy)
MemoryLambda 128MB to 10GB (AWS Lambda quotas)Cloudflare Workers 128MB; Netlify Edge 512MB (CF Workers limits, Netlify Edge limits)
CPU budgetLambda up to 15 min wall timeCloudflare Workers Free 10ms CPU; Paid 5 min CPU; Netlify Edge 50ms CPU per request
API surfaceFull Node.js stdlib + crypto moduleWeb Standards only by default (globalThis.crypto.subtle, fetch); nodejs_compat flag needed for Buffer/Streams on Workers
Bundle limitsVercel Node functions 250MB (Vercel Functions limitations)Cloudflare Workers 3MB Free / 10MB Paid (CF Workers limits); Vercel Edge Runtime 1MB Hobby / 2MB Pro / 4MB Enterprise after gzip (Vercel Edge Runtime docs); Netlify Edge 20MB compressed

Consequently, auth code for V8-isolates must be small, Web-Standards-native, and fast to start. Node.js serverless allows larger dependencies but pays a cold-start tax on instance turnover.

Why traditional session auth breaks down

A classical session model assumes a long-lived server with an in-memory cache backed by a nearby database. Three assumptions break here:

  • Stateful session stores assume proximity. Each edge point-of-presence (PoP) is physically far from any central session DB, so every verification becomes a cross-region round-trip. That defeats the latency advantage of serving from the edge.
  • In-memory caches do not persist. Serverless functions tear down after their invocation window. V8 isolates may be evicted at any time. Per-isolate caches exist, but a cold isolate starts with an empty one.
  • Database round-trips per request are expensive at scale. A community benchmark measured warm execution at ~167ms on an edge runtime versus ~287ms on a serverless backend when the serverless tier hit a session database (ByteIota edge vs serverless). Treat this as illustrative of the shape, not a universal constant — methodologies vary.

Cookie-based sessions still work if the verification path is edge-compatible: a signed cookie verified with Web Crypto, or a JWT cookie parsed without Node-only libraries.

Cold starts, latency, and geographic distribution

Cold starts amplify auth latency because new instances pay for container spin-up, module evaluation, JWKS fetch, JWT parse, and database lookups. A sub-millisecond warm check can become a multi-hundred-millisecond delay.

Verify close to the user, authorize close to the data. A signed JWT answers "is this a valid token?" without a database. Authorization ("can the user perform this action?") usually requires state and runs near the data.

Runtime constraints that trip up auth libraries

Many "Node-first" auth libraries fail on V8-isolates because they depend on absent APIs.

  • jsonwebtoken uses Node's crypto module; CF Workers require Web Crypto (crypto.subtle) unless nodejs_compat is enabled.
  • bcrypt ships a native binary; it will not run in a V8 isolate.
  • firebase-admin uses TCP sockets and Node.js-only APIs; it is not edge-compatible. Community library next-firebase-auth-edge fills the gap by reimplementing token verification with Web Crypto (next-firebase-auth-edge).
  • pg (node-postgres) opens raw TCP connections; not available on CF Workers or Netlify Edge.
  • Most Passport strategies expect Express-shaped middleware and the Node http module; porting to Web Fetch handlers is non-trivial.

Bundle limits matter. Vercel Edge Runtime limits are 1-4MB gzipped (Vercel Edge Runtime docs). Cloudflare Workers allow 3-10MB compressed (Cloudflare Workers limits). Both favor small, Web-Standards-first SDKs.

Tip

jose (github.com/panva/jose) is the universal Web-Crypto-first fallback — it runs in Node.js, Bun, Deno, Cloudflare Workers, and browsers with zero external dependencies and a tree-shakeable ESM build. If a vendor SDK will not run in your runtime, jose can verify the same JWTs as long as you know the issuer's JWKS URL.

Core Concepts for Serverless and Edge Authentication

Stateless authentication with JWTs

A JWT consists of three base64url-encoded segments: header.payload.signature. The header declares the algorithm and key ID (kid); the payload carries claims; the signature secures the token.

Stateless tokens fit ephemeral compute because verification only requires the issuer's public key—no server-side session record needed. Any function can verify any token without shared memory.

The tokens you usually encounter split into three roles:

  • Access tokens: short-lived (15–60 minutes typical; Clerk session tokens are 60 seconds), sent on every API request, proving the caller's identity and scopes.
  • Refresh tokens: long-lived, used to mint new access tokens without re-authentication.
  • Session tokens: in some providers, the same short-lived JWT is used both as an access token and to drive session refresh (Clerk's __session cookie is this shape).

The relevant specs are RFC 7519 (JWT), RFC 9068 (JWT Profile for OAuth 2.0 Access Tokens), and the OpenID Connect Core 1.0 spec for ID tokens.

Caution

The alg field in the JWT header tells the verifier which algorithm to use. Accepting alg: none or accepting whatever the header claims without an explicit allow-list is the source of multiple historical CVEs (CVE-2015-9235 jsonwebtoken algorithm confusion; CVE-2022-23529). Always verify with an explicit algorithm allow-list.

JWT verification with JWKS

A JSON Web Key Set (JWKS) is a JSON document of the shape { "keys": [...] } where each entry is a public key with fields like kty (key type), kid (key ID), use, alg, and the key material itself (RFC 7517). Providers publish JWKS at a well-known URL — conventionally the issuer URL followed by the path /.well-known/jwks.json — discoverable via the OIDC Discovery Document.

Verifying a JWT against a JWKS follows this flow:

  1. Parse the header and read the kid.
  2. Fetch the JWKS (or read it from cache) and find the key with the matching kid.
  3. Verify the signature using the algorithm in the header, checked against your allow-list.
  4. Validate the required claims: iss (issuer), aud (audience), exp (expiry), nbf (not-before), azp (authorized party, when applicable), and any custom claims the app cares about.
  5. Allow a small clock skew — 5 to 30 seconds is typical (Curity JWT best practices).

Asymmetric algorithms (RS256, ES256, EdDSA) beat HS256 for multi-service verification since every service can hold the public key without sharing a secret. EdDSA and ES256 signatures are smaller, and EdDSA signing is faster. However, verification is the hot path for edge systems, where RSA-2048 remains comparable (Connect2id Nimbus benchmark, WorkOS HMAC vs RSA vs ECDSA).

A portable, Web-Crypto-native verifier looks like this:

import { createRemoteJWKSet, jwtVerify } from 'jose'

const JWKS = createRemoteJWKSet(new URL('https://your-issuer.example.com/.well-known/jwks.json'))

export async function verify(token: string) {
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: 'https://your-issuer.example.com',
    audience: 'your-app-audience',
    algorithms: ['RS256', 'ES256'],
    clockTolerance: '30s',
  })
  return payload
}

That block runs unchanged in Node.js, Bun, Deno, and Cloudflare Workers. createRemoteJWKSet() caches the JWKS automatically and refreshes it on kid miss (jose GitHub).

JWKS caching at the edge

Fetching JWKS on every request adds a network round-trip. A community benchmark puts cold JWKS fetches at 15–25ms on Vercel Edge (SSOJet JWT at the edge). The fix is layered caching:

  • Per-isolate in-memory (LRU). Free, fastest, warm-only. jose's createRemoteJWKSet() does this by default.
  • Shared KV. Cloudflare KV, Upstash, or Redis shared across isolates. Cloudflare's post-October-2025 rearchitecture documents hot-key KV reads <1ms and p99 <5ms (Cloudflare KV performance).
  • Background refresh. Refresh the cache before the TTL expires to avoid a thundering herd at rollover.

Handling JWKS rotation requires publishing the new key early: pre-publish → wait for cache TTL → switch signing → retire old key (WorkOS JWKS guide, Zalando JWK rotation).

A NearForm benchmark on RS256 verification with an LRU cache showed a jump from 13,781 to 150,700 ops/sec (+993%) after caching public keys (NearForm JWT performance).

Tip

Cap JWKS refresh at a 5–10 minute minimum even on cache misses. A naive "refetch whenever a kid is missing" strategy turns a key-rotation event into an accidental DoS on the issuer.

Session vs. token-based approaches

Cookie-based sessions work inside a single trust domain, especially under one Node.js deployment. They struggle at the edge when:

  • The verification path cannot be edge-compatible (e.g., the session store is a Postgres DB reachable only over TCP).
  • Requests span multiple origins or runtimes, so cookies do not automatically flow.

Short-lived JWTs are a better fit when:

  • Multiple runtimes verify tokens from the same issuer (Node.js API + CF Workers edge + mobile clients).
  • The latency budget is low enough that a DB round-trip per request is unacceptable.

A hybrid model uses a long-lived origin session and a short-lived JWT for the edge. Clerk implements this: __client is a long-lived cookie on Clerk's API, and __session is a 60-second JWT for the app (How Clerk works). The edge verifies the JWT, while the origin handles revocation.

Cookie flags checklist for any session cookie at the edge:

  • Secure — send only over HTTPS.
  • SameSite=Lax as default (browsers apply this already). SameSite=None requires Secure and is necessary for cross-origin flows.
  • HttpOnly where feasible (prevents JS access; see Part 2 for Clerk's __session exception).
  • Domain — scope to the narrowest domain that still works; widening to .example.com expands XSS blast radius.
  • Path — default / unless you specifically need a narrower path.

Machine-to-machine and service identity

Service-to-service calls need identity. The canonical pattern is the OAuth 2.0 client credentials flow: a service proves identity with a client ID and secret to receive a scoped, short-lived access token (RFC 6749, RFC 9700).

Design choices that matter more than the grant type:

  • Asymmetric signed tokens (RS256 / ES256 / EdDSA) beat shared secrets for multi-service verification. Any service can verify with the public key.
  • Short expirations — 15 minutes or less — reduce the blast radius of a leaked token.
  • Scopes restrict what the token can do. The default should be the narrowest scope that works.
  • Audit logs should record service-to-service calls with a stable machine identifier, not just "service X called service Y."

Not every provider implements client credentials as the OAuth 2.0 grant type. Clerk's M2M tokens are a distinct machine-auth product, not an RFC 6749 client credentials grant — covered in depth in Part 2.

Architectural Patterns for Serverless and Edge Authentication

These are authentication patterns ("who is this request?"). Authorization ("what can this request do?") runs afterward, usually near the data. These patterns optimize authentication so authorization can rely on trusted identity.

Pattern 1 — Authentication at the edge (middleware)

Verify at the nearest PoP before the request reaches origin functions.

client → edge middleware (verify JWT) → origin function → data

Typical placements:

  • Next.js 16 proxy.ts (architectural edge, Node.js runtime).
  • Vercel Routing Middleware (runtime edge, still the Edge Runtime; not deprecated) (Vercel Routing Middleware docs).
  • Cloudflare Workers with routes or a front-door Worker.
  • Netlify Edge Functions wired as page routes.

This pattern spans both runtime families, but available APIs differ. Node-only primitives (jsonwebtoken) work in Next.js proxy.ts but break on Cloudflare Workers. Use Web Crypto + jose (or an edge-ready vendor SDK) for portability.

  • Pros: single point of auth enforcement; unauthenticated requests are rejected before origin compute runs.
  • Cons: misconfigurations can bypass auth entirely (CVE-2025-29927); must be paired with per-route verification for defense in depth.
  • Use when: you have a single trust boundary and want to minimize origin load.

Warning

CVE-2025-29927 (CVSS 9.1 Critical) allowed self-hosted Next.js apps to skip middleware.ts auth checks via the x-middleware-subrequest header (NVD CVE-2025-29927, Datadog Security Labs). Fixed in 12.3.5, 13.5.9, 14.2.25, 15.2.3. Vercel- and Netlify-hosted apps were not affected because the platforms stripped the header. The mitigation everywhere is: never rely on middleware as the sole auth gate — always re-verify in Server Components, Route Handlers, or backend code.

Pattern 2 — JWT verification inside each function

Every function verifies the token independently.

client → function A (verify) → data
client → function B (verify) → data

Works for function-per-endpoint architectures (AWS Lambda, Netlify Functions, CF Workers with URL-based routing).

  • Pros: no single point of failure; each function is self-contained; safer against middleware-bypass classes of bugs.
  • Cons: duplicated verification logic; risk of drift between functions; each function pays its own cold-start tax.
  • Use when: functions are heterogeneous or deployed separately; you cannot guarantee a common middleware layer.

Pattern 3 — Edge verification + origin session (hybrid)

Edge verifies a short-lived JWT; origin loads a rich session for Server Components or complex business logic.

client → edge (verify JWT) → origin (load session → render) → data
  • Pros: low-latency rejection at the edge plus a rich origin state model; instant revocation via the origin session record.
  • Cons: more moving parts; session sync concerns between edge and origin.
  • Use when: you need both geographic distribution and a rich server-side state that would not fit in claims.

Pattern 4 — Centralized auth gateway

A dedicated edge auth service issues and verifies tokens for other services—often a Backend-for-Frontend (BFF) or API gateway (Kong, Envoy).

client → gateway (verify, mint downstream token) → service A
                                                 → service B
  • Pros: clean separation of concerns; a single place to update auth logic; easier to enforce consistent policies.
  • Cons: additional hop; gateway itself has to scale; introduces a trust boundary that downstream services must respect.
  • Use when: you have multiple backend services with mixed runtime targets and want a uniform identity story.

See microservices.io — Auth in Microservices Part 1 (BFF).

Pattern 5 — Signed request headers for service-to-service auth

Signed tokens passed between services via the Authorization: Bearer <token> header. The edge verifies and forwards identity downstream.

client → edge (verify) → forward Authorization header → service A → service B
  • Pros: simple; works with any HTTP service mesh; tokens are cacheable.
  • Cons: token size on every request; a service mesh with sidecars is often a better fit at larger scale.
  • Use when: internal microservices need zero-trust identity propagation without a full mesh.

See microservices.io — JWT Authorization, Frontegg authentication in microservices, and Oso microservices authorization patterns.

Choosing a pattern — decision factors

Four factors drive the choice:

  • Runtime target — Node serverless, V8 isolate edge, or both.
  • State auth needs to carry — a few claims (JWT-only is enough) vs. rich session data (need origin state).
  • Latency budget per request — every network round-trip has to be justified.
  • Client heterogeneity — single web app vs. web + mobile + internal services.

A short decision sketch:

  • Primarily Node.js and a single Next.js 16 app → Pattern 1 with proxy.ts, re-verifying in Server Components/Route Handlers.
  • Mixed runtimes, including Cloudflare Workers or Netlify Edge → Pattern 3 (hybrid) — edge verifies, origin keeps rich state.
  • Microservices across many backends → Patterns 4 + 5 — a gateway verifies, downstream services consume forwarded identity.
  • Function-per-endpoint on AWS Lambda with no consistent front door → Pattern 2, with a shared verifier library to avoid drift.

Solution Options for Serverless and Edge Authentication

For each option: what it is in one sentence, edge/V8-isolate compatibility, strengths, and limits. Deep Clerk coverage lives in Part 2 of this series.

Rolling your own JWT verification

Roll your own means wiring a library like jose against an identity provider you operate (or against your own issuer). The jose library is the de facto Web-Crypto-first choice — it runs in Node.js, Bun, Deno, Cloudflare Workers, and browsers with zero external dependencies.

  • What you still have to build: sign-up/sign-in UI, password reset, MFA, organizations and RBAC, device management, key rotation, CVE tracking, refresh token rotation, JWKS caching, and secret rotation.
  • Maintenance burden: every CVE in the auth space becomes your problem to track and mitigate.
  • Use when: a very narrow API-only scope with no user UI and an existing IdP you trust.

AWS Cognito

Managed AWS service; JWKS-based verification works at the edge via the aws-jwt-verify library. The official pattern for CloudFront + Lambda@Edge is documented in the Authorization@Edge blog post.

  • Strengths: AWS-native integrations; scale; official guidance for CloudFront + Lambda@Edge.
  • Limits: UX customization is limited; token claim shape is opinionated; multi-cloud friction.

Auth0

Enterprise-grade IdP with JWKS-based verification.

  • The Next.js SDK requires the /edge subpath for middleware; getSession() only works in Node.js runtime (auth0/nextjs-auth0).
  • Strengths: SSO, enterprise features, mature Actions/Rules ecosystem.
  • Limits: per-MAU pricing. In November 2023, Auth0's pricing restructure raised B2C Essentials entry from $23/mo for 1,000 MAU to $35/mo for 500 MAU and raised the per-MAU overage from $0.023 to $0.07 (~3x) (Auth0 pricing change announcement, Auth0 pricing). Confirm current tiers against the live Auth0 pricing page before sizing cost.

Supabase Auth

JWT-based, easily verifiable at the edge. Supabase Auth migrated to asymmetric keys (RS256/ECC/Ed25519) in May 2025, so edge verification no longer requires sharing a symmetric secret (Supabase Auth JWTs docs).

  • Strengths: bundled with Postgres + storage; simple flows; getClaims() works in Deno Edge Functions.
  • Limits: tightly coupled to the Supabase backend; fewer enterprise features than Auth0 or Cognito.

Firebase Authentication

Token verification via Google's public keys.

  • firebase-admin is not edge-compatible; the community library next-firebase-auth-edge reimplements token creation/verification with Web Crypto for Next.js 16 and Node.js 24+ (next-firebase-auth-edge GitHub).
  • Strengths: mobile-first; generous free tier (Spark: 50K MAU free for email/social; confirm current tiers on the Firebase pricing page before sizing cost).
  • Limits: Google-hosted identity model; custom claim flexibility requires the Admin SDK; phone auth is billed per SMS.

Clerk

Covered in depth in Part 2. Differentiators surfaced here:

  • Edge-ready SDKs for Next.js, Cloudflare Workers, Hono, React Router (formerly Remix), Expo.
  • Networkless verification via jwtKey — no JWKS round-trip per request once the PEM public key is configured.
  • Managed JWKS and automatic key rotation; consumers never run their own rotation schedule.
  • Built-in UI, organizations, MFA, passkeys, and M2M tokens.

Next steps

In this first part, we explored the constraints of edge and serverless runtimes and the architectural patterns used to place authentication in distributed systems. In Part 2, we will apply these concepts in a hands-on implementation, using Clerk to secure Next.js, Cloudflare Workers, and mobile runtimes.

Frequently Asked Questions