0
0
Rest-apiHow-ToBeginner · 4 min read

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_token per 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.