CSRF Protection
Cross-Site Request Forgery (CSRF) is an attack that tricks users into performing unwanted actions on a web application where they’re authenticated. Fluxbase provides built-in CSRF protection to prevent these attacks.
What is CSRF?
Section titled “What is CSRF?”CSRF attacks exploit the trust a web application has in a user’s browser. If a user is logged into your application, an attacker can trick their browser into making requests to your application without their knowledge.
Example Attack Scenario
Section titled “Example Attack Scenario”- User logs into
https://yourapp.com - User visits malicious site
https://evil.com - Malicious site contains:
<img src="https://yourapp.com/api/v1/tables/users?delete=all" />
- Browser automatically includes authentication cookies
- Unintended action is performed
How Fluxbase Prevents CSRF
Section titled “How Fluxbase Prevents CSRF”Fluxbase implements the Double-Submit Cookie pattern:
- Server generates a random CSRF token
- Token is stored in:
- HTTP-only cookie (not accessible to JavaScript)
- Response header/body (for client to read)
- Client includes token in subsequent requests
- Server validates both tokens match
Request Flow
Section titled “Request Flow”Client Server | | |-- GET /api/v1/users --------->| | | Generate CSRF token |<------- Set-Cookie ----------| Set csrf_token cookie | | |-- POST /api/v1/users -------->| | X-CSRF-Token: abc123 | Validate: | Cookie: csrf_token=abc123 | - Cookie matches header | | - Token exists in storage |<------- 200 OK ---------------|Configuration
Section titled “Configuration”Enable CSRF Protection
Section titled “Enable CSRF Protection”CSRF protection is enabled by default for state-changing methods (POST, PUT, PATCH, DELETE).
Configuration via fluxbase.yaml:
security: csrf: enabled: true token_length: 32 token_lookup: "header:X-CSRF-Token" cookie_name: "csrf_token" cookie_secure: true # Set to true in production cookie_http_only: true cookie_same_site: "Strict" expiration: "24h"Configuration via Environment Variables:
FLUXBASE_SECURITY_CSRF_ENABLED=trueFLUXBASE_SECURITY_CSRF_TOKEN_LENGTH=32FLUXBASE_SECURITY_CSRF_COOKIE_NAME=csrf_tokenFLUXBASE_SECURITY_CSRF_COOKIE_SECURE=trueFLUXBASE_SECURITY_CSRF_COOKIE_SAME_SITE=StrictConfiguration Options
Section titled “Configuration Options”| Option | Default | Description |
|---|---|---|
enabled | true | Enable/disable CSRF protection |
token_length | 32 | Length of CSRF token in bytes |
token_lookup | header:X-CSRF-Token | Where to find the token in requests |
cookie_name | csrf_token | Name of the CSRF cookie |
cookie_secure | false | Mark cookie as HTTPS-only |
cookie_http_only | true | Prevent JavaScript access to cookie |
cookie_same_site | Strict | SameSite attribute (Strict, Lax, None) |
expiration | 24h | How long tokens are valid |
SameSite Cookie Attributes
Section titled “SameSite Cookie Attributes”- Strict: Cookie only sent for same-site requests (most secure)
- Lax: Cookie sent for same-site requests and top-level navigation
- None: Cookie sent for all requests (requires
Secureflag)
# Recommended for most applicationscookie_same_site: "Strict"
# For cross-site authentication flowscookie_same_site: "Lax"
# For third-party integrations (requires HTTPS)cookie_same_site: "None"cookie_secure: trueClient Implementation
Section titled “Client Implementation”Vanilla JavaScript/TypeScript
Section titled “Vanilla JavaScript/TypeScript”// 1. Get CSRF token from cookiefunction getCsrfToken(): string | null { const match = document.cookie.match(/csrf_token=([^;]+)/); return match ? match[1] : null;}
// 2. Include token in requestsasync function makeRequest(url: string, data: any) { const csrfToken = getCsrfToken();
const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken || "", // Include CSRF token Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify(data), credentials: "include", // Include cookies });
if (!response.ok) { throw new Error("Request failed"); }
return response.json();}
// Example usagemakeRequest("/api/v1/tables/users", { name: "John Doe", email: "john@example.com",});Fluxbase SDK (Automatic)
Section titled “Fluxbase SDK (Automatic)”The Fluxbase SDK handles CSRF tokens automatically:
import { createClient } from "@fluxbase/sdk";
const client = createClient("http://localhost:8080", "your-anon-key");
// CSRF token is automatically includedawait client .from("users") .insert({ name: "John Doe", email: "john@example.com", }) .execute();React Example
Section titled “React Example”import { useState, useEffect } from "react";
function getCsrfToken(): string | null { const match = document.cookie.match(/csrf_token=([^;]+)/); return match ? match[1] : null;}
function UserForm() { const [csrfToken, setCsrfToken] = useState<string | null>(null);
useEffect(() => { // Get CSRF token on mount setCsrfToken(getCsrfToken()); }, []);
const handleSubmit = async (e: React.FormEvent) => { e.preventDefault();
const response = await fetch("/api/v1/tables/users", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken || "", }, body: JSON.stringify({ name: "John Doe", email: "john@example.com", }), credentials: "include", });
if (!response.ok) { console.error("Request failed"); } };
return ( <form onSubmit={handleSubmit}> {/* Form fields */} <button type="submit">Submit</button> </form> );}Vue.js Example
Section titled “Vue.js Example”<template> <form @submit.prevent="handleSubmit"> <!-- Form fields --> <button type="submit">Submit</button> </form></template>
<script>export default { methods: { getCsrfToken() { const match = document.cookie.match(/csrf_token=([^;]+)/); return match ? match[1] : null; },
async handleSubmit() { const csrfToken = this.getCsrfToken();
const response = await fetch("/api/v1/tables/users", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken || "", }, body: JSON.stringify({ name: "John Doe", email: "john@example.com", }), credentials: "include", });
if (!response.ok) { console.error("Request failed"); } }, },};</script>Axios Interceptor
Section titled “Axios Interceptor”import axios from "axios";
// Create Axios instanceconst api = axios.create({ baseURL: "http://localhost:8080", withCredentials: true, // Include cookies});
// Add CSRF token to all requestsapi.interceptors.request.use((config) => { const match = document.cookie.match(/csrf_token=([^;]+)/); const csrfToken = match ? match[1] : null;
if (csrfToken && config.headers) { config.headers["X-CSRF-Token"] = csrfToken; }
return config;});
// Usageapi.post("/api/v1/tables/users", { name: "John Doe", email: "john@example.com",});Server-Side Implementation
Section titled “Server-Side Implementation”Custom Middleware (Go)
Section titled “Custom Middleware (Go)”If you’re building custom endpoints, use the CSRF middleware:
package main
import ( "github.com/gofiber/fiber/v2" "github.com/fluxbase-eu/fluxbase/internal/middleware")
func main() { app := fiber.New()
// Apply CSRF middleware app.Use(middleware.CSRF(middleware.CSRFConfig{ TokenLength: 32, TokenLookup: "header:X-CSRF-Token", CookieName: "csrf_token", CookieSecure: true, CookieHTTPOnly: true, CookieSameSite: "Strict", }))
// Your routes app.Post("/api/users", createUser)
app.Listen(":8080")}Excluded Paths
Section titled “Excluded Paths”Some paths are automatically excluded from CSRF protection:
- Safe methods: GET, HEAD, OPTIONS
- WebSocket endpoint:
/realtime - Health checks:
/health,/ready - Metrics:
/metrics - API requests with API key authentication
Testing CSRF Protection
Section titled “Testing CSRF Protection”Manual Testing
Section titled “Manual Testing”1. Test without CSRF token:
# This should fail with 403 Forbiddencurl -X POST http://localhost:8080/api/v1/tables/users \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{"name":"John Doe"}'2. Test with valid CSRF token:
# First, get the CSRF token (from browser cookies or initial request)CSRF_TOKEN="your-csrf-token-here"
# This should succeedcurl -X POST http://localhost:8080/api/v1/tables/users \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "X-CSRF-Token: $CSRF_TOKEN" \ -b "csrf_token=$CSRF_TOKEN" \ -d '{"name":"John Doe"}'Automated Testing
Section titled “Automated Testing”import { describe, it, expect } from "vitest";import { createClient } from "@fluxbase/sdk";
describe("CSRF Protection", () => { it("should reject requests without CSRF token", async () => { const client = createClient("http://localhost:8080", "your-anon-key");
await client.auth.signIn({ email: "user@example.com", password: "password", });
try { // Manually make request without CSRF token await fetch("http://localhost:8080/api/v1/tables/users", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${client.getAuthToken()}`, }, body: JSON.stringify({ name: "John" }), });
// Should not reach here expect(true).toBe(false); } catch (error) { expect(error.response.status).toBe(403); } });
it("should accept requests with valid CSRF token", async () => { const client = createClient("http://localhost:8080", "your-anon-key");
await client.auth.signIn({ email: "user@example.com", password: "password", });
// SDK automatically handles CSRF const { data, error } = await client .from("users") .insert({ name: "John Doe", }) .execute();
expect(error).toBeNull(); expect(data).toBeDefined(); });});Common Issues
Section titled “Common Issues”Issue: “CSRF token validation failed”
Section titled “Issue: “CSRF token validation failed””Cause: CSRF token missing or invalid
Solutions:
-
Ensure cookie is being sent:
fetch(url, {credentials: "include", // Include cookies}); -
Check cookie domain matches:
security:csrf:cookie_domain: ".yourdomain.com" # Allows subdomain.yourdomain.com -
Verify token is included in header:
headers: {'X-CSRF-Token': csrfToken}
Issue: “CSRF token expired”
Section titled “Issue: “CSRF token expired””Cause: Token older than configured expiration
Solution: Request a new token by making a GET request:
// Make a simple GET request to get new CSRF tokenawait fetch("/api/v1/health");
// Token is now refreshed in cookieconst newToken = getCsrfToken();Issue: CSRF with CORS
Section titled “Issue: CSRF with CORS”Cause: Cross-origin requests with credentials
Solution: Configure CORS properly:
server: cors: allowed_origins: - "https://yourdomain.com" allow_credentials: true # Required for cookies// Client must include credentialsfetch(url, { credentials: "include", // Send cookies cross-origin headers: { "X-CSRF-Token": csrfToken, },});Issue: CSRF in mobile apps
Section titled “Issue: CSRF in mobile apps”Cause: Mobile apps don’t use cookies like browsers
Solution: Use API key authentication instead:
// Mobile app using API key (no CSRF needed)const client = createClient("https://api.yourdomain.com", "your-anon-key");Disable CSRF (Not Recommended)
Section titled “Disable CSRF (Not Recommended)”For development or specific use cases, you can disable CSRF:
security: csrf: enabled: false⚠️ Warning: Only disable CSRF if:
- You’re in development
- Using API key authentication exclusively
- Using a different CSRF protection mechanism
- Building a non-browser client (mobile app, CLI)
Best Practices
Section titled “Best Practices”1. Always Use HTTPS in Production
Section titled “1. Always Use HTTPS in Production”server: tls: enabled: true cert_file: /path/to/cert.pem key_file: /path/to/key.pem
security: csrf: cookie_secure: true # Requires HTTPS2. Use Strict SameSite Cookies
Section titled “2. Use Strict SameSite Cookies”security: csrf: cookie_same_site: "Strict" # Most secure3. Set Reasonable Expiration
Section titled “3. Set Reasonable Expiration”security: csrf: expiration: "24h" # Balance security and UX4. Rotate Tokens After Sensitive Actions
Section titled “4. Rotate Tokens After Sensitive Actions”// After password change, force token refreshawait client.auth.changePassword(oldPassword, newPassword);
// Make a GET request to get new CSRF tokenawait fetch("/api/v1/health");5. Monitor CSRF Failures
Section titled “5. Monitor CSRF Failures”// Log CSRF failures for security monitoringapp.Use(func(c *fiber.Ctx) error { err := c.Next() if err != nil && err.Error() == "CSRF token validation failed" { log.Warn(). Str("ip", c.IP()). Str("path", c.Path()). Msg("CSRF validation failed") } return err})Security Considerations
Section titled “Security Considerations”CSRF vs XSS
Section titled “CSRF vs XSS”CSRF protection doesn’t prevent XSS attacks. Always:
- Implement Content Security Policy
- Sanitize user input
- Use secure templating
- Enable security headers
CSRF vs API Keys
Section titled “CSRF vs API Keys”API key authentication bypasses CSRF protection:
- API keys are not stored in cookies
- Intended for server-to-server communication
- Still need proper authentication and authorization
Token Storage
Section titled “Token Storage”Never store CSRF tokens in:
- ❌ LocalStorage (vulnerable to XSS)
- ❌ SessionStorage (vulnerable to XSS)
- ✅ HTTP-only cookies (safe from JavaScript)
Further Reading
Section titled “Further Reading”Summary
Section titled “Summary”Fluxbase provides robust CSRF protection out of the box:
- ✅ Double-submit cookie pattern
- ✅ Automatic token generation
- ✅ HTTP-only cookies
- ✅ SameSite attribute support
- ✅ Configurable expiration
- ✅ SDK handles tokens automatically
Enable CSRF protection in production and follow best practices to protect your users from CSRF attacks.