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);
}
}