> ## Documentation Index
> Fetch the complete documentation index at: https://docs.baanx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Security Best Practices

> Essential security guidelines for OAuth 2.0 implementation

## Overview

Security is paramount when implementing OAuth 2.0. This guide covers essential security practices, common vulnerabilities, and how to protect your application and users from attacks.

<Warning>
  **Production Requirement:** Review and implement ALL security practices in this guide before deploying to production.
</Warning>

## User Consent and Authorization Principles

OAuth 2.0 is fundamentally about user empowerment and consent. Security extends beyond protecting tokens—it includes respecting the authorization boundaries users establish.

<CardGroup cols={2}>
  <Card title="User Agency First" icon="user-shield">
    Users grant your application permission to act on their behalf. This authorization is not blanket access—it's permission to perform specific, expected actions.
  </Card>

  <Card title="Principle of Least Privilege" icon="key">
    Only request and use the minimum permissions necessary. Just because you have a token doesn't mean you should access all available user data.
  </Card>

  <Card title="Transparent Intent" icon="eye">
    Users should always understand what actions your application will perform on their behalf. Unexpected behavior erodes trust and violates consent.
  </Card>

  <Card title="Revocation Rights" icon="ban">
    Users must be able to revoke access at any time. Implement clear logout flows and honor token revocation immediately.
  </Card>
</CardGroup>

<Warning>
  **Critical Security Principle:** Obtaining an access token grants you permission to act on behalf of the user—not permission to access or modify their data without their explicit understanding and consent for each type of action.
</Warning>

## PKCE Implementation

PKCE (Proof Key for Code Exchange, pronounced "pixie") is **mandatory** for all OAuth flows. It prevents authorization code interception attacks.

### What is PKCE?

PKCE adds a cryptographic challenge to the OAuth flow:

1. **Code Verifier:** Random 43-128 character string
2. **Code Challenge:** SHA256 hash of the code verifier
3. **Verification:** Server validates verifier matches challenge

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant Server

    Note over Client: Generate code_verifier (random)
    Note over Client: Create code_challenge = SHA256(code_verifier)

    Client->>Server: Step 1: Send code_challenge
    Note over Server: Store code_challenge

    Client->>Server: Step 4: Send code_verifier
    Note over Server: Verify: SHA256(code_verifier) == code_challenge

    alt Verification Success
        Server->>Client: Return tokens
    else Verification Failed
        Server->>Client: Error: invalid_grant
    end
```

### Implementation

<CodeGroup>
  ```typescript TypeScript theme={null}
  import crypto from 'crypto';

  function generateCodeVerifier(): string {
    // Generate 32 random bytes, base64url encode
    // Result: 43 characters (meets 43-128 requirement)
    return crypto
      .randomBytes(32)
      .toString('base64url');
  }

  function generateCodeChallenge(verifier: string): string {
    // Hash with SHA256, base64url encode
    return crypto
      .createHash('sha256')
      .update(verifier)
      .digest('base64url');
  }

  // Example usage
  const codeVerifier = generateCodeVerifier();
  const codeChallenge = generateCodeChallenge(codeVerifier);

  console.log('Code Verifier:', codeVerifier);
  console.log('Code Challenge:', codeChallenge);

  // Store verifier securely for Step 4
  sessionStorage.setItem('code_verifier', codeVerifier);
  ```

  ```python Python theme={null}
  import secrets
  import hashlib
  import base64

  def generate_code_verifier() -> str:
      # Generate 32 random bytes, base64url encode
      # Result: 43 characters (meets 43-128 requirement)
      code_verifier = base64.urlsafe_b64encode(
          secrets.token_bytes(32)
      ).decode('utf-8')

      # Remove padding
      return code_verifier.rstrip('=')

  def generate_code_challenge(verifier: str) -> str:
      # Hash with SHA256, base64url encode
      digest = hashlib.sha256(verifier.encode('utf-8')).digest()
      challenge = base64.urlsafe_b64encode(digest).decode('utf-8')

      # Remove padding
      return challenge.rstrip('=')

  # Example usage
  code_verifier = generate_code_verifier()
  code_challenge = generate_code_challenge(code_verifier)

  print(f'Code Verifier: {code_verifier}')
  print(f'Code Challenge: {code_challenge}')

  # Store verifier securely for Step 4
  session['code_verifier'] = code_verifier
  ```

  ```bash Bash theme={null}
  # Using OpenSSL
  CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=' | tr '+/' '-_')
  CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | base64 | tr -d '=' | tr '+/' '-_')

  echo "Code Verifier: $CODE_VERIFIER"
  echo "Code Challenge: $CODE_CHALLENGE"

  # Save for later use
  export OAUTH_CODE_VERIFIER="$CODE_VERIFIER"
  ```
</CodeGroup>

### PKCE Requirements

<CardGroup cols={2}>
  <Card title="Code Verifier" icon="key">
    * Length: 43-128 characters
    * Character set: \[A-Z, a-z, 0-9, -, ., \_, \~]
    * Cryptographically random
    * Single use only
  </Card>

  <Card title="Code Challenge" icon="shield-check">
    * Algorithm: SHA256
    * Encoding: base64url (no padding)
    * Method: Must be S256
    * Derived from verifier
  </Card>
</CardGroup>

<Warning>
  **Common Mistake:** Using plain text as code\_challenge\_method. Always use `S256` (SHA256), never `plain`.
</Warning>

## State Parameter (CSRF Protection)

The state parameter prevents Cross-Site Request Forgery (CSRF) attacks.

### How It Works

1. **Generate:** Create random state before initiating OAuth
2. **Send:** Include in OAuth initiation request
3. **Verify:** Validate returned state matches original

<CodeGroup>
  ```typescript TypeScript theme={null}
  import crypto from 'crypto';

  function generateState(): string {
    // Generate cryptographically random state
    return crypto.randomBytes(16).toString('hex');
  }

  // Step 1: Generate and store state
  const state = generateState();
  sessionStorage.setItem('oauth_state', state);

  // Include in OAuth initiation
  const params = new URLSearchParams({
    state,
    // ... other params
  });

  // Step 3: Verify state on callback
  function handleCallback(returnedState: string) {
    const originalState = sessionStorage.getItem('oauth_state');

    if (returnedState !== originalState) {
      throw new Error('State mismatch - possible CSRF attack');
    }

    // Clean up
    sessionStorage.removeItem('oauth_state');

    // Continue with token exchange
  }
  ```

  ```python Python theme={null}
  import secrets

  def generate_state() -> str:
      # Generate cryptographically random state
      return secrets.token_urlsafe(16)

  # Step 1: Generate and store state
  state = generate_state()
  session['oauth_state'] = state

  # Include in OAuth initiation
  params = {
      'state': state,
      # ... other params
  }

  # Step 3: Verify state on callback
  def handle_callback(returned_state: str):
      original_state = session.get('oauth_state')

      if returned_state != original_state:
          raise ValueError('State mismatch - possible CSRF attack')

      # Clean up
      session.pop('oauth_state', None)

      # Continue with token exchange
  ```
</CodeGroup>

<Note>
  **State Requirements:**

  * Minimum 16 characters
  * Cryptographically random
  * Stored securely (session, not localStorage)
  * Validated exactly (case-sensitive)
  * Single use only
</Note>

## Secret Key Management

Your `x-secret-key` is highly sensitive and must be protected.

### Do's and Don'ts

<Tabs>
  <Tab title="✅ Do">
    **Store Secrets Securely**

    ```typescript theme={null}
    // Environment variables
    const secretKey = process.env.SECRET_KEY;

    // Server-side only
    app.post('/api/oauth/initiate', async (req, res) => {
      const response = await fetch(oauthURL, {
        headers: {
          'x-secret-key': process.env.SECRET_KEY
        }
      });
    });

    // Use secret management services
    // - AWS Secrets Manager
    // - HashiCorp Vault
    // - Azure Key Vault
    ```

    **Rotate Regularly**

    * Rotate secrets every 90 days
    * Rotate immediately if compromised
    * Use different keys per environment
  </Tab>

  <Tab title="❌ Don't">
    **Never Expose Secrets**

    ```typescript theme={null}
    // ❌ NEVER in client-side code
    const secretKey = 'sk_live_abc123...';

    // ❌ NEVER in frontend JavaScript
    fetch('/oauth/initiate', {
      headers: {
        'x-secret-key': 'sk_live_abc123...'
      }
    });

    // ❌ NEVER in version control
    // .env file:
    SECRET_KEY=sk_live_abc123...  // This will be committed!

    // ❌ NEVER in public repositories
    // ❌ NEVER in error messages
    // ❌ NEVER in logs
    // ❌ NEVER in URLs
    ```

    **Also Avoid**

    * Hardcoding in source code
    * Storing in localStorage/cookies
    * Passing in URL parameters
    * Including in mobile app bundles
    * Logging to console or files
  </Tab>
</Tabs>

### Secret Detection

Add pre-commit hooks to prevent accidental commits:

<CodeGroup>
  ```bash .git/hooks/pre-commit theme={null}
  #!/bin/bash

  # Check for potential secrets
  if git diff --cached | grep -E '(x-secret-key|SECRET_KEY|sk_live_|sk_test_)'; then
      echo "Error: Potential secret detected in commit"
      echo "Please remove secrets before committing"
      exit 1
  fi
  ```

  ```yaml .github/workflows/secret-scan.yml theme={null}
  name: Secret Scan
  on: [push, pull_request]

  jobs:
    scan:
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v2
        - name: Detect secrets
          uses: trufflesecurity/trufflehog@main
          with:
            path: ./
            base: ${{ github.event.repository.default_branch }}
            head: HEAD
  ```
</CodeGroup>

## Redirect URI Security

The **redirect URI** (or callback URL) is where users return after authentication, and it's critical for OAuth security. Incorrect configuration can lead to authorization code theft.

<Info>
  **What You'll Learn:** This section covers redirect URI requirements, HTTPS vs HTTP, custom schemes for mobile apps, CORS considerations, and validation techniques. For a high-level overview, see the [OAuth Quick Start](/guides/oauth/quickstart).
</Info>

### What is a Redirect URI?

A redirect URI serves as the callback endpoint where the authorization server returns the user along with the authorization code after successful authentication.

```mermaid theme={null}
sequenceDiagram
    participant App as Your App
    participant API as API Gateway
    participant User as End User

    App->>API: 1. Initiate OAuth (includes redirect_uri)
    API->>User: 2. Redirect to authentication
    User->>API: 3. Authenticate
    API->>App: 4. Redirect to redirect_uri with code
    Note over App: Code delivered via redirect_uri
```

### Allowed URI Schemes

<Tabs>
  <Tab title="HTTPS (Required for Production)">
    **Web applications in production environments MUST use HTTPS**

    ```
    ✅ https://app.example.com/oauth/callback
    ✅ https://dashboard.example.com/auth/callback
    ✅ https://api.example.com/v1/oauth/redirect
    ```

    <Warning>
      **Production Requirement:** HTTP redirect URIs are rejected in production. HTTPS ensures authorization codes are encrypted during transmission, preventing interception attacks.
    </Warning>

    **HTTPS Requirements:**

    * Valid SSL/TLS certificate
    * TLS 1.2 or higher
    * Proper certificate chain configuration
    * No mixed content warnings
  </Tab>

  <Tab title="HTTP (Development Only)">
    **Only allowed in sandbox/development environments**

    ```
    ⚠️  http://localhost:3000/oauth/callback
    ⚠️  http://127.0.0.1:8080/callback
    ⚠️  http://dev.example.test/oauth/callback
    ```

    <Note>
      **Development Use:** HTTP URIs are permitted for local development and sandbox testing only. They will be rejected in production environments.
    </Note>

    **When to use HTTP:**

    * Local development (`localhost`)
    * Testing OAuth flows
    * Sandbox environment testing
    * Before SSL certificate setup
  </Tab>

  <Tab title="Custom Schemes (Mobile)">
    **Mobile applications should use app-specific custom URL schemes**

    ```
    ✅ myapp://oauth/callback
    ✅ com.example.myapp://auth/callback
    ```

    <Warning>
      **Contact Required:** If you need to use a custom URL scheme, you **must** contact your **Technical Account Manager** to have your custom scheme whitelisted before implementation.
    </Warning>

    **Custom Scheme Best Practices:**

    * Use reverse domain notation (e.g., `com.example.app://`)
    * Make it unique to avoid conflicts with other apps
    * Keep it lowercase
    * Avoid special characters
    * Document in your app configuration

    **Platform Setup Examples:**

    <CodeGroup>
      ```swift iOS (Info.plist) theme={null}
      <key>CFBundleURLTypes</key>
      <array>
          <dict>
              <key>CFBundleURLSchemes</key>
              <array>
                  <string>myapp</string>
              </array>
          </dict>
      </array>
      ```

      ```xml Android (AndroidManifest.xml) theme={null}
      <activity android:name=".OAuthCallbackActivity">
          <intent-filter>
              <action android:name="android.intent.action.VIEW" />
              <category android:name="android.intent.category.DEFAULT" />
              <category android:name="android.intent.category.BROWSABLE" />
              <data
                  android:scheme="myapp"
                  android:host="oauth" />
          </intent-filter>
      </activity>
      ```
    </CodeGroup>
  </Tab>
</Tabs>

### Whitelist Configuration

<Card title="Strict Matching Required" icon="link">
  Redirect URIs must be **exactly** whitelisted in your environment configuration. No wildcards or partial matches allowed.

  ```typescript theme={null}
  // ✅ Correct - exact match
  redirect_uri: 'https://app.example.com/oauth/callback'

  // ❌ Wrong - subdomain mismatch
  redirect_uri: 'https://staging.example.com/oauth/callback'

  // ❌ Wrong - path mismatch
  redirect_uri: 'https://app.example.com/oauth/different-callback'

  // ❌ Wrong - protocol mismatch
  redirect_uri: 'http://app.example.com/oauth/callback'

  // ❌ Wrong - trailing slash mismatch
  redirect_uri: 'https://app.example.com/oauth/callback/'
  ```

  All components must match exactly: protocol, domain, path, and port (if specified).
</Card>

### CORS and Redirect URIs

When your frontend application makes OAuth API calls, **CORS (Cross-Origin Resource Sharing)** settings must align with your redirect URI configuration.

<Info>
  **Origin Relationship:** The redirect URI domain should typically match the origin making OAuth API requests to prevent cross-origin issues.
</Info>

<Tabs>
  <Tab title="Same-Origin (Recommended)">
    **Best Practice: Frontend and redirect URI on the same domain**

    ```
    Frontend Origin:  https://app.example.com
    Redirect URI:     https://app.example.com/oauth/callback
    ```

    No CORS configuration needed - requests and callbacks use the same origin.
  </Tab>

  <Tab title="Cross-Origin">
    **When frontend and redirect URI are different origins**

    ```
    Frontend Origin:  https://dashboard.example.com
    Redirect URI:     https://auth.example.com/callback
    ```

    <Warning>
      **CORS Setup Required:** Contact your **Technical Account Manager** to configure CORS headers for your frontend origin and redirect URI domain.
    </Warning>

    **Required CORS Configuration:**

    * Allowed Origins: Both frontend and redirect URI domains
    * Allowed Methods: GET, POST for OAuth endpoints
    * Allowed Headers: Authorization, Content-Type, x-client-key, x-secret-key
    * Credentials: Set to true for cookie-based sessions
  </Tab>
</Tabs>

### Security Requirements

<CardGroup cols={2}>
  <Card title="Never Use Wildcards" icon="asterisk">
    ```
    ❌ https://*.example.com/callback
    ❌ https://example.com/*
    ✅ https://app.example.com/callback
    ```

    Wildcards create security vulnerabilities allowing malicious redirects.
  </Card>

  <Card title="Use HTTPS in Production" icon="lock">
    ```
    ❌ http://app.example.com/callback
    ✅ https://app.example.com/callback
    ```

    HTTP exposes authorization codes to man-in-the-middle attacks.
  </Card>

  <Card title="Validate on Client" icon="shield-check">
    ```typescript theme={null}
    const ALLOWED_URIS = [
      'https://app.example.com/oauth/callback'
    ];

    function validateRedirectURI(uri: string): boolean {
      return ALLOWED_URIS.includes(uri);
    }
    ```

    Client-side validation prevents configuration errors before API calls.
  </Card>

  <Card title="Environment-Specific URIs" icon="server">
    ```typescript theme={null}
    const redirectUri = {
      development: 'http://localhost:3000/callback',
      staging: 'https://staging.example.com/callback',
      production: 'https://app.example.com/callback'
    }[process.env.NODE_ENV];
    ```

    Use different URIs for each environment to maintain security.
  </Card>
</CardGroup>

### Common Redirect URI Pitfalls

<AccordionGroup>
  <Accordion title="Trailing Slash Mismatch" icon="slash">
    **Problem:** Redirect URI with/without trailing slash doesn't match whitelist

    ```
    Whitelisted: https://app.example.com/callback
    Sent:        https://app.example.com/callback/
    Result:      ❌ Mismatch
    ```

    **Solution:** Ensure exact match including trailing slash presence/absence.
  </Accordion>

  <Accordion title="Protocol Mismatch" icon="link">
    **Problem:** HTTP used when HTTPS is whitelisted (or vice versa)

    ```
    Whitelisted: https://app.example.com/callback
    Sent:        http://app.example.com/callback
    Result:      ❌ Mismatch
    ```

    **Solution:** Match protocol exactly; always use HTTPS in production.
  </Accordion>

  <Accordion title="Port Number Issues" icon="hashtag">
    **Problem:** Explicit port in URI when whitelist doesn't include it

    ```
    Whitelisted: https://app.example.com/callback
    Sent:        https://app.example.com:443/callback
    Result:      ❌ Possible mismatch
    ```

    **Solution:** Omit default ports (80 for HTTP, 443 for HTTPS) unless explicitly required.
  </Accordion>

  <Accordion title="Custom Scheme Not Whitelisted" icon="mobile">
    **Problem:** Mobile app uses custom scheme without prior approval

    ```
    Sent:   myapp://oauth/callback
    Result: ❌ Not whitelisted
    ```

    **Solution:** Contact Technical Account Manager to whitelist custom schemes before implementation.
  </Accordion>

  <Accordion title="CORS Policy Blocked" icon="ban">
    **Problem:** Browser blocks OAuth API request due to CORS policy

    ```
    Error: Access to fetch at 'https://api.baanx.com/v1/authorize/initiate'
    from origin 'https://myapp.example.com' has been blocked by CORS policy
    ```

    **Solution:**

    1. Verify your frontend origin is whitelisted for CORS
    2. Check redirect URI matches whitelisted configuration
    3. Contact Technical Account Manager to update CORS settings
  </Accordion>
</AccordionGroup>

### Client-Side Validation Implementation

<CodeGroup>
  ```typescript TypeScript theme={null}
  const ALLOWED_REDIRECT_URIS = [
    'https://app.example.com/oauth/callback'
  ];

  function validateRedirectURI(uri: string): boolean {
    return ALLOWED_REDIRECT_URIS.includes(uri);
  }

  function initiateOAuth(redirectUri: string) {
    if (!validateRedirectURI(redirectUri)) {
      throw new Error('Redirect URI not whitelisted');
    }

    // Continue with OAuth
  }
  ```

  ```python Python theme={null}
  ALLOWED_REDIRECT_URIS = [
      'https://app.example.com/oauth/callback'
  ]

  def validate_redirect_uri(uri: str) -> bool:
      return uri in ALLOWED_REDIRECT_URIS

  def initiate_oauth(redirect_uri: str):
      if not validate_redirect_uri(redirect_uri):
          raise ValueError('Redirect URI not whitelisted')

      # Continue with OAuth
  ```
</CodeGroup>

### Configuration Checklist

<Steps>
  <Step title="Determine Redirect URI Scheme">
    Choose appropriate URI based on platform:

    * Web production: `https://yourdomain.com/oauth/callback`
    * Web development: `http://localhost:3000/oauth/callback`
    * Mobile app: `yourapp://oauth/callback` (requires technical account manager approval)
  </Step>

  <Step title="Request Whitelisting">
    Contact your Technical Account Manager with:

    * Redirect URI(s) for each environment
    * Custom URL schemes (if mobile - requires approval)
    * CORS origins (if different from redirect URI domain)
  </Step>

  <Step title="Implement Callback Handler">
    Create endpoint to receive authorization code:

    * Extract `code` and `state` query parameters
    * Validate state matches original value
    * Exchange code for tokens via `/token` endpoint
  </Step>

  <Step title="Test Complete Flow">
    Verify end-to-end:

    * Initiate OAuth with your redirect URI
    * Complete authentication
    * Callback receives authorization code
    * Code successfully exchanges for tokens
  </Step>
</Steps>

## Token Storage Security

How you store tokens is critical for both protecting user data and maintaining the trust users place in your application when they grant authorization. Secure storage prevents unauthorized access to user accounts through stolen tokens.

<Note>
  **Dual Responsibility:** Token security protects both your application's integrity and the user's trust. A compromised token allows attackers to act as the user within the scope of granted permissions.
</Note>

### Storage Methods by Platform

<Tabs>
  <Tab title="Web Applications">
    **Best: HTTP-Only Cookies**

    ```typescript theme={null}
    // Server-side: Set secure cookie
    res.cookie('access_token', token, {
      httpOnly: true,      // Not accessible via JavaScript
      secure: true,        // HTTPS only
      sameSite: 'strict',  // CSRF protection
      maxAge: 6 * 60 * 60 * 1000  // 6 hours
    });
    ```

    **Alternative: Memory Storage**

    ```typescript theme={null}
    // For SPAs, store in memory (not localStorage)
    class TokenStore {
      private token: string | null = null;

      set(token: string) {
        this.token = token;
      }

      get(): string | null {
        return this.token;
      }

      clear() {
        this.token = null;
      }
    }
    ```

    **⚠️ Avoid localStorage**

    ```typescript theme={null}
    // ❌ Vulnerable to XSS attacks
    localStorage.setItem('access_token', token);
    ```
  </Tab>

  <Tab title="Mobile Apps">
    **iOS: Keychain**

    ```swift theme={null}
    import KeychainSwift

    let keychain = KeychainSwift()
    keychain.synchronizable = false  // Don't sync to iCloud

    // Store
    keychain.set(token, forKey: "access_token")

    // Retrieve
    let token = keychain.get("access_token")

    // Delete
    keychain.delete("access_token")
    ```

    **Android: EncryptedSharedPreferences**

    ```kotlin theme={null}
    val masterKey = MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()

    val sharedPreferences = EncryptedSharedPreferences.create(
        context,
        "secure_prefs",
        masterKey,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )

    // Store
    sharedPreferences.edit()
        .putString("access_token", token)
        .apply()

    // Retrieve
    val token = sharedPreferences.getString("access_token", null)
    ```

    **React Native: SecureStore**

    ```typescript theme={null}
    import * as SecureStore from 'expo-secure-store';

    // Store
    await SecureStore.setItemAsync('access_token', token);

    // Retrieve
    const token = await SecureStore.getItemAsync('access_token');

    // Delete
    await SecureStore.deleteItemAsync('access_token');
    ```
  </Tab>

  <Tab title="Server-Side">
    **Database with Encryption**

    ```typescript theme={null}
    import crypto from 'crypto';

    const ENCRYPTION_KEY = process.env.TOKEN_ENCRYPTION_KEY;

    function encryptToken(token: string): string {
      const iv = crypto.randomBytes(16);
      const cipher = crypto.createCipheriv('aes-256-gcm', ENCRYPTION_KEY, iv);

      let encrypted = cipher.update(token, 'utf8', 'hex');
      encrypted += cipher.final('hex');

      const authTag = cipher.getAuthTag();

      return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
    }

    function decryptToken(encryptedToken: string): string {
      const [ivHex, authTagHex, encrypted] = encryptedToken.split(':');

      const iv = Buffer.from(ivHex, 'hex');
      const authTag = Buffer.from(authTagHex, 'hex');
      const decipher = crypto.createDecipheriv('aes-256-gcm', ENCRYPTION_KEY, iv);

      decipher.setAuthTag(authTag);

      let decrypted = decipher.update(encrypted, 'hex', 'utf8');
      decrypted += decipher.final('utf8');

      return decrypted;
    }
    ```

    **Session Storage**

    ```typescript theme={null}
    import session from 'express-session';
    import RedisStore from 'connect-redis';
    import { createClient } from 'redis';

    const redisClient = createClient();

    app.use(session({
      store: new RedisStore({ client: redisClient }),
      secret: process.env.SESSION_SECRET,
      resave: false,
      saveUninitialized: false,
      cookie: {
        secure: true,
        httpOnly: true,
        maxAge: 6 * 60 * 60 * 1000
      }
    }));
    ```
  </Tab>
</Tabs>

## Common Vulnerabilities

<AccordionGroup>
  <Accordion title="Authorization Code Interception" icon="user-secret">
    **Attack:** Attacker intercepts authorization code during redirect.

    **Prevention:**

    * Use PKCE (mandatory)
    * Short authorization code lifetime (10 minutes)
    * HTTPS only in production
    * Validate redirect URI strictly

    **Example:**

    ```typescript theme={null}
    // ✅ Protected by PKCE
    // Even if code is intercepted, attacker doesn't have code_verifier
    const tokens = await exchangeCode({
      code: interceptedCode,
      code_verifier: storedVerifier  // Attacker doesn't have this
    });
    ```
  </Accordion>

  <Accordion title="Cross-Site Request Forgery (CSRF)" icon="shuffle">
    **Attack:** Attacker tricks user into initiating OAuth with attacker's code.

    **Prevention:**

    * Use state parameter (mandatory)
    * Validate state on callback
    * Cryptographically random state

    **Example:**

    ```typescript theme={null}
    // ✅ Protected by state validation
    if (returnedState !== storedState) {
      throw new Error('CSRF attack detected');
    }
    ```
  </Accordion>

  <Accordion title="Token Leakage via Logs" icon="file-lines">
    **Attack:** Tokens exposed in application or server logs.

    **Prevention:**

    * Never log tokens
    * Redact Authorization headers
    * Use log sanitization

    **Example:**

    ```typescript theme={null}
    // ✅ Sanitize logs
    function sanitizeHeaders(headers: Headers) {
      const sanitized = { ...headers };
      if (sanitized.authorization) {
        sanitized.authorization = 'Bearer [REDACTED]';
      }
      return sanitized;
    }

    logger.info('API call', { headers: sanitizeHeaders(headers) });
    ```
  </Accordion>

  <Accordion title="XSS (Cross-Site Scripting)" icon="code">
    **Attack:** Malicious script steals tokens from localStorage.

    **Prevention:**

    * Don't store tokens in localStorage
    * Use HTTP-only cookies
    * Implement Content Security Policy
    * Sanitize user inputs

    **Example:**

    ```typescript theme={null}
    // ❌ Vulnerable
    localStorage.setItem('token', accessToken);

    // ✅ Secure
    res.cookie('token', accessToken, { httpOnly: true });
    ```
  </Accordion>

  <Accordion title="Replay Attacks" icon="rotate-left">
    **Attack:** Reuse intercepted authorization code or token.

    **Prevention:**

    * Authorization codes are single-use
    * Short token lifetimes
    * Implement token revocation
    * Use HTTPS

    **Server-side check:**

    ```python theme={null}
    def exchange_code(code: str):
        if redis.get(f'used_code:{code}'):
            raise Exception('Code already used')

        # Mark as used
        redis.setex(f'used_code:{code}', 600, '1')

        # Exchange for tokens
    ```
  </Accordion>

  <Accordion title="Phishing Attacks" icon="fish">
    **Attack:** Fake login page steals user credentials.

    **Prevention (Hosted UI):**

    * Use hosted UI for authentication
    * Educate users to verify URL
    * Implement domain verification

    **Prevention (API Mode):**

    * Use certificate pinning
    * Implement biometric authentication
    * Add additional verification steps
  </Accordion>
</AccordionGroup>

## Security Checklist

### Pre-Production

<CardGroup cols={2}>
  <Card title="PKCE Implementation" icon="check">
    * [ ] Code verifier is 43-128 characters
    * [ ] SHA256 used for code challenge
    * [ ] code\_challenge\_method is S256
    * [ ] Verifier stored securely
    * [ ] Verifier validated on exchange
  </Card>

  <Card title="State Parameter" icon="check">
    * [ ] State is cryptographically random
    * [ ] State is at least 16 characters
    * [ ] State stored in secure session
    * [ ] State validated on callback
    * [ ] State is single-use
  </Card>

  <Card title="Secret Management" icon="check">
    * [ ] Secrets in environment variables
    * [ ] Secrets never in client code
    * [ ] Secrets never in version control
    * [ ] Secrets not logged
    * [ ] Different keys per environment
  </Card>

  <Card title="Redirect URI" icon="check">
    * [ ] All URIs whitelisted
    * [ ] HTTPS used in production
    * [ ] Exact matching implemented
    * [ ] No wildcards used
    * [ ] Client-side validation added
  </Card>

  <Card title="Token Storage" icon="check">
    * [ ] Platform secure storage used
    * [ ] Not in localStorage (web)
    * [ ] HTTP-only cookies (web)
    * [ ] Keychain (iOS)
    * [ ] EncryptedSharedPreferences (Android)
  </Card>

  <Card title="Error Handling" icon="check">
    * [ ] Tokens not in error messages
    * [ ] Secrets not exposed on error
    * [ ] Sanitized logging implemented
    * [ ] User-friendly error messages
    * [ ] Security events logged
  </Card>
</CardGroup>

### Runtime Security

* [ ] HTTPS enforced in production
* [ ] Certificate validation enabled
* [ ] TLS 1.2+ required
* [ ] Token refresh before expiry
* [ ] Expired refresh token handled
* [ ] Token revocation implemented
* [ ] Logout clears all tokens
* [ ] Session timeout enforced
* [ ] Rate limiting implemented
* [ ] Security headers set

### Monitoring

* [ ] Failed authentication monitored
* [ ] Token refresh failures logged
* [ ] Suspicious activity detected
* [ ] Security events alerted
* [ ] Access patterns analyzed

## Security Headers

Implement these HTTP security headers:

<CodeGroup>
  ```typescript Express.js theme={null}
  import helmet from 'helmet';

  app.use(helmet());

  app.use((req, res, next) => {
    res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
    res.setHeader('X-Content-Type-Options', 'nosniff');
    res.setHeader('X-Frame-Options', 'DENY');
    res.setHeader('X-XSS-Protection', '1; mode=block');
    res.setHeader('Content-Security-Policy', "default-src 'self'");
    res.setHeader('Referrer-Policy', 'no-referrer');
    next();
  });
  ```

  ```python Flask theme={null}
  from flask import Flask
  from flask_talisman import Talisman

  app = Flask(__name__)
  Talisman(app,
      force_https=True,
      strict_transport_security=True,
      content_security_policy={
          'default-src': "'self'",
      }
  )

  @app.after_request
  def set_security_headers(response):
      response.headers['X-Content-Type-Options'] = 'nosniff'
      response.headers['X-Frame-Options'] = 'DENY'
      response.headers['X-XSS-Protection'] = '1; mode=block'
      response.headers['Referrer-Policy'] = 'no-referrer'
      return response
  ```
</CodeGroup>

## Incident Response

If you suspect a security breach:

<Steps>
  <Step title="Immediate Actions">
    * Revoke all affected tokens immediately
    * Rotate compromised secrets
    * Force password reset for affected users
    * Disable compromised client keys
  </Step>

  <Step title="Investigation">
    * Review access logs
    * Identify scope of breach
    * Document timeline
    * Preserve evidence
  </Step>

  <Step title="Notification">
    * Notify affected users
    * Report to security team
    * Comply with regulations (GDPR, etc.)
    * Update security documentation
  </Step>

  <Step title="Prevention">
    * Implement additional controls
    * Update security practices
    * Conduct security review
    * Train development team
  </Step>
</Steps>

## Additional Resources

<CardGroup cols={2}>
  <Card title="OAuth 2.0 Security BCP" icon="book" href="https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics">
    Official OAuth 2.0 Security Best Current Practice
  </Card>

  <Card title="OWASP Top 10" icon="shield" href="https://owasp.org/www-project-top-ten/">
    Web application security risks
  </Card>

  <Card title="RFC 7636 (PKCE)" icon="file" href="https://datatracker.ietf.org/doc/html/rfc7636">
    PKCE specification
  </Card>

  <Card title="RFC 6749 (OAuth 2.0)" icon="file" href="https://datatracker.ietf.org/doc/html/rfc6749">
    OAuth 2.0 Authorization Framework
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Hosted UI Flow" icon="browser" href="/guides/oauth/hosted-ui">
    Implement OAuth with hosted authentication
  </Card>

  <Card title="API Mode Flow" icon="code" href="/guides/oauth/api-mode">
    Implement OAuth with custom UI
  </Card>

  <Card title="Token Management" icon="rotate" href="/guides/oauth/token-management">
    Master token lifecycle
  </Card>

  <Card title="Troubleshooting" icon="screwdriver-wrench" href="/guides/oauth/troubleshooting">
    Common issues and solutions
  </Card>
</CardGroup>
