Skip to content

CSRF Protection for APIs — Complete Anti-Forgery Guide

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about CSRF Protection for APIs. We cover key concepts, practical examples, and best practices to help you master this topic.

Cross-Site Request Forgery (CSRF) tricks an authenticated user into performing actions on a web application without their knowledge. For APIs, CSRF protection ensures that requests originate from legitimate sources.

What You'll Learn

You'll learn how CSRF Attacks work, protection strategies including anti-CSRF tokens, SameSite cookies, and custom headers.

Why It Matters

CSRF can force users to change email, transfer funds, or delete accounts without consent. Modern APIs must defend against CSRF even though browsers have improved protections.

Real-World Use

A banking API with CSRF protection blocked an attack where a malicious site submitted a fund transfer form using the user's active session cookie. The missing CSRF token prevented the Transaction.

sequenceDiagram
    participant User
    participant Victim as Bank Website
    participant Attacker as Malicious Site
    participant API as Bank API

    User->>Victim: Login
    Victim->>User: Session Cookie + CSRF Token
    User->>Attacker: Visits Malicious Site
    Attacker->>User: Hidden Form to Bank API
    User->>API: Auto-submit Form (Cookie sent)
    API->>API: Check CSRF Token
    API-->>Attacker: Missing Token → Rejected

Teacher's Mindset

CSRF is like a con artist handing a signed blank check to a bank teller. The check has the victim's real signature (cookie), but the teller asks for a second ID (CSRF token) that the con artist doesn't have.

Implementing CSRF Protection

from flask import Flask, request, jsonify, session
import secrets

app = Flask(__name__)
app.secret_key = "your-secret-key"

@app.route("/api/csrf-token", methods=["GET"])
def get_csrf_token():
    token = secrets.token_hex(32)
    session["csrf_token"] = token
    return jsonify({"csrf_token": token})

@app.route("/api/transfer", methods=["POST"])
def transfer():
    csrf_token = request.headers.get("X-CSRF-Token")
    if not csrf_token or csrf_token != session.get("csrf_token"):
        return jsonify({"error": "CSRF validation failed"}), 403
    session["csrf_token"] = secrets.token_hex(32)
    return jsonify({"message": "Transfer completed"})
# Double submit cookie pattern
def generate_double_submit_cookie():
    token = secrets.token_hex(32)
    response = jsonify({"message": "Token set"})
    response.set_cookie("csrf_token", token, httponly=True, samesite="Lax")
    return response, token

def validate_double_submit():
    cookie_token = request.cookies.get("csrf_token")
    header_token = request.headers.get("X-CSRF-Token")
    if not cookie_token or not header_token:
        return False
    return secrets.compare_digest(cookie_token, header_token)
# SameSite cookie protection
def set_samesite_cookies(response):
    response.set_cookie(
        "session",
        session["user_id"],
        httponly=True,
        secure=True,
        samesite="Strict"
    )
    return response

# Custom header validation
@app.before_request
def validate_custom_header():
    if request.method in ["POST", "PUT", "DELETE", "PATCH"]:
        content_type = request.headers.get("Content-Type", "")
        if "application/json" not in content_type:
            return jsonify({"error": "Invalid Content-Type"}), 415

Common Mistakes

Mistake Why It's Wrong Fix
Relying only on POST vs GET Attackers can submit POST forms too Use CSRF tokens or custom headers
Token in URL parameter Token appears in browser history and logs Send token in header or hidden form field
Using GET for state-changing operations CSRF via IMG tags and links Use POST, PUT, DELETE for mutations
Not rotating tokens Compromised tokens work for the whole session Rotate token after each state change
Exposing CSRF token in error messages Token leak defeats the purpose Never return tokens in error responses

Practice Questions

  1. How does a CSRF attack work?
  2. What is the difference between SameSite Lax and Strict?
  3. Why is the double-submit cookie pattern useful for APIs?
  4. How do custom headers prevent CSRF?
  5. What is the difference between CSRF and XSS?

Challenge

Build a Flask API with CSRF protection using the double-submit cookie pattern. Test with and without the CSRF header using curl.

FAQ

Does using JSON prevent CSRF?

Not automatically. Browsers can send POST requests with Content-Type: application/json from forms. Use CSRF tokens or check the Origin header.

Is CSRF relevant for APIs that don't use cookies?

No. CSRF exploits cookie-based authentication. If you use header-based auth (Bearer token), you don't need CSRF protection.

What is the Origin header and how does it prevent CSRF?

The Origin header indicates where the request originated. APIs can check that Origin matches the expected domain. However, Origin is not always sent.

Does CORS prevent CSRF?

No. CORS controls cross-origin reads, not writes. CSRF attacks exploit same-origin requests which are allowed by CORS by default.

What is the recommended CSRF protection for SPAs?

Use SameSite=Strict cookies plus a custom header check (like X-Requested-With). SPA frameworks can include this header automatically.

Mini Project

Create an API with CSRF protection using both SameSite cookies and custom headers. Implement a frontend that includes the CSRF token in AJAX requests. Test with and without the token.

What's Next

Learn about CORS configuration to safely allow cross-origin requests to your API.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro