Edge Middleware
Handle user context in edge computing environments like Cloudflare Workers or Vercel Edge Functions.
Cloudflare Workers
Basic Worker Setup
import { Tokenlay } from 'tokenlay'
const tokenlay = new Tokenlay({
apiKey: 'your-api-key' // Use environment variables in production
})
export default {
async fetch(request, env, ctx) {
// Extract user context from request
const userContext = await extractUserContext(request, env)
if (request.method === 'POST' && new URL(request.url).pathname === '/api/process') {
try {
const data = await request.json()
const result = await tokenlay.process(data, {
headers: userContext
})
return new Response(JSON.stringify(result), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
}
})
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
})
}
}
return new Response('Not found', { status: 404 })
}
}
async function extractUserContext(request, env) {
const authHeader = request.headers.get('Authorization')
if (!authHeader?.startsWith('Bearer ')) {
return {
'User-ID': 'anonymous',
'User-Role': 'guest',
'Auth-Status': 'no-token',
'Edge-Location': getEdgeLocation(request),
'Request-ID': generateRequestId()
}
}
const token = authHeader.slice(7)
try {
// Verify JWT using Web Crypto API
const payload = await verifyJWT(token, env.JWT_SECRET)
return {
'User-ID': payload.sub,
'User-Email': payload.email,
'User-Role': payload.role || 'user',
'Organization-ID': payload.org,
'Auth-Status': 'authenticated',
'Edge-Location': getEdgeLocation(request),
'Request-ID': generateRequestId(),
'Token-Expires': payload.exp?.toString()
}
} catch (error) {
return {
'User-ID': 'anonymous',
'User-Role': 'guest',
'Auth-Status': 'invalid-token',
'Auth-Error': error.message,
'Edge-Location': getEdgeLocation(request),
'Request-ID': generateRequestId()
}
}
}
function getEdgeLocation(request) {
// Cloudflare provides location info in headers
return request.cf?.colo || 'unknown'
}
function generateRequestId() {
return `edge_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
}
JWT Verification in Workers
async function verifyJWT(token, secret) {
const parts = token.split('.')
if (parts.length !== 3) {
throw new Error('Invalid JWT format')
}
const [headerB64, payloadB64, signatureB64] = parts
// Decode header and payload
const header = JSON.parse(atob(headerB64))
const payload = JSON.parse(atob(payloadB64))
// Check algorithm
if (header.alg !== 'HS256') {
throw new Error('Unsupported algorithm')
}
// Check expiration
if (payload.exp && payload.exp < Date.now() / 1000) {
throw new Error('Token expired')
}
// Verify signature
const encoder = new TextEncoder()
const data = encoder.encode(`${headerB64}.${payloadB64}`)
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
)
const signature = Uint8Array.from(atob(signatureB64), c => c.charCodeAt(0))
const isValid = await crypto.subtle.verify('HMAC', key, signature, data)
if (!isValid) {
throw new Error('Invalid signature')
}
return payload
}
Advanced Cloudflare Worker Patterns
KV Storage for User Data
export default {
async fetch(request, env, ctx) {
const userContext = await extractUserContextWithCache(request, env)
// Process request with user context
if (request.method === 'POST' && new URL(request.url).pathname === '/api/process') {
const data = await request.json()
const result = await tokenlay.process(data, {
headers: userContext
})
return new Response(JSON.stringify(result), {
headers: { 'Content-Type': 'application/json' }
})
}
return new Response('Not found', { status: 404 })
}
}
async function extractUserContextWithCache(request, env) {
const authHeader = request.headers.get('Authorization')
if (!authHeader?.startsWith('Bearer ')) {
return createAnonymousContext(request)
}
const token = authHeader.slice(7)
// Check KV cache first
const cacheKey = `user_context:${hashToken(token)}`
const cachedContext = await env.USER_CACHE.get(cacheKey, 'json')
if (cachedContext) {
// Update request-specific fields
return {
...cachedContext,
'Request-ID': generateRequestId(),
'Cache-Hit': 'true'
}
}
try {
const payload = await verifyJWT(token, env.JWT_SECRET)
// Fetch additional user data
const userProfile = await fetchUserProfile(payload.sub, env)
const userContext = {
'User-ID': payload.sub,
'User-Email': payload.email,
'User-Role': payload.role || 'user',
'Organization-ID': payload.org,
'User-Tier': userProfile?.tier || 'free',
'User-Permissions': userProfile?.permissions?.join(',') || '',
'Auth-Status': 'authenticated',
'Edge-Location': request.cf?.colo || 'unknown',
'Request-ID': generateRequestId(),
'Cache-Hit': 'false'
}
// Cache for 5 minutes
await env.USER_CACHE.put(cacheKey, JSON.stringify(userContext), { expirationTtl: 300 })
return userContext
} catch (error) {
return createAnonymousContext(request, error.message)
}
}
async function fetchUserProfile(userId, env) {
try {
const response = await fetch(`${env.USER_API_URL}/users/${userId}`, {
headers: {
'Authorization': `Bearer ${env.INTERNAL_API_TOKEN}`
}
})
return response.ok ? await response.json() : null
} catch {
return null
}
}
function hashToken(token) {
// Simple hash for cache key (use a proper hash in production)
let hash = 0
for (let i = 0; i < token.length; i++) {
const char = token.charCodeAt(i)
hash = ((hash << 5) - hash) + char
hash = hash & hash // Convert to 32-bit integer
}
return hash.toString()
}
function createAnonymousContext(request, error = null) {
return {
'User-ID': 'anonymous',
'User-Role': 'guest',
'Auth-Status': error ? 'invalid-token' : 'no-token',
'Auth-Error': error || '',
'Edge-Location': request.cf?.colo || 'unknown',
'Request-ID': generateRequestId()
}
}
Vercel Edge Functions
Basic Edge Function
import { NextRequest, NextResponse } from 'next/server'
import { jwtVerify, SignJWT } from 'jose'
import { Tokenlay } from 'tokenlay'
const tokenlay = new Tokenlay({
apiKey: process.env.TOKENLAY_API_KEY
})
export async function POST(request: NextRequest) {
try {
const userContext = await extractUserContext(request)
const data = await request.json()
const result = await tokenlay.process(data, {
headers: userContext
})
return NextResponse.json(result)
} catch (error) {
return NextResponse.json(
{ error: error.message },
{ status: 500 }
)
}
}
async function extractUserContext(request: NextRequest) {
const authHeader = request.headers.get('Authorization')
if (!authHeader?.startsWith('Bearer ')) {
return {
'User-ID': 'anonymous',
'User-Role': 'guest',
'Auth-Status': 'no-token',
'Edge-Region': process.env.VERCEL_REGION || 'unknown',
'Request-ID': generateRequestId()
}
}
try {
const token = authHeader.slice(7)
const secret = new TextEncoder().encode(process.env.JWT_SECRET)
const { payload } = await jwtVerify(token, secret)
return {
'User-ID': payload.sub as string,
'User-Email': payload.email as string,
'User-Role': payload.role as string || 'user',
'Organization-ID': payload.org as string,
'Auth-Status': 'authenticated',
'Edge-Region': process.env.VERCEL_REGION || 'unknown',
'Request-ID': generateRequestId(),
'Token-Expires': payload.exp?.toString()
}
} catch (error) {
return {
'User-ID': 'anonymous',
'User-Role': 'guest',
'Auth-Status': 'invalid-token',
'Auth-Error': error.message,
'Edge-Region': process.env.VERCEL_REGION || 'unknown',
'Request-ID': generateRequestId()
}
}
}
function generateRequestId(): string {
return `vercel_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
}
export const runtime = 'edge'
Advanced Vercel Edge Patterns
Edge Config for User Permissions
import { get } from '@vercel/edge-config'
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
const userContext = await extractUserContextWithPermissions(request)
// Check permissions before processing
if (!hasRequiredPermission(userContext, 'process_data')) {
return NextResponse.json(
{ error: 'Insufficient permissions' },
{ status: 403 }
)
}
const data = await request.json()
const result = await tokenlay.process(data, { headers: userContext })
return NextResponse.json(result)
}
async function extractUserContextWithPermissions(request: NextRequest) {
const baseContext = await extractUserContext(request)
if (baseContext['Auth-Status'] !== 'authenticated') {
return baseContext
}
const userId = baseContext['User-ID']
// Fetch user permissions from Edge Config
const userPermissions = await get(`user_permissions:${userId}`) || []
const userTier = await get(`user_tier:${userId}`) || 'free'
return {
...baseContext,
'User-Permissions': Array.isArray(userPermissions)
? userPermissions.join(',')
: '',
'User-Tier': userTier,
'Permissions-Source': 'edge-config'
}
}
function hasRequiredPermission(context: Record<string, string>, permission: string): boolean {
const permissions = context['User-Permissions']?.split(',') || []
return permissions.includes(permission) || context['User-Role'] === 'admin'
}
Geolocation-Based Context
export async function POST(request: NextRequest) {
const userContext = await extractUserContextWithGeo(request)
const data = await request.json()
const result = await tokenlay.process(data, { headers: userContext })
return NextResponse.json(result)
}
async function extractUserContextWithGeo(request: NextRequest) {
const baseContext = await extractUserContext(request)
// Add geolocation data
const geo = request.geo
const geoContext = {
'Client-Country': geo?.country || 'unknown',
'Client-Region': geo?.region || 'unknown',
'Client-City': geo?.city || 'unknown',
'Client-Latitude': geo?.latitude || '',
'Client-Longitude': geo?.longitude || '',
'Client-Timezone': Intl.DateTimeFormat().resolvedOptions().timeZone
}
return {
...baseContext,
...geoContext
}
}
Deno Deploy
Basic Deno Deploy Function
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { verify } from 'https://deno.land/x/djwt@v2.8/mod.ts'
interface UserContext {
'User-ID': string
'User-Email'?: string
'User-Role': string
'Organization-ID'?: string
'Auth-Status': string
'Request-ID': string
'Deno-Region': string
}
const tokenlay = new Tokenlay({
apiKey: Deno.env.get('TOKENLAY_API_KEY')!
})
serve(async (request: Request) => {
if (request.method === 'POST' && new URL(request.url).pathname === '/api/process') {
try {
const userContext = await extractUserContext(request)
const data = await request.json()
const result = await tokenlay.process(data, {
headers: userContext as Record<string, string>
})
return new Response(JSON.stringify(result), {
headers: { 'Content-Type': 'application/json' }
})
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
)
}
}
return new Response('Not found', { status: 404 })
})
async function extractUserContext(request: Request): Promise<UserContext> {
const authHeader = request.headers.get('Authorization')
if (!authHeader?.startsWith('Bearer ')) {
return {
'User-ID': 'anonymous',
'User-Role': 'guest',
'Auth-Status': 'no-token',
'Request-ID': generateRequestId(),
'Deno-Region': Deno.env.get('DENO_REGION') || 'unknown'
}
}
try {
const token = authHeader.slice(7)
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(Deno.env.get('JWT_SECRET')!),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
)
const payload = await verify(token, key)
return {
'User-ID': payload.sub as string,
'User-Email': payload.email as string,
'User-Role': payload.role as string || 'user',
'Organization-ID': payload.org as string,
'Auth-Status': 'authenticated',
'Request-ID': generateRequestId(),
'Deno-Region': Deno.env.get('DENO_REGION') || 'unknown'
}
} catch (error) {
return {
'User-ID': 'anonymous',
'User-Role': 'guest',
'Auth-Status': 'invalid-token',
'Request-ID': generateRequestId(),
'Deno-Region': Deno.env.get('DENO_REGION') || 'unknown'
}
}
}
function generateRequestId(): string {
return `deno_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
}
Error Handling and Monitoring
Comprehensive Error Handling
// Cloudflare Workers
export default {
async fetch(request, env, ctx) {
try {
const userContext = await extractUserContext(request, env)
if (request.method === 'POST' && new URL(request.url).pathname === '/api/process') {
const data = await request.json()
const result = await tokenlay.process(data, {
headers: userContext
})
// Log successful request
await logEvent(env, 'request_success', {
userId: userContext['User-ID'],
requestId: userContext['Request-ID'],
edgeLocation: userContext['Edge-Location']
})
return new Response(JSON.stringify(result), {
headers: { 'Content-Type': 'application/json' }
})
}
} catch (error) {
// Log error with context
await logEvent(env, 'request_error', {
error: error.message,
stack: error.stack,
userId: userContext?.['User-ID'] || 'unknown',
requestId: userContext?.['Request-ID'] || 'unknown'
})
return new Response(
JSON.stringify({
error: 'Processing failed',
requestId: userContext?.['Request-ID']
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
)
}
return new Response('Not found', { status: 404 })
}
}
async function logEvent(env, event, data) {
try {
await fetch(env.LOGGING_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event,
timestamp: new Date().toISOString(),
data
})
})
} catch {
// Silently fail logging to not impact main request
}
}
Performance Monitoring
// Vercel Edge Function with monitoring
export async function POST(request: NextRequest) {
const startTime = Date.now()
let userContext: Record<string, string> = {}
try {
userContext = await extractUserContext(request)
const data = await request.json()
const result = await tokenlay.process(data, {
headers: userContext
})
// Log performance metrics
const duration = Date.now() - startTime
await logMetrics({
event: 'request_completed',
duration,
userId: userContext['User-ID'],
region: userContext['Edge-Region'],
success: true
})
return NextResponse.json(result)
} catch (error) {
const duration = Date.now() - startTime
await logMetrics({
event: 'request_failed',
duration,
userId: userContext['User-ID'] || 'unknown',
region: userContext['Edge-Region'] || 'unknown',
error: error.message,
success: false
})
return NextResponse.json(
{ error: 'Processing failed' },
{ status: 500 }
)
}
}
async function logMetrics(data: any) {
try {
// Send to your analytics service
await fetch(process.env.ANALYTICS_ENDPOINT!, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...data,
timestamp: new Date().toISOString()
})
})
} catch {
// Don't fail the request if logging fails
}
}
Best Practices
Security
- Always validate JWT tokens properly
- Use environment variables for secrets
- Implement proper CORS policies
- Log security events without exposing sensitive data
- Use HTTPS for all external communications
Performance
- Cache user context when possible
- Minimize external API calls in edge functions
- Use efficient JWT libraries for edge environments
- Implement proper timeout handling
- Consider edge storage options (KV, Edge Config)
Monitoring and Debugging
- Include request IDs for tracing
- Log important events without impacting performance
- Monitor edge function performance and errors
- Use structured logging for better analysis
- Set up alerts for critical failures
Cost Optimization
- Cache frequently accessed data
- Minimize compute time in edge functions
- Use appropriate timeout values
- Consider regional deployment strategies
- Monitor and optimize function execution time