How to Implement CSRF Protection in REST API Securely
To implement
CSRF protection in a REST API, use a CSRF token that the server generates and the client sends back in a custom header with each state-changing request. The server verifies this token to ensure the request is legitimate and not forged by a third party.Syntax
CSRF protection typically involves these parts:
- Generate Token: Server creates a unique
csrf_tokenper user session. - Send Token: Server sends this token to the client, often in a cookie or response body.
- Client Sends Token: Client includes the token in a custom HTTP header (e.g.,
X-CSRF-Token) on requests that change data (POST, PUT, DELETE). - Verify Token: Server checks the token from the header against the stored token to allow or reject the request.
python
import secrets from flask import session, request, abort def generate_csrf_token(): # Create a unique token for the user session token = secrets.token_hex(16) session['csrf_token'] = token return token # Client sends token in header: # X-CSRF-Token: <token_value> # Server verifies: if request.headers.get('X-CSRF-Token') != session.get('csrf_token'): abort(403) # Forbidden
Example
This example shows a simple Flask REST API with CSRF protection using tokens sent in a custom header.
python
from flask import Flask, request, session, jsonify, abort import secrets app = Flask(__name__) app.secret_key = 'supersecretkey' @app.route('/get-csrf-token') def get_csrf_token(): token = secrets.token_hex(16) session['csrf_token'] = token return jsonify({'csrf_token': token}) @app.route('/update-data', methods=['POST']) def update_data(): token = request.headers.get('X-CSRF-Token') if not token or token != session.get('csrf_token'): abort(403) # Forbidden # Process the update safely return jsonify({'message': 'Data updated successfully'}) if __name__ == '__main__': app.run(debug=True)
Output
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [Date] "GET /get-csrf-token HTTP/1.1" 200 -
127.0.0.1 - - [Date] "POST /update-data HTTP/1.1" 200 -
Common Pitfalls
- Not verifying tokens on all state-changing requests: Always check the CSRF token on POST, PUT, DELETE methods.
- Sending token only in cookies: Cookies are sent automatically and vulnerable; use a custom header to send the token explicitly.
- Reusing tokens across sessions: Generate a new token per user session to avoid reuse attacks.
- Ignoring CORS policies: CSRF protection complements CORS; ensure both are configured properly.
python
from flask import jsonify, abort, request, session from flask import Flask app = Flask(__name__) ## Wrong way: Accepting requests without token check @app.route('/update-data', methods=['POST']) def update_data_wrong(): # No CSRF token verification return jsonify({'message': 'Data updated without CSRF check'}) ## Right way: Verify token @app.route('/update-data-secure', methods=['POST']) def update_data_right(): token = request.headers.get('X-CSRF-Token') if not token or token != session.get('csrf_token'): abort(403) return jsonify({'message': 'Data updated with CSRF check'})
Quick Reference
- Generate a unique CSRF token per user session.
- Send the token to the client securely (cookie or response body).
- Require the client to send the token in a custom header on unsafe HTTP methods.
- Verify the token on the server before processing the request.
- Reject requests with missing or invalid tokens with HTTP 403 Forbidden.
Key Takeaways
Always generate and verify a unique CSRF token per user session to protect your API.
Send the CSRF token to the client and require it in a custom header for state-changing requests.
Reject requests missing or having invalid CSRF tokens with a 403 Forbidden response.
Do not rely solely on cookies for CSRF protection; use explicit token headers.
Combine CSRF protection with proper CORS settings for best security.