Skip to content

Implement CSRF protection

Prevent cross-site request forgery in your auth flow using the OAuth state parameter and secure cookies

You will add robust CSRF protection to your login flow by using the OAuth state parameter and secure cookies. This prevents cross-site request forgery and open-redirect attacks during authentication.

What goes wrong without CSRF protection

  • You are signed in to your app and visit an attacker’s site.
  • The site auto-submits your browser to your OAuth callback using the attacker’s authorization code.
  • Your app exchanges the code and sets a session — in your browser — for the attacker’s account.

Result: login CSRF (account mix-up). You appear signed in, but as the attacker.

Attacker page (auto-submits your callback)
<form action="https://your-app.com/auth/callback" method="GET" id="f">
<input type="hidden" name="code" value="C_attacker" />
<!-- No valid state included -->
</form>
<script>document.getElementById('f').submit()</script>

With proper state validation, your callback rejects this request (401) because the state is missing or mismatched. The steps below show how to implement that protection.

  1. Create a random, unguessable state value and persist it server-side (session) or in a signed, HTTP-only cookie. Use at least 256 bits of entropy.

    Express.js
    import crypto from 'crypto'
    // Generate and persist state
    const state = crypto.randomBytes(32).toString('hex')
    // Recommended: store in a server session. If you use a cookie, make it signed & HTTP-only.
    res.cookie('sk_auth_state', state, {
    httpOnly: true,
    secure: true,
    sameSite: 'lax', // Lax allows the cookie to be sent on the OAuth GET redirect
    path: '/',
    })
  2. Pass the state when you build the authorization URL.

    Express.js
    const redirectUri = 'https://your-app.com/auth/callback'
    const options = {
    scopes: ['openid', 'profile', 'email', 'offline_access'],
    state,
    }
    const authorizationUrl = scalekit.getAuthorizationUrl(redirectUri, options)
    res.redirect(authorizationUrl)
  3. Compare the state in the callback with what you stored. Reject on mismatch.

    Express.js
    app.get('/auth/callback', (req, res) => {
    const returned = req.query.state
    const expected = req.cookies.sk_auth_state // or session store
    if (!returned || returned !== expected) {
    return res.status(401).send('Invalid state')
    }
    // Clear one-time state after successful validation
    res.clearCookie('sk_auth_state', { path: '/' })
    // Proceed to exchange the code
    })
    • Use HttpOnly, Secure, and SameSite=Lax on session cookies used in the OAuth redirect.
    • Set a short TTL for sk_auth_state and delete it after use.
    • For HTML forms in your app, use framework-native anti-CSRF tokens (separate from OAuth state).