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