The double submit cookie scheme
What is the Double Submit Cookie Scheme
The Double Submit Cookie Scheme is a technical measure to defend against CSRF (Cross-Site Request Forgery) attacks. Its core idea is to have the client carry two identical tokens when sending a request: one sent automatically via a cookie, and the other sent explicitly via a form field or HTTP header. The server verifies the legitimacy of the request by comparing whether these two values match.
This scheme is effective because although attackers can exploit the user's cookies (due to the same-origin policy), they cannot read the cookie content or set custom HTTP headers in frontend code. When the server requires the two tokens to match, forged requests will be rejected due to the absence of one of the tokens.
Detailed Working Principle
- Token Generation: The server generates a random encrypted token when the user session starts.
- Token Distribution:
- Writes the token to a cookie via the
Set-Cookie
header. - Embeds the same token in the page (e.g., in a meta tag or JavaScript variable).
- Writes the token to a cookie via the
- Request Verification:
- The browser automatically carries the token from the cookie.
- The frontend code explicitly carries another token (via form field/HTTP header).
- Server Comparison: Verifies whether the two tokens match and are not expired.
// Server-side token generation example (Node.js)
const crypto = require('crypto');
function generateCSRFToken() {
return crypto.randomBytes(32).toString('hex');
}
// Express middleware example
app.use((req, res, next) => {
if (!req.cookies._csrf) {
const token = generateCSRFToken();
res.cookie('_csrf', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production'
});
res.locals.csrfToken = token; // For template use
}
next();
});
Implementation Methods
Form Submission Scenario
For traditional form submissions, the CSRF token is typically inserted as a hidden field:
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="text" name="amount">
<button type="submit">Transfer</button>
</form>
AJAX Request Scenario
Modern frontend applications often need to adapt to AJAX requests, which can be implemented as follows:
// Read CSRF token from cookie
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
// Add CSRF header to all AJAX requests
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': getCookie('_csrf')
},
body: JSON.stringify({ amount: 1000 })
});
Impact of Same-Origin Policy
The double submit scheme relies on the browser's same-origin policy:
- Cookies are automatically sent with requests (affected by the SameSite attribute).
- Attackers cannot read the target site's cookie content.
- Custom HTTP headers cannot be carried in cross-origin requests.
Security Enhancements
SameSite Cookie Attribute
Combining the SameSite attribute can further enhance security:
res.cookie('_csrf', token, {
httpOnly: true,
secure: true,
sameSite: 'strict' // or 'lax'
});
Token Binding
Binding tokens to user sessions can prevent token substitution attacks:
// During verification, not only compare values but also verify token ownership
function verifyCSRFToken(req) {
const cookieToken = req.cookies._csrf;
const bodyToken = req.body._csrf || req.headers['x-csrftoken'];
if (!cookieToken || !bodyToken) return false;
if (cookieToken !== bodyToken) return false;
// Optional: Verify the token belongs to the current session
return validateTokenInSession(req.session.userId, cookieToken);
}
Common Issues and Solutions
Multi-Tab Scenario
When users open multiple tabs, token inconsistency may occur. Solutions include:
- Using session-level cookies instead of persistent cookies.
- Generating unique tokens for each form and maintaining a token pool.
// Token pool implementation example
const tokenPool = new Map();
app.post('/submit', (req, res) => {
const { _csrf } = req.body;
if (!tokenPool.get(req.sessionID)?.includes(_csrf)) {
return res.status(403).send('Invalid CSRF token');
}
// Remove used token after verification
tokenPool.set(req.sessionID,
tokenPool.get(req.sessionID).filter(t => t !== _csrf));
});
API Compatibility Issues
For scenarios requiring support for both web and native apps:
- Retain the double submit mechanism for web.
- Use a signature-based authentication scheme for native apps.
// Conditional CSRF check middleware
app.use((req, res, next) => {
if (req.path.startsWith('/api/') &&
req.headers['x-requested-with'] === 'mobile-app') {
return next(); // Skip CSRF check
}
// Normal CSRF verification flow
});
Performance Optimization Practices
Token Caching Strategy
For high-traffic scenarios, the following optimizations can be applied:
- Use HMAC signatures to reduce server-side storage.
- Set reasonable token expiration times.
// HMAC signature example
const hmac = crypto.createHmac('sha256', SECRET_KEY);
hmac.update(userSessionId);
const token = hmac.digest('hex');
// During verification, simply recalculate and compare
function verifyToken(sessionId, token) {
const hmac = crypto.createHmac('sha256', SECRET_KEY);
hmac.update(sessionId);
return hmac.digest('hex') === token;
}
CDN Scenario Adaptation
When static resources are hosted on a CDN, special handling is required:
<!-- Use meta tag to pass the token -->
<meta name="csrf-token" content="{{csrfToken}}">
<script>
// Unified frontend token reading logic
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
axios.defaults.headers.common['X-CSRF-TOKEN'] = csrfToken;
</script>
Comparison with Other Security Mechanisms
Comparison with Traditional CSRF Token
Feature | Traditional Scheme | Double Submit Scheme |
---|---|---|
Token Storage | Session/DB | Cookie |
Frontend Involvement | High | Medium |
Distributed Support | Requires shared storage | Stateless |
Defense Strength | Strong | Strong |
Comparison with Encrypted Token Scheme
Encrypted tokens (e.g., JWT) can also be used for CSRF protection but have different characteristics:
- Encrypted tokens can contain more information.
- But they cannot achieve instant revocation.
- Key rotation issues need to be handled.
// JWT implementation example
const jwt = require('jsonwebtoken');
function generateToken(userId) {
return jwt.sign({ userId }, SECRET_KEY, { expiresIn: '1h' });
}
function verifyToken(token) {
try {
return jwt.verify(token, SECRET_KEY);
} catch {
return null;
}
}
Deployment Considerations
Load Balancing Environment
In a multi-server environment, ensure:
- All servers use the same key to generate tokens.
- Clock synchronization is important for time-sensitive tokens.
// Use a shared configuration key
const SECRET_KEY = process.env.CSRF_SECRET || 'default-secret';
// Or fetch from a central configuration service
async function getCSRFSecret() {
return fetchConfigService('csrf_secret');
}
Logging and Monitoring
It is recommended to log CSRF verification failures for security analysis:
app.post('/submit', (req, res) => {
if (!verifyCSRFToken(req)) {
securityLogger.warn('CSRF validation failed', {
ip: req.ip,
path: req.path,
userAgent: req.get('User-Agent')
});
return res.status(403).send('Forbidden');
}
// Normal processing logic
});
Browser Compatibility Handling
Fallback for Older Browsers
For browsers that do not support the SameSite attribute:
- Add additional verification parameters.
- Use
postMessage
for token synchronization.
// Detect SameSite support
const isSameSiteSupported = () => {
try {
document.cookie = 'test=1; SameSite=Lax';
return document.cookie.includes('test=1');
} catch {
return false;
}
};
if (!isSameSiteSupported()) {
// Enable traditional verification mode
window.addEventListener('message', syncCSRFToken);
}
Mobile-Specific Handling
Some mobile browsers handle cookies differently. Recommendations:
- Add UA detection logic.
- Use longer token expiration times for mobile.
function isMobileBrowser(ua) {
return /Mobile|Android|iP(hone|od|ad)/.test(ua);
}
app.use((req, res, next) => {
const isMobile = isMobileBrowser(req.get('User-Agent'));
res.cookie('_csrf', token, {
httpOnly: true,
secure: true,
maxAge: isMobile ? 86400000 : 1800000 // 1 day for mobile, 30 minutes for web
});
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
下一篇:前端框架中的 CSRF 防护