Skip to content

Cloudflare Zero Trust Setup

This guide covers setting up Cloudflare as the comprehensive security layer for the Coaching App, handling rate limiting, DDoS protection, bot mitigation, and application-level authentication.

Overview

With Cloudflare Zero Trust, we delegate multiple security concerns to Cloudflare's edge network:

Security Layer Handled By Implementation
Rate Limiting Cloudflare Rate Limiting Rules WAF custom rules
DDoS Protection Cloudflare DDoS Protection Auto-enabled on all plans
Bot Protection Cloudflare Bot Management Challenge pages + JS validation
App Authentication Cloudflare Access + Service Tokens Validates which app (web/mobile)
User Authentication Better-auth sessions HttpOnly cookie, 7-day expiry

Architecture:

graph LR
    A[Web App] -->|Service Token| B[Cloudflare Zero Trust]
    C[Mobile App] -->|Service Token| B
    B -->|Rate Limit| D{Rules Engine}
    D -->|Valid Request| E[Hono Backend]
    D -->|Blocked| F[429/403 Response]
    E -->|User Login| G[JWT Token]
    G -->|Auto-refresh| A
    G -->|Auto-refresh| C

Prerequisites

  • Cloudflare account (Free or paid plan)
  • Domain added to Cloudflare
  • Hono backend accessible via domain (e.g., api.coaching-app.com)

Part 1: Rate Limiting

Protect authentication endpoints from brute force attacks.

1.1 WAF Custom Rules

Navigate to: Security → WAF → Custom rules → Create rule

Rule 1: Login Rate Limiting

Name: rate-limit-login
Expression: (http.request.uri.path eq "/auth/login") and
  (http.request.method eq "POST")
Action: Block
Duration: 15 minutes
Requests per period: 5 requests per 15 minutes
Counting method: Per IP address

Rule 2: Password Reset Rate Limiting

Name: rate-limit-password-reset
Expression: (http.request.uri.path eq "/auth/password/request") and
  (http.request.method eq "POST")
Action: Block
Duration: 1 hour
Requests per period: 3 requests per hour
Counting method: Per IP address

Rule 3: Token Refresh Rate Limiting

Name: rate-limit-refresh
Expression: (http.request.uri.path eq "/auth/refresh") and
  (http.request.method eq "POST")
Action: Challenge (Managed Challenge)
Duration: 1 hour
Requests per period: 20 requests per hour
Counting method: Per IP address

1.2 Response Configuration

Configure custom error responses:

Navigate to: Security → WAF → Custom rules → [Rule] → Edit → Response

{
  "error": {
    "message": "Too many requests. Please try again later.",
    "code": "RATE_LIMIT_EXCEEDED"
  }
}

Part 2: DDoS Protection

DDoS protection is automatically enabled for all Cloudflare customers.

2.1 Verify Protection

Navigate to: Security → DDoS

Ensure these are enabled:

  • ✅ HTTP DDoS Attack Protection
  • ✅ Network-layer DDoS Attack Protection
  • ✅ Advanced TCP Protection (if available on your plan)

2.2 Sensitivity Level

Set to High for production:

  • Blocks more aggressive patterns
  • May have slightly more false positives (rare)
  • Recommended for authentication APIs

Part 3: Bot Management

Protect against automated attacks and credential stuffing.

3.1 Bot Fight Mode (Free Plan)

Navigate to: Security → Bots

Enable:

  • Bot Fight Mode - Challenges automated traffic

3.2 Super Bot Fight Mode (Pro+ Plan)

If on Pro or higher plan:

  • Super Bot Fight Mode
  • Configure: Definitely automated → Block
  • Configure: Likely automated → Challenge
  • Configure: Verified bots → Allow (for Google, etc.)

3.3 JavaScript Detection

Enable JavaScript detection for login pages:

Navigate to: Security → Settings

  • Browser Integrity Check - Ensures valid browser

Part 4: Application Authentication (Service Tokens)

Cloudflare Access with Service Tokens validates which application is calling your API.

4.1 Enable Cloudflare Access

Navigate to: Zero Trust → Settings → Authentication

  1. Set up your team domain: your-team.cloudflareaccess.com
  2. No additional authentication method needed for service tokens

4.2 Create Service Tokens

Navigate to: Zero Trust → Access → Service Auth → Service Tokens → Create Service Token

Token 1: Web Application

Name: web-app-client
Duration: Non-expiring
Scope: (leave as global or limit to specific applications)

Copy both values:

  • Client ID: a1b2c3d4e5f6...
  • Client Secret: x9y8z7w6v5u4... (shown only once!)

Token 2: Mobile Application

Name: mobile-app-client
Duration: Non-expiring
Scope: (leave as global)

Copy both values.

4.3 Create Access Application

Navigate to: Zero Trust → Access → Applications → Add an application → Self-hosted

Application Configuration:

Application name: Coaching App API
Session duration: No duration (service tokens don't create sessions)
Application domain: api.coaching-app.com
Subdomain: api
Domain: coaching-app.com

Add Policy:

Policy name: Allow Service Tokens
Action: Service Auth
Include: Service Token
Select: web-app-client, mobile-app-client

Advanced Settings:

CORS settings:
  ✅ Allow CORS requests
  Allowed origins: https://app.coaching-app.com, http://localhost:19006
  Allowed methods: GET, POST, PUT, PATCH, DELETE
  Allow credentials: Yes

4.4 Configure Path Exemptions

Some paths should bypass service token validation:

Navigate to: Zero Trust → Access → Applications → [Coaching App API] → Edit

Add Bypass policies for:

Policy name: Public Health Check
Action: Bypass
Include: Everyone
Path: /server/health

Part 5: Frontend Integration

5.1 Environment Variables

Web Application (frontend/.env.production):

# Cloudflare Service Token for web app
EXPO_PUBLIC_CF_CLIENT_ID=a1b2c3d4e5f6...
EXPO_PUBLIC_CF_CLIENT_SECRET=x9y8z7w6v5u4...

# API endpoint (behind Cloudflare)
EXPO_PUBLIC_API_URL=https://api.coaching-app.com

Mobile Application (use separate token):

# Cloudflare Service Token for mobile app
EXPO_PUBLIC_CF_CLIENT_ID=m5n6o7p8q9r0...
EXPO_PUBLIC_CF_CLIENT_SECRET=s1t2u3v4w5x6...

EXPO_PUBLIC_API_URL=https://api.coaching-app.com

5.2 Update API Client

Update frontend/src/lib/api.ts:

import axios from 'axios';
import Constants from 'expo-constants';

const API_URL = Constants.expoConfig?.extra?.apiUrl || process.env.EXPO_PUBLIC_API_URL;
const CF_CLIENT_ID = process.env.EXPO_PUBLIC_CF_CLIENT_ID;
const CF_CLIENT_SECRET = process.env.EXPO_PUBLIC_CF_CLIENT_SECRET;

const apiClient = axios.create({
  baseURL: API_URL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
    // Cloudflare Service Token headers
    'CF-Access-Client-Id': CF_CLIENT_ID,
    'CF-Access-Client-Secret': CF_CLIENT_SECRET,
  },
});

// Add JWT token to requests
apiClient.interceptors.request.use((config) => {
  const token = authStore.getState().token;
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

export default apiClient;

5.3 Error Handling

Handle Cloudflare-specific error responses:

apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response) {
      switch (error.response.status) {
        case 403:
          // Cloudflare Access denied (invalid service token)
          console.error('App authentication failed - invalid credentials');
          // Could trigger app update flow
          break;
        case 429:
          // Rate limited by Cloudflare
          const retryAfter = error.response.headers['retry-after'];
          console.error(`Rate limited. Retry after ${retryAfter} seconds`);
          // Show user-friendly message
          break;
        case 1020:
          // Cloudflare Access denied (custom error)
          console.error('Access denied by security policy');
          break;
      }
    }
    return Promise.reject(error);
  }
);

Part 6: Testing & Verification

6.1 Test Rate Limiting

# Should succeed 5 times, then block
for i in {1..6}; do
  curl -X POST https://api.coaching-app.com/auth/login \
    -H "Content-Type: application/json" \
    -H "CF-Access-Client-Id: $CLIENT_ID" \
    -H "CF-Access-Client-Secret: $CLIENT_SECRET" \
    -d '{"email":"test@example.com","password":"wrong"}'
  echo "Attempt $i"
done

Expected: First 5 return 401 (wrong password), 6th returns 429 (rate limited).

6.2 Test Service Token Validation

Without Service Token (should fail with 403):

curl https://api.coaching-app.com/users/me \
  -H "Authorization: Bearer $JWT_TOKEN"

Expected: 403 Forbidden or Cloudflare challenge page.

With Service Token (should succeed):

curl https://api.coaching-app.com/users/me \
  -H "Authorization: Bearer $JWT_TOKEN" \
  -H "CF-Access-Client-Id: $CLIENT_ID" \
  -H "CF-Access-Client-Secret: $CLIENT_SECRET"

Expected: 200 OK with user data.

6.3 Verify Cloudflare Analytics

Navigate to: Zero Trust → Analytics → Access

You should see:

  • Request counts by application
  • Authentication success/failure rates
  • Service token usage by token name

Part 7: Production Checklist

Before going live:

  • All service tokens created and stored securely
  • Rate limiting rules configured and tested
  • DDoS protection enabled (verify in dashboard)
  • Bot Fight Mode or Super Bot Fight Mode enabled
  • Access application configured with correct domain
  • Frontend environment variables set for production
  • Health check endpoint exempted from authentication
  • Test login flow end-to-end
  • Test rate limiting triggers correctly
  • Test mobile app with mobile service token
  • Monitor Cloudflare Analytics for first 24 hours
  • Set up alerts for high block rates (potential false positives)

Part 8: Cost Optimization

Free Plan Features (Sufficient for Most Use Cases)

  • ✅ Rate limiting (10 rules)
  • ✅ DDoS protection (automatic)
  • ✅ Bot Fight Mode (basic bot blocking)
  • ✅ Cloudflare Access (50 users, unlimited service tokens)
  • ✅ WAF custom rules (5 rules)

When to Upgrade to Pro ($20/month)

  • Need more than 10 rate limiting rules
  • Want Super Bot Fight Mode (better bot detection)
  • Need advanced DDOS analytics
  • Require more WAF rules (20 on Pro)

When to Upgrade to Business ($200/month)

  • Need more than 50 Zero Trust users
  • Want advanced bot management with custom rules
  • Need PCI compliance
  • Require 100% uptime SLA

Recommendation for Coaching App: Start with Free Plan for development/staging, upgrade to Pro Plan ($20/month) for production once you have real users.

Troubleshooting

Issue: 403 on All Requests

Cause: Service token not configured or incorrect.

Solution:

  1. Verify CF-Access-Client-Id and CF-Access-Client-Secret headers are sent
  2. Check service token is still active in Cloudflare dashboard
  3. Verify Access application policy includes your service token

Issue: Rate Limiting Too Aggressive

Cause: Rule configured with too low threshold.

Solution:

  1. Adjust rate limit in WAF rules (e.g., 10 requests instead of 5)
  2. Consider using "Challenge" instead of "Block" for less critical endpoints
  3. Whitelist known IPs (office, CI/CD servers) in rate limit rules

Issue: Legitimate Bots Blocked

Cause: Bot Fight Mode blocking search engines or monitoring tools.

Solution:

  1. Navigate to Security → Bots → Configure
  2. Set Verified bots → Allow
  3. Add specific user-agents to allowlist in WAF rules

Issue: Mobile App Not Working

Cause: Mobile service token not configured or using web token.

Solution:

  1. Verify mobile app uses separate service token
  2. Check environment variables are correctly set in app build
  3. Ensure token has not expired (set to non-expiring)

Security Best Practices

  1. Rotate Service Tokens Annually
  2. Create new tokens
  3. Update frontend environment variables
  4. Revoke old tokens after deployment

  5. Monitor Access Logs

  6. Set up Cloudflare Logpush to S3/Splunk
  7. Alert on unusual patterns (spike in 403s)
  8. Review blocked requests weekly

  9. Separate Tokens by Environment

  10. Dev, staging, production should have separate tokens
  11. Makes it easier to revoke/rotate without downtime

  12. Never Commit Tokens to Git

  13. Use environment variables only
  14. Add *.env* to .gitignore
  15. Use secrets management (AWS Secrets Manager, etc.) in CI/CD

  16. Implement Token Validation in Frontend

  17. Check if tokens are set on app startup
  18. Graceful error handling if missing
  19. Provide helpful error messages for developers

Additional Resources

Support

For issues with Cloudflare configuration, contact:

For application-specific issues, see docs/development/development.md.