CSRF is a type of web security vulnerability where an attacker tricks a user’s browser into making an unauthorized request to a different site where the user is already authenticated. The goal of CSRF protection is to prevent attackers from forging actions on behalf of a user without their consent.
A simple scenario
- You’re logged into
yourbank.com
. - You visit a malicious site.
- That site makes a request to
yourbank.com/transfer?to=attacker&amount=1000
. - Your browser sends your bank cookies automatically, so the request is accepted as coming from you.
Preventing CSRF in OAuth 2.0
In OAuth 2.0, CSRF is prevented by:
- Using a state parameter, a random string sent during the login request.
- When the user is redirected back to your app, you check that the returned state matches what you originally sent.
- If it doesn’t, the request is rejected.
A concrete example
Let’s see a concrete code example. For simplicity, I’ll assume we’re want to authenticate with Google OAuth 2.0 flow.
User starts login from a frontend app
// Generate a string that is cryptographically random, not something predictable like a timestamp.
const state = crypto.randomUUID();
// Store state in localStorage.
// In server-rendered apps, store the state in the session server-side instead.
localStorage.setItem('oauth_state', state);
const clientId = 'YOUR_GOOGLE_CLIENT_ID';
const redirectUri = 'https://yourapp.com/oauth2callback';
const scope = 'openid email profile';
window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?` +
`client_id=${clientId}` +
`&redirect_uri=${redirectUri}` +
`&response_type=code` +
`&scope=${scope}` +
`&state=${state}`;
User is redirected back to your app
After login and consent, Google redirects the user back with:
https://yourapp.com/oauth2callback?code=abcd1234&state=b70c9c34-ff52-40df-8cf6-013f2c37cf7e
State parameter validation
const urlParams = new URLSearchParams(window.location.search);
const returnedState = urlParams.get('state');
const expectedState = localStorage.getItem('oauth_state');
if (returnedState !== expectedState) {
alert('Invalid state. Login aborted.');
} else {
const code = urlParams.get('code');
// Send `code` to your backend
}