CSRF (Cross-Site Request Forgery) attacks are a security threat that allows attackers to perform unintended actions using a victim's identity. To defend against such attacks, front-end developers typically need to combine back-end validation with specific front-end strategies.
Token Validation
This is the most common defense mechanism, involving the generation of a CSRF token exchanged between the client and server.
Server-Side Token Generation
After a user logs in successfully, the server generates a CSRF token and stores it in the user’s session.
# Python example
from flask import Flask, session
app = Flask(__name__)
@app.route('/login')
def login():
session['csrf_token'] = generate_csrf_token()
return render_template('login.html', csrf_token=session['csrf_token'])
Client-Side Token Usage
Embed the token in each protected form or include it as part of an HTTP header.
<!-- HTML example -->
<form action="/protected" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<!-- ...other form fields... -->
</form>
// JavaScript example (using Fetch API)
fetch('/protected', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRF-Token': window.csrfToken
},
body: formData
});
Checking the Referer Header
Although not entirely reliable due to the possibility of browsers disabling or tampering with the Referer header, it can serve as a supplementary defense measure.
# Python example (Flask middleware)
from flask import request
@app.before_request
def check_referer():
if request.method in ['POST', 'PUT', 'DELETE']:
if not request.referrer or request.referrer != app.config['APP_BASE_URL']:
return abort(403)
Two-Factor Authentication
Require users to confirm sensitive operations with a secondary step, such as a pop-up or verification code.
SameSite Cookie Attribute
Set the SameSite
attribute of cookies to Lax
or Strict
to restrict cross-site submissions. Lax
allows cookies in top-level navigation, while Strict
is more restrictive, allowing cookies only for same-site requests.
# Python example (using Flask-Session extension)
from flask_session import Session
from flask import Flask
app = Flask(__name__)
Session(app)
app.session_interface.cookie_samesite = 'Lax'
POST-Only Interfaces
For sensitive operations, accept only POST requests and avoid GET requests, as GET requests are prone to misuse by browser bookmarks, link preloading, or third-party scripts.
In front-end code, defending against CSRF typically involves including a CSRF token in form submissions or Ajax requests. Validating the token’s authenticity on the back end is critical, as front-end defenses can be bypassed.
Example: Node.js + Express with CSRF Protection
// server.js
const express = require('express');
const bodyParser = require('body-parser');
const csrf = require('csurf');
const app = express();
// Use cookie-parser middleware to parse cookies
app.use(require('cookie-parser')());
// Use middleware to generate and inject CSRF tokens
const csrfProtection = csrf({ cookie: true });
app.use(csrfProtection);
// Protect route
app.post('/protected', csrfProtection, (req, res) => {
// Validate token and perform action
if (req.csrfToken() === req.body.csrfToken) {
// Safe operation
} else {
// Invalid CSRF token, return error
res.status(403).send('CSRF token mismatch');
}
});
// Respond with HTML page containing CSRF token
app.get('/', (req, res) => {
res.send(`
<form action="/protected" method="POST">
<input type="hidden" name="csrfToken" value="${req.csrfToken()}">
<button type="submit">Submit</button>
</form>
`);
});
app.listen(3000);
Using CSP (Content Security Policy)
Content Security Policy (CSP) is a web security strategy that reduces the risk of cross-site scripting (XSS) attacks by specifying which external resources can be loaded or executed. While CSP primarily targets XSS, it can indirectly help defend against CSRF by limiting the ability of third-party sites to initiate requests to your application when properly configured.
// Set CSP header in an Express application
app.use((req, res, next) => {
res.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'");
next();
});
Verifying the Origin Header
Although less reliable than CSRF tokens, checking the Origin header can serve as a supplementary defense in certain scenarios. If a request originates from an untrusted source, the server can reject it.
# Flask example
from flask import request
@app.before_request
def verify_origin():
if request.method == 'POST':
allowed_origins = ['https://your-trusted-origin.com']
origin = request.headers.get('Origin')
if origin not in allowed_origins:
return 'Unauthorized', 401
HSTS (HTTP Strict Transport Security)
Enforcing HTTPS-only communication with the server prevents man-in-the-middle attacks, reducing CSRF risks by making it harder for attackers to intercept or modify HTTPS communications.
# Flask example
@app.after_request
def apply_hsts(response):
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
return response
Using a Web Application Firewall (WAF)
A Web Application Firewall provides an additional layer of protection by detecting and blocking malicious requests, including CSRF attacks. WAFs can be hardware devices, software services, or cloud-based solutions, typically using signature matching, behavioral analysis, or machine learning algorithms to identify and block attacks.
Rate Limiting Sensitive Operations
Limiting the frequency of sensitive operations can help prevent large-scale CSRF attacks. For example, restricting users to a few password changes or account deletions per minute.
// Simple rate limiting with JavaScript
let lastRequestTime = null;
const LIMIT = 60 * 1000; // One minute
function submitSensitiveAction() {
const currentTime = Date.now();
if (lastRequestTime && currentTime - lastRequestTime < LIMIT) {
alert('Too many requests, please try again later.');
return;
}
// Submit request...
lastRequestTime = currentTime;
}
Avoiding GET Requests for Sensitive Operations
While POST requests are generally considered safer than GET requests, GET requests may be unavoidable in some cases. In such scenarios, ensure URLs are not cached or stored in browser history to reduce CSRF risks. For example, use dynamically generated random parameters instead of exposing sensitive information directly.
# Django example
from django.http import HttpResponseBadRequest
def sensitive_get_view(request):
if not request.GET.get('random_token') == request.session.get('random_token'):
return HttpResponseBadRequest('Invalid token')
# Perform sensitive operation...
# Clear random token to prevent replay attacks
request.session.pop('random_token', None)
Using OAuth 2.0 or JWT
Modern web applications often use OAuth 2.0 or JSON Web Tokens (JWT) for authentication. These protocols provide built-in mechanisms to verify request origins and authorization states, reducing CSRF risks. However, proper implementation is critical, as these protocols have their own security challenges.
Keeping Frameworks and Libraries Updated
Ensure that front-end frameworks, back-end libraries, and server software are kept up to date to benefit from the latest security patches and fixes.
Secure Coding and Testing
Following secure coding best practices, such as input validation, output encoding, error handling, and logging, can help identify and fix potential vulnerabilities. Additionally, regular security testing, such as penetration testing and code reviews, ensures the application is secure before deployment.
Top comments (0)