CSRF Protection
How SVA OAuth protects against Cross-Site Request Forgery (CSRF) attacks.
What is CSRF?
CSRF is an attack that forces a user to execute unwanted actions on a web application in which they're authenticated.
State Parameter
SVA OAuth uses the state parameter for CSRF protection:
- Generate State - Random token generated per OAuth flow
- Store in Session - State stored server-side in session
- Include in Request - State sent in authorization request
- Verify in Callback - State verified when callback is received
How It Works
Step 1: Generate State
import secrets
state = secrets.token_urlsafe(32)
request.session['sva_oauth_state'] = state
Step 2: Include in Authorization
GET /oauth/authorize?
client_id=...
&redirect_uri=...
&state={state}
...
Step 3: Verify in Callback
received_state = request.GET.get('state')
stored_state = request.session.get('sva_oauth_state')
if received_state != stored_state:
# CSRF attack detected
raise CSRFError()
# Clear state after verification
del request.session['sva_oauth_state']
Automatic Protection
The sva-oauth-client package automatically implements CSRF protection:
from sva_oauth_client.client import SVAOAuthClient
client = SVAOAuthClient(...)
# State is automatically generated and verified
auth_url, code_verifier = client.get_authorization_url()
# State is included in auth_url
# State is stored in session
Built-in Views
The package's built-in views handle CSRF protection automatically:
/oauth/login/- Generates state/oauth/callback/- Verifies state/oauth/exchange/- Validates state
Manual Implementation
Generate State
import secrets
def generate_state(request):
"""Generate CSRF state token"""
state = secrets.token_urlsafe(32)
request.session['oauth_state'] = state
return state
Verify State
def verify_state(request, received_state):
"""Verify CSRF state token"""
stored_state = request.session.get('oauth_state')
if not stored_state:
return False
if received_state != stored_state:
return False
# Clear state after verification
del request.session['oauth_state']
return True
Security Properties
Randomness
State tokens are cryptographically random:
import secrets
# Cryptographically secure random token
state = secrets.token_urlsafe(32)
Unpredictability
State tokens are unpredictable:
- 32 bytes of random data
- Base64 URL-safe encoding
- ~256 bits of entropy
Single Use
State tokens are single-use:
- Verified once in callback
- Cleared from session after verification
- Cannot be reused
Best Practices
- Always use state - Never skip state parameter
- Store securely - State in server-side session
- Verify always - Always verify state in callback
- Clear after use - Remove state after verification
- Generate randomly - Use cryptographically secure random
Common Mistakes
❌ Skipping State
# ❌ Bad - No state parameter
auth_url = f"{base_url}/oauth/authorize?client_id=..."
❌ Client-Side State
# ❌ Bad - State stored client-side
state = generate_state()
localStorage.setItem('oauth_state', state)
❌ Not Verifying
# ❌ Bad - Not verifying state
code = request.GET.get('code')
# Missing state verification
✅ Correct Implementation
# ✅ Good - State generated and verified
state = secrets.token_urlsafe(32)
request.session['oauth_state'] = state
# In callback
received_state = request.GET.get('state')
stored_state = request.session.get('oauth_state')
if received_state != stored_state:
raise CSRFError()
Testing
Test CSRF Protection
# tests.py
from django.test import TestCase, Client
class CSRFTestCase(TestCase):
def test_state_verification(self):
"""Test state parameter verification"""
client = Client()
# Generate state
response = client.get('/oauth/login/')
state = client.session.get('sva_oauth_state')
self.assertIsNotNone(state)
# Verify with correct state
response = client.get(f'/oauth/callback/?code=test&state={state}')
# Should succeed
# Verify with incorrect state
response = client.get('/oauth/callback/?code=test&state=wrong')
# Should fail
Next Steps
- Learn about PKCE
- Understand Data Token Verification
- Read Security Best Practices