Authentication Methods Overview

Method Use Case Pros Cons
JWT API authentication, stateless apps Fast, stateless, scalable Token revocation challenges
OAuth Social login, third-party access Delegated authorization, secure Complex setup, requires providers
Cloudflare Access Zero-trust security, enterprise Zero-trust, SSO integration Cloudflare-specific
Basic Auth Simple APIs, internal tools Simple implementation Credentials in every request

JWT Authentication

JWT (JSON Web Tokens) are perfect for Workers due to their stateless nature and fast verification.

JWT Implementation

import jwt from 'jsonwebtoken';

export default {
  async fetch(request, env) {
    // Verify JWT token
    const authHeader = request.headers.get('Authorization');
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      return new Response('Unauthorized', { status: 401 });
    }

    const token = authHeader.substring(7); // Remove 'Bearer '

    try {
      const decoded = jwt.verify(token, env.JWT_SECRET);
      // Token is valid, proceed with request
      return new Response(`Hello ${decoded.userId}!`);
    } catch (error) {
      return new Response('Invalid token', { status: 401 });
    }
  }
}

Creating JWT Tokens

// Login endpoint that creates JWT
export async function handleLogin(request, env) {
  const { email, password } = await request.json();

  // Verify credentials (implement your own logic)
  const user = await verifyCredentials(email, password);
  if (!user) {
    return new Response('Invalid credentials', { status: 401 });
  }

  // Create JWT token
  const token = jwt.sign(
    {
      userId: user.id,
      email: user.email,
      exp: Math.floor(Date.now() / 1000) + (24 * 60 * 60) // 24 hours
    },
    env.JWT_SECRET
  );

  return new Response(JSON.stringify({ token }), {
    headers: { 'Content-Type': 'application/json' }
  });
}

JWT Middleware

// Reusable JWT authentication middleware
export async function authenticate(request, env) {
  const authHeader = request.headers.get('Authorization');

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    throw new Error('Missing or invalid authorization header');
  }

  const token = authHeader.substring(7);

  try {
    const decoded = jwt.verify(token, env.JWT_SECRET);

    // Add user info to request
    request.user = decoded;
    return request;
  } catch (error) {
    throw new Error('Invalid or expired token');
  }
}

// Usage in routes
export default {
  async fetch(request, env) {
    try {
      const authenticatedRequest = await authenticate(request, env);

      // Handle authenticated request
      return new Response(`Welcome ${authenticatedRequest.user.email}!`);
    } catch (error) {
      return new Response(error.message, { status: 401 });
    }
  }
}

OAuth Integration

OAuth allows users to authenticate using third-party providers like Google, GitHub, or Auth0.

OAuth Flow Implementation

// OAuth initiation
export async function handleOAuthStart(request, env) {
  const state = crypto.randomUUID(); // CSRF protection

  // Store state in Durable Object or KV for verification
  await env.OAUTH_STATE.put(state, 'pending', { expirationTtl: 300 }); // 5 min

  const authUrl = new URL('https://github.com/login/oauth/authorize');
  authUrl.searchParams.set('client_id', env.GITHUB_CLIENT_ID);
  authUrl.searchParams.set('redirect_uri', `${env.BASE_URL}/oauth/callback`);
  authUrl.searchParams.set('scope', 'user:email');
  authUrl.searchParams.set('state', state);

  return Response.redirect(authUrl.toString());
}

// OAuth callback
export async function handleOAuthCallback(request, env) {
  const url = new URL(request.url);
  const code = url.searchParams.get('code');
  const state = url.searchParams.get('state');

  // Verify state to prevent CSRF
  const storedState = await env.OAUTH_STATE.get(state);
  if (!storedState) {
    return new Response('Invalid state', { status: 400 });
  }

  // Exchange code for access token
  const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      client_id: env.GITHUB_CLIENT_ID,
      client_secret: env.GITHUB_CLIENT_SECRET,
      code: code,
      redirect_uri: `${env.BASE_URL}/oauth/callback`
    })
  });

  const tokenData = await tokenResponse.json();

  if (tokenData.error) {
    return new Response('OAuth failed', { status: 400 });
  }

  // Get user info
  const userResponse = await fetch('https://api.github.com/user', {
    headers: {
      'Authorization': `Bearer ${tokenData.access_token}`,
      'User-Agent': 'Clodo-App'
    }
  });

  const userData = await userResponse.json();

  // Create session or JWT for your app
  const sessionToken = jwt.sign({
    userId: userData.id,
    provider: 'github',
    exp: Math.floor(Date.now() / 1000) + (7 * 24 * 60 * 60) // 7 days
  }, env.JWT_SECRET);

  // Redirect to app with session token
  return Response.redirect(`${env.FRONTEND_URL}/login/success?token=${sessionToken}`);
}

OAuth with Auth0

// Auth0 integration
export async function handleAuth0Login(request, env) {
  const authUrl = new URL(`https://${env.AUTH0_DOMAIN}/authorize`);
  authUrl.searchParams.set('client_id', env.AUTH0_CLIENT_ID);
  authUrl.searchParams.set('redirect_uri', `${env.BASE_URL}/auth0/callback`);
  authUrl.searchParams.set('response_type', 'code');
  authUrl.searchParams.set('scope', 'openid profile email');
  authUrl.searchParams.set('state', crypto.randomUUID());

  return Response.redirect(authUrl.toString());
}

export async function handleAuth0Callback(request, env) {
  const url = new URL(request.url);
  const code = url.searchParams.get('code');

  // Exchange code for tokens
  const tokenResponse = await fetch(`https://${env.AUTH0_DOMAIN}/oauth/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'authorization_code',
      client_id: env.AUTH0_CLIENT_ID,
      client_secret: env.AUTH0_CLIENT_SECRET,
      code: code,
      redirect_uri: `${env.BASE_URL}/auth0/callback`
    })
  });

  const tokens = await tokenResponse.json();

  // Decode ID token to get user info
  const userInfo = jwt.decode(tokens.id_token);

  return new Response(JSON.stringify({
    access_token: tokens.access_token,
    user: userInfo
  }));
}

Cloudflare Access

Cloudflare Access provides zero-trust security with SSO integration.

Access JWT Validation

// Validate Cloudflare Access JWT
export async function validateAccessToken(request, env) {
  const token = request.headers.get('Cf-Access-Jwt-Assertion');

  if (!token) {
    return new Response('Access token missing', { status: 401 });
  }

  try {
    // Fetch Cloudflare's public keys
    const keysResponse = await fetch(`https://${env.CLOUDFLARE_TEAM}.cloudflareaccess.com/cdn-cgi/access/certs`);
    const keys = await keysResponse.json();

    // Verify JWT (simplified - use a proper JWT library)
    const decoded = jwt.verify(token, keys.keys[0].x, {
      issuer: `https://${env.CLOUDFLARE_TEAM}.cloudflareaccess.com`,
      audience: env.ACCESS_AUDIENCE
    });

    return decoded;
  } catch (error) {
    return new Response('Invalid access token', { status: 401 });
  }
}

// Protected route
export default {
  async fetch(request, env) {
    const user = await validateAccessToken(request, env);

    if (!user) {
      return new Response('Unauthorized', { status: 401 });
    }

    return new Response(`Hello ${user.email}!`);
  }
}

Access with Service Tokens

// Service token authentication for API-to-API
export async function validateServiceToken(request, env) {
  const token = request.headers.get('Authorization')?.replace('Bearer ', '');

  if (!token) {
    return false;
  }

  // Verify service token (implement your own validation)
  const expectedToken = await env.SERVICE_TOKENS.get('api-service');

  return token === expectedToken;
}

// API route with service token auth
export async function handleAPIRequest(request, env) {
  const isValidService = await validateServiceToken(request, env);

  if (!isValidService) {
    return new Response('Invalid service token', { status: 401 });
  }

  // Process API request
  return new Response('API response');
}

Turnstile Bot Protection

Turnstile provides invisible bot protection and CAPTCHA challenges.

Turnstile Integration

// Verify Turnstile token
export async function verifyTurnstile(token, env) {
  const response = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      secret: env.TURNSTILE_SECRET,
      response: token
    })
  });

  const result = await response.json();
  return result.success;
}

// Login with Turnstile verification
export async function handleLogin(request, env) {
  const { email, password, turnstileToken } = await request.json();

  // Verify Turnstile token first
  const isValidBot = await verifyTurnstile(turnstileToken, env);
  if (!isValidBot) {
    return new Response('Bot verification failed', { status: 400 });
  }

  // Proceed with authentication
  const user = await verifyCredentials(email, password);
  if (!user) {
    return new Response('Invalid credentials', { status: 401 });
  }

  const token = jwt.sign({ userId: user.id }, env.JWT_SECRET);
  return new Response(JSON.stringify({ token }));
}

Frontend Turnstile Integration

<!DOCTYPE html>
<html>
<head>
  <title>Login with Turnstile</title>
  <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
  <form id="loginForm">
    <input type="email" name="email" placeholder="Email" required>
    <input type="password" name="password" placeholder="Password" required>

    <!-- Turnstile widget -->
    <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>

    <button type="submit">Login</button>
  </form>

  <script>
    document.getElementById('loginForm').addEventListener('submit', async (e) => {
      e.preventDefault();

      const formData = new FormData(e.target);
      const turnstileToken = formData.get('cf-turnstile-response');

      const response = await fetch('/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          email: formData.get('email'),
          password: formData.get('password'),
          turnstileToken: turnstileToken
        })
      });

      const result = await response.json();
      if (response.ok) {
        localStorage.setItem('token', result.token);
        window.location.href = '/dashboard';
      } else {
        alert(result.error);
      }
    });
  </script>
</body>
</html>

Session Management with Durable Objects

Use Durable Objects for server-side session storage in stateless Workers.

Durable Object Session Store

// Session Durable Object
export class SessionStore {
  constructor(state) {
    this.state = state;
    this.sessions = new Map();
  }

  async handleSession(request) {
    const url = new URL(request.url);

    if (request.method === 'POST') {
      // Create session
      const sessionId = crypto.randomUUID();
      const sessionData = await request.json();

      this.sessions.set(sessionId, {
        ...sessionData,
        created: Date.now(),
        lastAccess: Date.now()
      });

      return new Response(JSON.stringify({ sessionId }));
    }

    if (request.method === 'GET') {
      // Get session
      const sessionId = url.searchParams.get('id');

      if (!this.sessions.has(sessionId)) {
        return new Response('Session not found', { status: 404 });
      }

      const session = this.sessions.get(sessionId);
      session.lastAccess = Date.now();

      return new Response(JSON.stringify(session));
    }

    if (request.method === 'DELETE') {
      // Delete session
      const sessionId = url.searchParams.get('id');
      this.sessions.delete(sessionId);

      return new Response('Session deleted');
    }

    return new Response('Method not allowed', { status: 405 });
  }
}

export default {
  async fetch(request, env) {
    const id = env.SESSION_STORE.idFromName('global-sessions');
    const stub = env.SESSION_STORE.get(id);

    return stub.fetch(request);
  }
}

Session Middleware

// Session middleware for Workers
export async function withSession(handler, env) {
  return async (request) => {
    const sessionId = getSessionIdFromRequest(request);

    if (sessionId) {
      // Get session data
      const sessionResponse = await env.SESSION_STORE.fetch(
        `${env.SESSION_STORE_URL}?id=${sessionId}`
      );

      if (sessionResponse.ok) {
        const sessionData = await sessionResponse.json();
        request.session = sessionData;
      }
    }

    const response = await handler(request, env);

    // Set session cookie if needed
    if (request.newSession) {
      const sessionCookie = `session=${request.newSession.id}; HttpOnly; Secure; SameSite=Strict`;
      response.headers.set('Set-Cookie', sessionCookie);
    }

    return response;
  };
}

function getSessionIdFromRequest(request) {
  const cookie = request.headers.get('Cookie');
  if (!cookie) return null;

  const sessionMatch = cookie.match(/session=([^;]+)/);
  return sessionMatch ? sessionMatch[1] : null;
}

// Usage
const authenticatedHandler = withSession(async (request, env) => {
  if (!request.session) {
    return new Response('Not authenticated', { status: 401 });
  }

  return new Response(`Hello ${request.session.user.email}!`);
}, env);

Security Best Practices

Rate Limiting

// Rate limiting with Durable Objects
export class RateLimiter {
  constructor(state) {
    this.state = state;
    this.requests = new Map();
  }

  async checkLimit(identifier, maxRequests = 100, windowMs = 60000) {
    const now = Date.now();
    const windowStart = now - windowMs;

    if (!this.requests.has(identifier)) {
      this.requests.set(identifier, []);
    }

    const userRequests = this.requests.get(identifier);

    // Remove old requests outside the window
    const validRequests = userRequests.filter(time => time > windowStart);
    this.requests.set(identifier, validRequests);

    if (validRequests.length >= maxRequests) {
      return false; // Rate limit exceeded
    }

    validRequests.push(now);
    return true; // Within limit
  }
}

// Usage in Worker
export default {
  async fetch(request, env) {
    const clientIP = request.headers.get('CF-Connecting-IP');
    const limiterId = env.RATE_LIMITER.idFromName(clientIP);
    const limiter = env.RATE_LIMITER.get(limiterId);

    const withinLimit = await limiter.checkLimit(clientIP);

    if (!withinLimit) {
      return new Response('Rate limit exceeded', {
        status: 429,
        headers: { 'Retry-After': '60' }
      });
    }

    // Process request
    return new Response('OK');
  }
}

Input Validation and Sanitization

// Input validation utilities
export function validateEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

export function sanitizeInput(input) {
  if (typeof input !== 'string') return input;

  // Remove potentially dangerous characters
  return input.replace(/[<>'"&]/g, '');
}

export function validatePassword(password) {
  // At least 8 characters, one uppercase, one lowercase, one number
  const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$/;
  return passwordRegex.test(password);
}

// Usage in auth endpoint
export async function handleRegistration(request, env) {
  const { email, password, name } = await request.json();

  // Validate inputs
  if (!validateEmail(email)) {
    return new Response('Invalid email', { status: 400 });
  }

  if (!validatePassword(password)) {
    return new Response('Password too weak', { status: 400 });
  }

  const sanitizedName = sanitizeInput(name);

  // Proceed with registration
  const user = await createUser(env, email, password, sanitizedName);

  return new Response(JSON.stringify(user));
}

Secure Headers

// Security headers middleware
export function addSecurityHeaders(response) {
  const headers = new Headers(response.headers);

  // Prevent clickjacking
  headers.set('X-Frame-Options', 'DENY');

  // Prevent MIME type sniffing
  headers.set('X-Content-Type-Options', 'nosniff');

  // Enable XSS protection
  headers.set('X-XSS-Protection', '1; mode=block');

  // Strict transport security
  headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');

  // Content Security Policy
  headers.set('Content-Security-Policy', "default-src 'self'; script-src 'self'");

  // Referrer policy
  headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');

  return new Response(response.body, {
    status: response.status,
    statusText: response.statusText,
    headers
  });
}

// Apply to all responses
export default {
  async fetch(request, env) {
    const response = await handleRequest(request, env);
    return addSecurityHeaders(response);
  }
}