csrf-protection

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

CSRF Protection

CSRF防护

Overview

概述

Implement comprehensive Cross-Site Request Forgery protection using synchronizer tokens, double-submit cookies, SameSite cookie attributes, and custom headers.
使用同步令牌、双重提交Cookie、SameSite Cookie属性和自定义标头实现全面的跨站请求伪造(CSRF)防护。

When to Use

适用场景

  • Form submissions
  • State-changing operations
  • Authentication systems
  • Payment processing
  • Account management
  • Any POST/PUT/DELETE requests
  • 表单提交
  • 状态变更操作
  • 认证系统
  • 支付处理
  • 账户管理
  • 所有POST/PUT/DELETE请求

Implementation Examples

实现示例

1. Node.js/Express CSRF Protection

1. Node.js/Express CSRF防护

javascript
// csrf-protection.js
const crypto = require('crypto');
const csrf = require('csurf');

class CSRFProtection {
  constructor() {
    this.tokens = new Map();
    this.tokenExpiry = 3600000; // 1 hour
  }

  /**
   * Generate CSRF token
   */
  generateToken() {
    return crypto.randomBytes(32).toString('hex');
  }

  /**
   * Create token for session
   */
  createToken(sessionId) {
    const token = this.generateToken();
    const expiry = Date.now() + this.tokenExpiry;

    this.tokens.set(sessionId, {
      token,
      expiry
    });

    return token;
  }

  /**
   * Validate CSRF token
   */
  validateToken(sessionId, token) {
    const stored = this.tokens.get(sessionId);

    if (!stored) {
      return false;
    }

    if (Date.now() > stored.expiry) {
      this.tokens.delete(sessionId);
      return false;
    }

    return crypto.timingSafeEqual(
      Buffer.from(stored.token),
      Buffer.from(token)
    );
  }

  /**
   * Express middleware
   */
  middleware() {
    return (req, res, next) => {
      // Skip GET, HEAD, OPTIONS
      if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
        return next();
      }

      const token = req.headers['x-csrf-token'] || req.body._csrf;
      const sessionId = req.session?.id;

      if (!token) {
        return res.status(403).json({
          error: 'csrf_token_missing',
          message: 'CSRF token is required'
        });
      }

      if (!this.validateToken(sessionId, token)) {
        return res.status(403).json({
          error: 'csrf_token_invalid',
          message: 'Invalid or expired CSRF token'
        });
      }

      next();
    };
  }
}

// Express setup with csurf package
const express = require('express');
const session = require('express-session');
const cookieParser = require('cookie-parser');

const app = express();

// Session configuration
app.use(cookieParser());
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 3600000
  }
}));

// CSRF protection middleware
const csrfProtection = csrf({
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'strict'
  }
});

app.use(csrfProtection);

// Provide token to templates
app.use((req, res, next) => {
  res.locals.csrfToken = req.csrfToken();
  next();
});

// API endpoint to get CSRF token
app.get('/api/csrf-token', (req, res) => {
  res.json({
    csrfToken: req.csrfToken()
  });
});

// Protected route
app.post('/api/transfer', csrfProtection, (req, res) => {
  const { amount, toAccount } = req.body;

  // Process transfer
  res.json({
    message: 'Transfer successful',
    amount,
    toAccount
  });
});

// Error handler for CSRF errors
app.use((err, req, res, next) => {
  if (err.code === 'EBADCSRFTOKEN') {
    return res.status(403).json({
      error: 'csrf_error',
      message: 'Invalid CSRF token'
    });
  }

  next(err);
});

module.exports = { CSRFProtection, csrfProtection };
javascript
// csrf-protection.js
const crypto = require('crypto');
const csrf = require('csurf');

class CSRFProtection {
  constructor() {
    this.tokens = new Map();
    this.tokenExpiry = 3600000; // 1 hour
  }

  /**
   * Generate CSRF token
   */
  generateToken() {
    return crypto.randomBytes(32).toString('hex');
  }

  /**
   * Create token for session
   */
  createToken(sessionId) {
    const token = this.generateToken();
    const expiry = Date.now() + this.tokenExpiry;

    this.tokens.set(sessionId, {
      token,
      expiry
    });

    return token;
  }

  /**
   * Validate CSRF token
   */
  validateToken(sessionId, token) {
    const stored = this.tokens.get(sessionId);

    if (!stored) {
      return false;
    }

    if (Date.now() > stored.expiry) {
      this.tokens.delete(sessionId);
      return false;
    }

    return crypto.timingSafeEqual(
      Buffer.from(stored.token),
      Buffer.from(token)
    );
  }

  /**
   * Express middleware
   */
  middleware() {
    return (req, res, next) => {
      // Skip GET, HEAD, OPTIONS
      if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
        return next();
      }

      const token = req.headers['x-csrf-token'] || req.body._csrf;
      const sessionId = req.session?.id;

      if (!token) {
        return res.status(403).json({
          error: 'csrf_token_missing',
          message: 'CSRF token is required'
        });
      }

      if (!this.validateToken(sessionId, token)) {
        return res.status(403).json({
          error: 'csrf_token_invalid',
          message: 'Invalid or expired CSRF token'
        });
      }

      next();
    };
  }
}

// Express setup with csurf package
const express = require('express');
const session = require('express-session');
const cookieParser = require('cookie-parser');

const app = express();

// Session configuration
app.use(cookieParser());
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 3600000
  }
}));

// CSRF protection middleware
const csrfProtection = csrf({
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'strict'
  }
});

app.use(csrfProtection);

// Provide token to templates
app.use((req, res, next) => {
  res.locals.csrfToken = req.csrfToken();
  next();
});

// API endpoint to get CSRF token
app.get('/api/csrf-token', (req, res) => {
  res.json({
    csrfToken: req.csrfToken()
  });
});

// Protected route
app.post('/api/transfer', csrfProtection, (req, res) => {
  const { amount, toAccount } = req.body;

  // Process transfer
  res.json({
    message: 'Transfer successful',
    amount,
    toAccount
  });
});

// Error handler for CSRF errors
app.use((err, req, res, next) => {
  if (err.code === 'EBADCSRFTOKEN') {
    return res.status(403).json({
      error: 'csrf_error',
      message: 'Invalid CSRF token'
    });
  }

  next(err);
});

module.exports = { CSRFProtection, csrfProtection };

2. Double Submit Cookie Pattern

2. 双重提交Cookie模式

javascript
// double-submit-csrf.js
const crypto = require('crypto');

class DoubleSubmitCSRF {
  /**
   * Generate CSRF token and set cookie
   */
  static generateAndSetToken(res) {
    const token = crypto.randomBytes(32).toString('hex');

    // Set CSRF cookie
    res.cookie('XSRF-TOKEN', token, {
      httpOnly: false, // Allow JS to read for double submit
      secure: true,
      sameSite: 'strict',
      maxAge: 3600000
    });

    return token;
  }

  /**
   * Middleware to validate double submit
   */
  static middleware() {
    return (req, res, next) => {
      // Skip GET, HEAD, OPTIONS
      if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
        return next();
      }

      const cookieToken = req.cookies['XSRF-TOKEN'];
      const headerToken = req.headers['x-xsrf-token'];

      if (!cookieToken || !headerToken) {
        return res.status(403).json({
          error: 'csrf_token_missing'
        });
      }

      // Compare tokens (timing-safe)
      if (!crypto.timingSafeEqual(
        Buffer.from(cookieToken),
        Buffer.from(headerToken)
      )) {
        return res.status(403).json({
          error: 'csrf_token_mismatch'
        });
      }

      next();
    };
  }
}

// Express setup
const app = express();
const cookieParser = require('cookie-parser');

app.use(cookieParser());
app.use(express.json());

// Generate token on login
app.post('/api/login', async (req, res) => {
  // Authenticate user
  const token = DoubleSubmitCSRF.generateAndSetToken(res);

  res.json({
    message: 'Login successful',
    csrfToken: token
  });
});

// Protected routes
app.use('/api/*', DoubleSubmitCSRF.middleware());

app.post('/api/update-profile', (req, res) => {
  // Update profile
  res.json({ message: 'Profile updated' });
});
javascript
// double-submit-csrf.js
const crypto = require('crypto');

class DoubleSubmitCSRF {
  /**
   * Generate CSRF token and set cookie
   */
  static generateAndSetToken(res) {
    const token = crypto.randomBytes(32).toString('hex');

    // Set CSRF cookie
    res.cookie('XSRF-TOKEN', token, {
      httpOnly: false, // Allow JS to read for double submit
      secure: true,
      sameSite: 'strict',
      maxAge: 3600000
    });

    return token;
  }

  /**
   * Middleware to validate double submit
   */
  static middleware() {
    return (req, res, next) => {
      // Skip GET, HEAD, OPTIONS
      if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
        return next();
      }

      const cookieToken = req.cookies['XSRF-TOKEN'];
      const headerToken = req.headers['x-xsrf-token'];

      if (!cookieToken || !headerToken) {
        return res.status(403).json({
          error: 'csrf_token_missing'
        });
      }

      // Compare tokens (timing-safe)
      if (!crypto.timingSafeEqual(
        Buffer.from(cookieToken),
        Buffer.from(headerToken)
      )) {
        return res.status(403).json({
          error: 'csrf_token_mismatch'
        });
      }

      next();
    };
  }
}

// Express setup
const app = express();
const cookieParser = require('cookie-parser');

app.use(cookieParser());
app.use(express.json());

// Generate token on login
app.post('/api/login', async (req, res) => {
  // Authenticate user
  const token = DoubleSubmitCSRF.generateAndSetToken(res);

  res.json({
    message: 'Login successful',
    csrfToken: token
  });
});

// Protected routes
app.use('/api/*', DoubleSubmitCSRF.middleware());

app.post('/api/update-profile', (req, res) => {
  // Update profile
  res.json({ message: 'Profile updated' });
});

3. Python Flask CSRF Protection

3. Python Flask CSRF防护

python
undefined
python
undefined

csrf_protection.py

csrf_protection.py

from flask import Flask, session, request, jsonify from flask_wtf.csrf import CSRFProtect, generate_csrf, validate_csrf from functools import wraps import secrets
app = Flask(name) app.config['SECRET_KEY'] = 'your-secret-key' app.config['WTF_CSRF_TIME_LIMIT'] = 3600 # 1 hour app.config['WTF_CSRF_SSL_STRICT'] = True
csrf = CSRFProtect(app)
from flask import Flask, session, request, jsonify from flask_wtf.csrf import CSRFProtect, generate_csrf, validate_csrf from functools import wraps import secrets
app = Flask(name) app.config['SECRET_KEY'] = 'your-secret-key' app.config['WTF_CSRF_TIME_LIMIT'] = 3600 # 1 hour app.config['WTF_CSRF_SSL_STRICT'] = True
csrf = CSRFProtect(app)

Cookie configuration

Cookie configuration

app.config.update( SESSION_COOKIE_SECURE=True, SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE='Strict' )
@app.before_request def csrf_protect(): """Validate CSRF token for state-changing methods""" if request.method in ['POST', 'PUT', 'DELETE', 'PATCH']: token = request.headers.get('X-CSRF-Token') or request.form.get('csrf_token')
    if not token:
        return jsonify({'error': 'CSRF token missing'}), 403

    try:
        validate_csrf(token)
    except:
        return jsonify({'error': 'Invalid CSRF token'}), 403
@app.route('/api/csrf-token', methods=['GET']) def get_csrf_token(): """Provide CSRF token to clients""" token = generate_csrf() return jsonify({'csrfToken': token})
@app.route('/api/transfer', methods=['POST']) def transfer_funds(): """Protected endpoint""" data = request.get_json()
return jsonify({
    'message': 'Transfer successful',
    'amount': data.get('amount')
})
app.config.update( SESSION_COOKIE_SECURE=True, SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE='Strict' )
@app.before_request def csrf_protect(): """Validate CSRF token for state-changing methods""" if request.method in ['POST', 'PUT', 'DELETE', 'PATCH']: token = request.headers.get('X-CSRF-Token') or request.form.get('csrf_token')
    if not token:
        return jsonify({'error': 'CSRF token missing'}), 403

    try:
        validate_csrf(token)
    except:
        return jsonify({'error': 'Invalid CSRF token'}), 403
@app.route('/api/csrf-token', methods=['GET']) def get_csrf_token(): """Provide CSRF token to clients""" token = generate_csrf() return jsonify({'csrfToken': token})
@app.route('/api/transfer', methods=['POST']) def transfer_funds(): """Protected endpoint""" data = request.get_json()
return jsonify({
    'message': 'Transfer successful',
    'amount': data.get('amount')
})

Custom CSRF decorator

Custom CSRF decorator

def require_csrf(f): @wraps(f) def decorated_function(*args, **kwargs): if request.method in ['POST', 'PUT', 'DELETE']: token = request.headers.get('X-CSRF-Token')
        if not token:
            return jsonify({'error': 'CSRF token required'}), 403

        try:
            validate_csrf(token)
        except:
            return jsonify({'error': 'Invalid CSRF token'}), 403

    return f(*args, **kwargs)

return decorated_function
@app.route('/api/sensitive-action', methods=['POST']) @require_csrf def sensitive_action(): return jsonify({'message': 'Action completed'})
if name == 'main': app.run(ssl_context='adhoc')
undefined
def require_csrf(f): @wraps(f) def decorated_function(*args, **kwargs): if request.method in ['POST', 'PUT', 'DELETE']: token = request.headers.get('X-CSRF-Token')
        if not token:
            return jsonify({'error': 'CSRF token required'}), 403

        try:
            validate_csrf(token)
        except:
            return jsonify({'error': 'Invalid CSRF token'}), 403

    return f(*args, **kwargs)

return decorated_function
@app.route('/api/sensitive-action', methods=['POST']) @require_csrf def sensitive_action(): return jsonify({'message': 'Action completed'})
if name == 'main': app.run(ssl_context='adhoc')
undefined

4. Frontend CSRF Implementation

4. 前端CSRF实现

javascript
// csrf-client.js
class CSRFClient {
  constructor() {
    this.token = null;
    this.tokenExpiry = null;
  }

  /**
   * Fetch CSRF token from server
   */
  async fetchToken() {
    const response = await fetch('/api/csrf-token', {
      credentials: 'include'
    });

    const data = await response.json();
    this.token = data.csrfToken;
    this.tokenExpiry = Date.now() + 3600000; // 1 hour

    return this.token;
  }

  /**
   * Get valid token (fetch if needed)
   */
  async getToken() {
    if (!this.token || Date.now() > this.tokenExpiry) {
      await this.fetchToken();
    }

    return this.token;
  }

  /**
   * Make protected request
   */
  async request(url, options = {}) {
    const token = await this.getToken();

    const headers = {
      'Content-Type': 'application/json',
      'X-CSRF-Token': token,
      ...options.headers
    };

    return fetch(url, {
      ...options,
      headers,
      credentials: 'include'
    });
  }

  /**
   * POST request with CSRF token
   */
  async post(url, data) {
    return this.request(url, {
      method: 'POST',
      body: JSON.stringify(data)
    });
  }

  /**
   * PUT request with CSRF token
   */
  async put(url, data) {
    return this.request(url, {
      method: 'PUT',
      body: JSON.stringify(data)
    });
  }

  /**
   * DELETE request with CSRF token
   */
  async delete(url) {
    return this.request(url, {
      method: 'DELETE'
    });
  }
}

// Usage
const client = new CSRFClient();

async function transferFunds() {
  try {
    const response = await client.post('/api/transfer', {
      amount: 1000,
      toAccount: '123456'
    });

    const result = await response.json();
    console.log('Transfer successful:', result);
  } catch (error) {
    console.error('Transfer failed:', error);
  }
}

// React hook for CSRF
function useCSRF() {
  const [token, setToken] = React.useState(null);

  React.useEffect(() => {
    async function fetchToken() {
      const response = await fetch('/api/csrf-token');
      const data = await response.json();
      setToken(data.csrfToken);
    }

    fetchToken();
  }, []);

  return token;
}

// Usage in React form
function TransferForm() {
  const csrfToken = useCSRF();

  const handleSubmit = async (e) => {
    e.preventDefault();

    await fetch('/api/transfer', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrfToken
      },
      body: JSON.stringify({
        amount: 1000,
        toAccount: '123456'
      })
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="hidden" name="_csrf" value={csrfToken} />
      {/* form fields */}
      <button type="submit">Transfer</button>
    </form>
  );
}
javascript
// csrf-client.js
class CSRFClient {
  constructor() {
    this.token = null;
    this.tokenExpiry = null;
  }

  /**
   * Fetch CSRF token from server
   */
  async fetchToken() {
    const response = await fetch('/api/csrf-token', {
      credentials: 'include'
    });

    const data = await response.json();
    this.token = data.csrfToken;
    this.tokenExpiry = Date.now() + 3600000; // 1 hour

    return this.token;
  }

  /**
   * Get valid token (fetch if needed)
   */
  async getToken() {
    if (!this.token || Date.now() > this.tokenExpiry) {
      await this.fetchToken();
    }

    return this.token;
  }

  /**
   * Make protected request
   */
  async request(url, options = {}) {
    const token = await this.getToken();

    const headers = {
      'Content-Type': 'application/json',
      'X-CSRF-Token': token,
      ...options.headers
    };

    return fetch(url, {
      ...options,
      headers,
      credentials: 'include'
    });
  }

  /**
   * POST request with CSRF token
   */
  async post(url, data) {
    return this.request(url, {
      method: 'POST',
      body: JSON.stringify(data)
    });
  }

  /**
   * PUT request with CSRF token
   */
  async put(url, data) {
    return this.request(url, {
      method: 'PUT',
      body: JSON.stringify(data)
    });
  }

  /**
   * DELETE request with CSRF token
   */
  async delete(url) {
    return this.request(url, {
      method: 'DELETE'
    });
  }
}

// Usage
const client = new CSRFClient();

async function transferFunds() {
  try {
    const response = await client.post('/api/transfer', {
      amount: 1000,
      toAccount: '123456'
    });

    const result = await response.json();
    console.log('Transfer successful:', result);
  } catch (error) {
    console.error('Transfer failed:', error);
  }
}

// React hook for CSRF
function useCSRF() {
  const [token, setToken] = React.useState(null);

  React.useEffect(() => {
    async function fetchToken() {
      const response = await fetch('/api/csrf-token');
      const data = await response.json();
      setToken(data.csrfToken);
    }

    fetchToken();
  }, []);

  return token;
}

// Usage in React form
function TransferForm() {
  const csrfToken = useCSRF();

  const handleSubmit = async (e) => {
    e.preventDefault();

    await fetch('/api/transfer', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrfToken
      },
      body: JSON.stringify({
        amount: 1000,
        toAccount: '123456'
      })
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="hidden" name="_csrf" value={csrfToken} />
      {/* form fields */}
      <button type="submit">Transfer</button>
    </form>
  );
}

5. Origin and Referer Validation

5. 来源与Referer验证

javascript
// origin-validation.js
function validateOrigin(req, res, next) {
  const allowedOrigins = [
    'https://example.com',
    'https://app.example.com'
  ];

  const origin = req.headers.origin;
  const referer = req.headers.referer;

  // Check Origin header
  if (origin && !allowedOrigins.includes(origin)) {
    return res.status(403).json({
      error: 'invalid_origin'
    });
  }

  // Check Referer header as fallback
  if (!origin && referer) {
    const refererUrl = new URL(referer);
    if (!allowedOrigins.includes(refererUrl.origin)) {
      return res.status(403).json({
        error: 'invalid_referer'
      });
    }
  }

  next();
}

// Apply to state-changing routes
app.use('/api/*', validateOrigin);
javascript
// origin-validation.js
function validateOrigin(req, res, next) {
  const allowedOrigins = [
    'https://example.com',
    'https://app.example.com'
  ];

  const origin = req.headers.origin;
  const referer = req.headers.referer;

  // Check Origin header
  if (origin && !allowedOrigins.includes(origin)) {
    return res.status(403).json({
      error: 'invalid_origin'
    });
  }

  // Check Referer header as fallback
  if (!origin && referer) {
    const refererUrl = new URL(referer);
    if (!allowedOrigins.includes(refererUrl.origin)) {
      return res.status(403).json({
        error: 'invalid_referer'
      });
    }
  }

  next();
}

// Apply to state-changing routes
app.use('/api/*', validateOrigin);

Best Practices

最佳实践

✅ DO

✅ 建议

  • Use CSRF tokens for all state-changing operations
  • Set SameSite=Strict on cookies
  • Validate Origin/Referer headers
  • Use secure, random tokens
  • Implement token expiration
  • Use HTTPS only
  • Include tokens in AJAX requests
  • Test CSRF protection
  • 对所有状态变更操作使用CSRF令牌
  • 为Cookie设置SameSite=Strict属性
  • 验证Origin/Referer标头
  • 使用安全的随机令牌
  • 实现令牌过期机制
  • 仅使用HTTPS
  • 在AJAX请求中包含令牌
  • 测试CSRF防护

❌ DON'T

❌ 避免

  • Skip CSRF for authenticated requests
  • Use GET for state changes
  • Trust Origin header alone
  • Reuse tokens
  • Store tokens in localStorage
  • Allow credentials in CORS without validation
  • 对已认证请求跳过CSRF防护
  • 使用GET请求进行状态变更
  • 仅信任Origin标头
  • 重复使用令牌
  • 将令牌存储在localStorage中
  • 在未验证的情况下允许CORS中的凭证

CSRF Protection Methods

CSRF防护方法

  1. Synchronizer Token: Server-generated tokens
  2. Double Submit Cookie: Cookie and header match
  3. SameSite Cookies: Browser-level protection
  4. Custom Headers: X-Requested-With
  5. Origin Validation: Check request origin
  1. 同步令牌:服务器生成的令牌
  2. 双重提交Cookie:Cookie与标头匹配
  3. SameSite Cookie:浏览器级防护
  4. 自定义标头:X-Requested-With
  5. 来源验证:检查请求来源

Defense Layers

防御层级

  • CSRF tokens implemented
  • SameSite cookies configured
  • Origin/Referer validation
  • Custom request headers
  • Token expiration
  • Secure cookie flags
  • HTTPS enforced
  • 已实现CSRF令牌
  • 已配置SameSite Cookie
  • 已验证Origin/Referer
  • 已使用自定义请求标头
  • 已实现令牌过期
  • 已设置安全Cookie标记
  • 已强制使用HTTPS

Resources

参考资源