乐闻世界logo
搜索文章和话题

How to protect against CSRF attacks in REST APIs and what are the special considerations?

2月19日 19:55

CSRF protection in REST APIs differs from traditional web applications because REST APIs typically use JSON format for data exchange and may be called by various clients (web, mobile apps, third-party services).

CSRF Specificity in REST APIs

1. Client Diversity

  • Web Applications: Browser environment, automatically sends cookies
  • Mobile Apps: Native environment, requires manual authentication management
  • Third-party Services: API calls, may use different authentication methods

2. Request Format

  • Traditional Web: Form submission, Content-Type: application/x-www-form-urlencoded
  • REST API: JSON data, Content-Type: application/json

3. Authentication Methods

  • Cookie Authentication: Vulnerable to CSRF attacks
  • Token Authentication: JWT, OAuth, relatively secure
  • Mixed Authentication: Cookie + Token

REST API CSRF Protection Strategies

1. Use Token Authentication (Recommended)

javascript
// JWT Token Authentication function authenticateJWT(req, res, next) { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) { return res.status(401).send('No token provided'); } try { const decoded = jwt.verify(token, JWT_SECRET); req.user = decoded; next(); } catch (error) { return res.status(401).send('Invalid token'); } } // Protect routes app.post('/api/transfer', authenticateJWT, (req, res) => { // Handle transfer request });

Advantages:

  • Token stored on client (localStorage or memory)
  • Not automatically sent, naturally protects against CSRF
  • Suitable for mobile apps and third-party integration

2. Custom Request Header Verification

javascript
// Generate CSRF Token function generateCSRFToken() { return crypto.randomBytes(32).toString('hex'); } // Set Token app.get('/api/csrf-token', (req, res) => { const token = generateCSRFToken(); req.session.csrfToken = token; res.json({ csrfToken: token }); }); // Verify custom header function validateCSRFHeader(req, res, next) { const token = req.headers['x-csrf-token']; if (!token || token !== req.session.csrfToken) { return res.status(403).send('Invalid CSRF token'); } next(); } // Protect routes app.post('/api/transfer', validateCSRFHeader, (req, res) => { // Handle request });

Frontend Implementation:

javascript
// Get Token async function getCSRFToken() { const response = await fetch('/api/csrf-token'); const data = await response.json(); return data.csrfToken; } // Send request async function makeRequest() { const token = await getCSRFToken(); await fetch('/api/transfer', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': token }, body: JSON.stringify({ to: 'user123', amount: 100 }) }); }
javascript
app.use(session({ secret: 'secret', cookie: { httpOnly: true, secure: true, sameSite: 'strict' // or 'lax' } }));

4. Origin Header Verification

javascript
function validateOrigin(req, res, next) { const origin = req.headers.origin; const allowedOrigins = ['https://example.com', 'https://app.example.com']; if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') { return next(); } if (!origin || !allowedOrigins.includes(origin)) { return res.status(403).send('Invalid origin'); } next(); }

Protection Schemes for Different Scenarios

Scenario 1: Pure Web Application (Browser)

javascript
// Use Cookie + CSRF Token app.use(session({ secret: 'secret', cookie: { httpOnly: true, secure: true, sameSite: 'lax' } })); app.use(csrf({ cookie: true })); app.post('/api/transfer', (req, res) => { // req.csrfToken() can be used on frontend });

Scenario 2: Mobile App + Web App

javascript
// Mixed authentication strategy function authenticate(req, res, next) { // Prioritize JWT Token const authHeader = req.headers.authorization; if (authHeader) { return authenticateJWT(req, res, next); } // Fallback to Cookie authentication if (req.session.userId) { req.user = { id: req.session.userId }; return next(); } return res.status(401).send('Authentication required'); } // CSRF protection only effective for Cookie authentication function csrfProtection(req, res, next) { if (req.headers.authorization) { // JWT authentication, skip CSRF verification return next(); } // Cookie authentication, requires CSRF verification if (req.body._csrf !== req.session.csrfToken) { return res.status(403).send('Invalid CSRF token'); } next(); }

Scenario 3: Third-party API Integration

javascript
// API Key Authentication function authenticateAPIKey(req, res, next) { const apiKey = req.headers['x-api-key']; if (!apiKey) { return res.status(401).send('API key required'); } // Verify API Key const user = await validateAPIKey(apiKey); if (!user) { return res.status(401).send('Invalid API key'); } req.user = user; next(); } // API Key authentication doesn't need CSRF protection app.post('/api/transfer', authenticateAPIKey, (req, res) => { // Handle request });

CORS Configuration and CSRF

javascript
const corsOptions = { origin: function (origin, callback) { const allowedOrigins = ['https://example.com', 'https://app.example.com']; // Allow requests without origin (e.g., mobile apps) if (!origin) return callback(null, true); if (allowedOrigins.indexOf(origin) !== -1) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } }, credentials: true, // Allow sending cookies methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization', 'X-CSRF-Token'] }; app.use(cors(corsOptions));

Best Practices Summary

  1. Prioritize Token Authentication: JWT, OAuth naturally protect against CSRF
  2. Cookie Authentication Must Be Protected: SameSite + CSRF Token
  3. Custom Request Headers: Safer than form fields
  4. Origin Header Verification: Supplementary protection measure
  5. Correct CORS Configuration: Restrict allowed origins
  6. Layered Protection: Combine multiple measures

CSRF protection for REST APIs needs to choose appropriate strategies based on specific usage scenarios and client types; there is no universal solution.

标签:CSRF