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
- Set up your team domain:
your-team.cloudflareaccess.com - 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
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:
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):
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:
- Verify
CF-Access-Client-IdandCF-Access-Client-Secretheaders are sent - Check service token is still active in Cloudflare dashboard
- Verify Access application policy includes your service token
Issue: Rate Limiting Too Aggressive¶
Cause: Rule configured with too low threshold.
Solution:
- Adjust rate limit in WAF rules (e.g., 10 requests instead of 5)
- Consider using "Challenge" instead of "Block" for less critical endpoints
- 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:
- Navigate to Security → Bots → Configure
- Set Verified bots → Allow
- 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:
- Verify mobile app uses separate service token
- Check environment variables are correctly set in app build
- Ensure token has not expired (set to non-expiring)
Security Best Practices¶
- Rotate Service Tokens Annually
- Create new tokens
- Update frontend environment variables
-
Revoke old tokens after deployment
-
Monitor Access Logs
- Set up Cloudflare Logpush to S3/Splunk
- Alert on unusual patterns (spike in 403s)
-
Review blocked requests weekly
-
Separate Tokens by Environment
- Dev, staging, production should have separate tokens
-
Makes it easier to revoke/rotate without downtime
-
Never Commit Tokens to Git
- Use environment variables only
- Add
*.env*to.gitignore -
Use secrets management (AWS Secrets Manager, etc.) in CI/CD
-
Implement Token Validation in Frontend
- Check if tokens are set on app startup
- Graceful error handling if missing
- Provide helpful error messages for developers
Additional Resources¶
- Cloudflare Zero Trust Documentation
- Service Tokens Guide
- Rate Limiting Rules
- Bot Management
- DDoS Protection
Support¶
For issues with Cloudflare configuration, contact:
- Cloudflare Support (if on paid plan)
- Community Forums: community.cloudflare.com
For application-specific issues, see docs/development/development.md.