Skip to content

Authentication & Security

Temp edited this page Feb 11, 2026 · 6 revisions

Authentication & Security

R2-Manager-Worker uses Cloudflare Access (Zero Trust) to provide enterprise-grade authentication with GitHub SSO integration.

Overview

The application implements a Zero Trust security model where:

  1. All requests are authenticated by Cloudflare's edge network
  2. No user credentials are stored in the application
  3. JWT tokens are validated on every API request
  4. Access policies are managed centrally in the Cloudflare dashboard

Authentication Flow

┌─────────┐          ┌──────────────────┐          ┌─────────────┐
│  User   │          │ Cloudflare Access │          │   Worker    │
└────┬────┘          └────────┬─────────┘          └──────┬──────┘
     │                         │                            │
     │  1. Visit App           │                            │
     ├────────────────────────>│                            │
     │                         │                            │
     │  2. Check Auth          │                            │
     │<────────────────────────┤                            │
     │                         │                            │
     │  3. Redirect to GitHub  │                            │
     ├────────────────────────>│                            │
     │                         │                            │
     │  4. GitHub OAuth        │                            │
     │<───────────────────────>│                            │
     │                         │                            │
     │  5. Set JWT Cookie      │                            │
     │<────────────────────────┤                            │
     │                         │                            │
     │  6. Access App          │                            │
     ├────────────────────────────────────────────────────>│
     │                         │                            │
     │                         │  7. Validate JWT           │
     │                         │<───────────────────────────┤
     │                         │                            │
     │                         │  8. Return User Email      │
     │                         │────────────────────────────>
     │                         │                            │
     │  9. Return Response     │                            │
     │<────────────────────────────────────────────────────┤

Step-by-Step Process

  1. User visits your domain - Request reaches Cloudflare's edge network
  2. Cloudflare Access intercepts - Checks for valid authentication
  3. GitHub SSO login - User authenticates via GitHub OAuth
  4. JWT cookie issued - cf-access-jwt-assertion cookie is set automatically
  5. Worker receives request - With JWT in cookie or header
  6. JWT validation - Worker validates JWT against Cloudflare's public keys
  7. User email extracted - From validated JWT payload
  8. Access granted - User can manage R2 buckets

JWT Token Structure

The cf-access-jwt-assertion token is a standard JWT with three parts:

Header

{
  "alg": "RS256",
  "kid": "key-id",
  "typ": "JWT"
}

Payload

{
  "aud": ["your-application-aud-tag"],
  "email": "user@example.com",
  "exp": 1234567890,
  "iat": 1234567800,
  "nbf": 1234567800,
  "iss": "https://yourteam.cloudflareaccess.com",
  "type": "app",
  "identity_nonce": "...",
  "sub": "...",
  "country": "US"
}

Signature

Signed with Cloudflare's private key, validated using public keys from:

https://yourteam.cloudflareaccess.com/cdn-cgi/access/certs

JWT Validation Process

The Worker validates JWT tokens using the jose library:

import { jwtVerify, createRemoteJWKSet } from "jose";

async function validateAccessJWT(
  request: Request,
  env: Env,
): Promise<string | null> {
  // 1. Extract token from header
  const token = request.headers.get("cf-access-jwt-assertion");
  if (!token) return null;

  // 2. Create JWK Set from Cloudflare's public keys
  const JWKS = createRemoteJWKSet(
    new URL(`${env.TEAM_DOMAIN}/cdn-cgi/access/certs`),
  );

  // 3. Verify token signature and claims
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: env.TEAM_DOMAIN,
    audience: env.POLICY_AUD,
  });

  // 4. Extract and return user email
  return payload.email as string;
}

Validation Steps

  1. Token extraction - Get JWT from cf-access-jwt-assertion header or cookie
  2. Public key retrieval - Fetch Cloudflare's public keys from JWK endpoint
  3. Signature verification - Verify token was signed by Cloudflare
  4. Issuer check - Ensure token was issued by your team domain
  5. Audience check - Verify token is for your specific application
  6. Expiration check - Ensure token hasn't expired
  7. Email extraction - Extract user email from validated payload

Security Features

✅ Zero Trust Architecture

  • No stored credentials - No passwords, API keys, or tokens in the database
  • Edge authentication - All authentication handled at Cloudflare's edge
  • Policy-based access - Fine-grained control over who can access the app

✅ JWT Token Security

  • RS256 signing - Asymmetric cryptography prevents token forgery
  • Short expiration - Tokens expire based on session duration (default: 24 hours)
  • Audience validation - Tokens work only for specific applications
  • Issuer validation - Tokens must come from your Cloudflare team

✅ Upload Integrity Verification

  • MD5 checksum validation - All uploads verified using industry-standard MD5 checksums
  • Client-side calculation - Checksums calculated before upload using spark-md5 library
  • Server-side verification - ETag from R2 compared with client checksum
  • Automatic detection - Identifies data corruption, network failures, and silent errors
  • Visual feedback - UI displays verification status with "✓ Verified" indicator
  • Prevents data loss - Ensures uploaded files match stored files exactly

✅ HTTPS Only

  • TLS encryption - All traffic encrypted via Cloudflare's network
  • Automatic certificates - Cloudflare manages SSL/TLS certificates
  • HSTS support - HTTP Strict Transport Security can be enabled

✅ Signed URLs

  • HMAC-SHA256 signatures - Download URLs include tamper-proof signatures
  • Timestamp-based - Each URL includes unique timestamp
  • Path validation - Signatures cover full URL path and query parameters
function generateSignature(path: string, env: Env): string {
  const encoder = new TextEncoder();
  const data = encoder.encode(path + env.URL_SIGNING_KEY);
  const hashArray = new Uint8Array(data);
  const reducedHash = Array.from(hashArray).reduce((a, b) => a ^ b, 0);
  return reducedHash.toString(16).padStart(2, "0");
}

✅ CORS Protection

  • Origin validation - CORS headers configured for legitimate domains
  • Method restrictions - Only allowed HTTP methods permitted
  • Header controls - Limited set of allowed request headers

✅ No Stored Passwords

  • OAuth delegation - Authentication handled by GitHub
  • SSO integration - Enterprise users can leverage existing identity providers
  • Session management - Cloudflare manages session cookies

✅ Rate Limiting

API endpoints are protected by intelligent rate limiting to prevent abuse and ensure fair resource allocation.

Tiered Rate Limits:

  • READ Operations (GET): 100 requests per 60 seconds
  • WRITE Operations (POST/PATCH): 30 requests per 60 seconds
  • DELETE Operations: 10 requests per 60 seconds

Key Benefits:

  • Abuse Prevention - Protects against brute force attacks and API abuse
  • Fair Resource Sharing - Ensures no single user can monopolize system resources
  • DDoS Mitigation - Automatic throttling of excessive requests
  • Per-User Enforcement - Independent limits for each authenticated user
  • Automatic Logging - All violations logged for security monitoring

How It Works:

  1. Rate limits are enforced after JWT validation
  2. Each user (identified by email) has independent rate limit counters
  3. Limits are applied per Cloudflare location for optimal performance
  4. When exceeded, requests return 429 Too Many Requests with retry guidance
  5. Violations are logged with timestamp, user email, and endpoint details

Response on Rate Limit Exceeded:

{
  "error": "Rate limit exceeded",
  "message": "You have exceeded the read operation rate limit of 100 requests per 60 seconds. Please wait before retrying.",
  "tier": "READ",
  "limit": 100,
  "period": 60,
  "retryAfter": 60
}

Local Development: Rate limiting is automatically bypassed for localhost requests to simplify development and testing workflows.

Configuration: Rate limits are configurable via wrangler.toml and require Wrangler 4.36.0+. See Configuration Reference for details.

Access Control

Policy Types

Cloudflare Access supports multiple policy types:

Allow Policies:

  • Grant access to specific users, groups, or identity providers
  • Example: Allow all users from GitHub organization "your-org"

Deny Policies:

  • Block access for specific users or groups
  • Example: Deny access to specific email addresses

Bypass Policies:

  • Skip authentication for specific paths
  • Example: Allow public access to /site.webmanifest, /favicon.ico

Default Configuration

By default, R2-Manager-Worker is configured with:

  1. Allow Policy - Authenticated GitHub users
  2. Bypass Policy - Static assets (manifests, icons, logos)

Restricting Access

To limit access to specific users:

Option 1: Cloudflare Access Policies

Update your Access application policies in the Cloudflare dashboard:

  1. Go to AccessApplications → Select your app
  2. Edit the Allow policy
  3. Change from "Everyone" to specific:
    • GitHub organizations
    • Email addresses
    • Email domains
    • Groups

Option 2: Worker-Level Authorization

Add email validation in the Worker code:

// In worker/index.ts
const ALLOWED_USERS = ["admin@example.com", "user@example.com"];

async function handleApiRequest(request: Request, env: Env): Promise<Response> {
  const userEmail = await validateAccessJWT(request, env);

  if (!userEmail) {
    return new Response("Unauthorized", { status: 401 });
  }

  // Additional authorization check
  if (!ALLOWED_USERS.includes(userEmail)) {
    return new Response("Forbidden", { status: 403 });
  }

  // Continue with request handling...
}

Per-Bucket Access Control (Future)

Future enhancements planned for access control:

  • Per-bucket ownership tracking
  • Role-based access control (RBAC)
  • User-specific bucket views
  • Audit logging with detailed tracking

Local Development

During local development, JWT validation and rate limiting are disabled to simplify testing:

  • Requests to localhost skip authentication
  • Rate limiting is automatically bypassed
  • All API endpoints are accessible without tokens
  • Production JWT validation is not enforced

To test with authentication:

  1. Deploy to production, or
  2. Set VITE_WORKER_API to your deployed Worker URL

Testing Authentication

Test JWT Token in Browser

  1. Open your deployed application
  2. Log in via GitHub SSO
  3. Open DevTools (F12) → Application tab → Cookies
  4. Find cf-access-jwt-assertion and copy its value
  5. Visit jwt.io and paste the token
  6. Verify:
    • iss matches your team domain
    • aud contains your Application Audience Tag
    • Token is not expired (check exp timestamp)

Test with curl

curl -H "Cookie: cf-access-jwt-assertion=YOUR_JWT_TOKEN" \
     https://YOUR_DOMAIN/api/buckets

Replace:

  • YOUR_JWT_TOKEN with the token from your browser
  • YOUR_DOMAIN with your Worker's domain

Test Token Validation

Enable debug logging in worker/index.ts:

console.log("JWT Validation:", {
  token: token ? "Present" : "Missing",
  issuer: env.TEAM_DOMAIN,
  audience: env.POLICY_AUD,
});

View logs with:

npx wrangler tail

Security Best Practices

✅ DO

  • Keep API_KEY and URL_SIGNING_KEY secrets secure
  • Use GitHub organizations for team access control
  • Enable 2FA on your Cloudflare account
  • Regularly review Access logs in Cloudflare dashboard
  • Set appropriate session durations (24 hours is reasonable)
  • Use custom domains with your own SSL certificates

❌ DON'T

  • Share JWT tokens between users
  • Disable HTTPS or use HTTP in production
  • Store secrets in code or version control
  • Use weak or predictable signing keys
  • Allow public access to API endpoints
  • Bypass Cloudflare Access for authenticated routes

Additional Resources


Questions? Check the Troubleshooting page or open an issue on GitHub.

R2 Bucket Manager Wiki

Getting Started

Core Features

Development

Security & Authentication

Support & Resources

External Links

Clone this wiki locally