# Authorization Code with PKCE

## Overview

PKCE (Proof Key for Code Exchange) is an extension to the OAuth 2.0 Authorization Code Grant (RFC 7636) that provides enhanced security, especially for public clients like single-page applications (SPAs) and mobile apps that cannot securely store a client_secret. PKCE is now recommended by OAuth 2.0 best practices and is already supported in Genesys Cloud.

**Status**: Implicit Grant deprecated (May 2027 deadline), PKCE is the secure replacement.

---

## Why PKCE?

```
The Problem PKCE Solves:

Authorization Code Can Be Intercepted:
├─ Attacker monitors network traffic
├─ Captures authorization code
├─ Attempts to exchange code for token
└─ Without PKCE: Attacker succeeds

Public Clients Cannot Store Secrets:
├─ Browser-based apps: No server backend
├─ Mobile apps: Can be reverse engineered
├─ Desktop apps: Can be analyzed
├─ Cannot safely store client_secret
└─ Traditional approach inadequate

PKCE Solution:

Add Proof to Authorization Code:
├─ Generate random code_verifier
├─ Compute code_challenge (hash)
├─ Send code_challenge to auth server
├─ Auth server stores it
├─ Only original verifier can exchange code
└─ Attacker lacks verifier → cannot exchange

Proof Cannot Be Reversed:
├─ code_challenge = SHA256(code_verifier)
├─ Hash is one-way function
├─ Cannot reverse-engineer verifier from hash
├─ Even if code intercepted: useless without verifier
└─ Verifier never sent over network
```

---

## Complete PKCE Flow

### Step 1: Generate Proof Strings

```
Your Application Generates:

code_verifier:
├─ Random string, 43-128 characters
├─ Cryptographically secure (use crypto random)
├─ Unrepeatable (different each request)
├─ Only stored in memory
├─ Example: "E9Mrozoa2owusvxrFHo89ejyK3OMVZZWhtbQrHfl"

code_challenge:
├─ SHA256 hash of code_verifier
├─ BASE64-URL encoded
├─ Sent to authorization server
├─ Example: "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"

code_challenge_method:
├─ "S256" (SHA256 hash)
├─ Only recommended method
├─ "plain" exists but deprecated
└─ Always use "S256"

Pseudo Code:

code_verifier = generateRandomString(128)
code_challenge = BASE64URL(SHA256(code_verifier))
code_challenge_method = "S256"
```

### Step 2: Redirect to Authorization Endpoint

```
Redirect user browser to:

https://login.mypurecloud.com/oauth/authorize
  ?client_id=YOUR_CLIENT_ID
  &response_type=code
  &redirect_uri=https://yourapp.com/callback
  &scope=conversations:readonly
  &code_challenge=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
  &code_challenge_method=S256
  &state=random_state_string

Parameters:

client_id:
├─ Your app's public identifier
└─ Can be embedded in SPA code

response_type:
├─ Must be "code"
└─ Returns authorization code

redirect_uri:
├─ Where user is redirected
├─ Can be embedded in SPA code
├─ Example: https://yourapp.com/callback
└─ Must be registered in OAuth client

scope:
├─ Requested permissions
├─ Space-separated list
└─ Example: "conversations:readonly"

code_challenge (PKCE):
├─ SHA256 hash of random string
├─ Sent to auth server
├─ Auth server stores it
└─ Cannot derive original verifier

code_challenge_method (PKCE):
├─ "S256" (recommended and required)
├─ Indicates SHA256 method used
└─ Only secure method

state (CSRF Protection):
├─ Random string
├─ Prevents CSRF attacks
└─ Verified in callback
```

### Step 3: User Authenticates & Consents

```
Same as Authorization Code Grant:

1. User sees Genesys Cloud login
2. User enters credentials
3. User sees permission consent screen
4. User grants permission
5. Auth server generates authorization code
6. Auth server stores code_challenge with authorization code
```

### Step 4: Callback with Authorization Code

```
Auth server redirects to callback:

https://yourapp.com/callback
  ?code=AUTH_CODE_12345abcde67890
  &state=random_state_string

In Callback Handler:

1. Verify state parameter (CSRF check)
2. Retrieve authorization code
3. Verify you have code_verifier in memory
4. Proceed to Step 5 (exchange code)

Never:
├─ Do NOT send code_verifier in callback
├─ Do NOT include code_verifier in URL
├─ Do NOT exchange code in browser
└─ Do NOT expose code_verifier to user
```

### Step 5: Exchange Code with PKCE Proof

```
Now you have:
├─ Authorization code (from Step 4)
├─ code_verifier (generated in Step 1, stored in memory)
└─ client_id (public, stored in app)

Exchange Code:

POST https://login.mypurecloud.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTH_CODE_12345abcde67890
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/callback
&code_verifier=E9Mrozoa2owusvxrFHo89ejyK3OMVZZWhtbQrHfl

Parameters:

grant_type:
├─ "authorization_code"
└─ Standard OAuth parameter

code:
├─ Authorization code from Step 4
└─ Single-use only

client_id:
├─ Your public client ID
└─ Can be public

redirect_uri:
├─ Must match redirect_uri from Step 2
└─ Confirms code ownership

code_verifier (PKCE):
├─ Original random string from Step 1
├─ Proves you own the authorization code
├─ MUST match the code_challenge sent in Step 2
└─ Server recomputes SHA256(code_verifier) and compares

NOTE: NO client_secret needed!
├─ PKCE replaces need for client_secret
├─ Perfect for public clients
├─ Proof of ownership is cryptographic
└─ Signature is binding
```

### Step 6: Server Validates & Issues Token

```
Genesys Cloud Authorization Server:

1. Receives code_verifier
2. Retrieves authorization code from storage
3. Retrieves stored code_challenge
4. Computes: SHA256(code_verifier) → new_hash
5. Compares: new_hash == stored_code_challenge?

If MATCH (✓):
├─ code_verifier is correct
├─ Same application that requested code
├─ Issue access token
└─ Return token in response

If NO MATCH (✗):
├─ code_verifier is wrong
├─ Likely code theft attempt
├─ Reject request with error
└─ Attack prevented!

Response on Success:

HTTP 200 OK
{
  "access_token": "abc123xyz789...",
  "token_type": "bearer",
  "expires_in": 3600,
  "scope": "conversations:readonly"
}

Response on Failure:

HTTP 400 Bad Request
{
  "error": "invalid_grant",
  "error_description": "code_verifier invalid"
}
```

### Step 7: Use Access Token

```
Same as Authorization Code Grant:

GET /api/v2/conversations
Authorization: Bearer abc123xyz789...

Response:
HTTP 200 OK
{...conversation data...}
```

---

## JavaScript Implementation Example

```javascript
// Configuration
const CLIENT_ID = 'your_client_id';
const REDIRECT_URI = 'https://yourapp.com/callback';
const SCOPES = 'conversations:readonly users:readonly';
const GENESYS_REGION = 'mypurecloud.com';

// Step 1: Generate PKCE code challenge
async function generatePKCE() {
  // Generate random code_verifier
  const array = new Uint8Array(64);
  crypto.getRandomValues(array);
  const codeVerifier = btoa(String.fromCharCode.apply(null, array))
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');
  
  // Compute code_challenge = BASE64URL(SHA256(code_verifier))
  const encoder = new TextEncoder();
  const data = encoder.encode(codeVerifier);
  const hashBuffer = await crypto.subtle.digest('SHA-256', data);
  
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  const hashString = String.fromCharCode.apply(null, hashArray);
  const codeChallenge = btoa(hashString)
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');
  
  return { codeVerifier, codeChallenge };
}

// Step 2: Redirect to authorization
async function login() {
  const { codeVerifier, codeChallenge } = await generatePKCE();
  
  // Store verifier in memory (or sessionStorage for SPA)
  sessionStorage.setItem('pkce_verifier', codeVerifier);
  
  // Generate state for CSRF protection
  const state = btoa(Math.random().toString()).substring(0, 40);
  sessionStorage.setItem('pkce_state', state);
  
  // Redirect to authorization endpoint
  const authUrl = new URL(`https://login.${GENESYS_REGION}/oauth/authorize`);
  authUrl.searchParams.append('client_id', CLIENT_ID);
  authUrl.searchParams.append('response_type', 'code');
  authUrl.searchParams.append('redirect_uri', REDIRECT_URI);
  authUrl.searchParams.append('scope', SCOPES);
  authUrl.searchParams.append('code_challenge', codeChallenge);
  authUrl.searchParams.append('code_challenge_method', 'S256');
  authUrl.searchParams.append('state', state);
  
  window.location.href = authUrl.toString();
}

// Step 4: Handle callback
async function handleCallback() {
  const urlParams = new URLSearchParams(window.location.search);
  const code = urlParams.get('code');
  const state = urlParams.get('state');
  const error = urlParams.get('error');
  
  // Check for errors
  if (error) {
    console.error('Authorization error:', error);
    return false;
  }
  
  // Verify state (CSRF protection)
  const storedState = sessionStorage.getItem('pkce_state');
  if (state !== storedState) {
    console.error('State mismatch - CSRF attack detected');
    return false;
  }
  
  // Retrieve code_verifier from memory
  const codeVerifier = sessionStorage.getItem('pkce_verifier');
  if (!codeVerifier) {
    console.error('Code verifier not found');
    return false;
  }
  
  // Step 5: Exchange code for token
  try {
    const response = await fetch(`https://login.${GENESYS_REGION}/oauth/token`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        code: code,
        client_id: CLIENT_ID,
        redirect_uri: REDIRECT_URI,
        code_verifier: codeVerifier  // PKCE proof
      })
    });
    
    if (!response.ok) {
      throw new Error('Token exchange failed');
    }
    
    const { access_token } = await response.json();
    
    // Step 7: Store token and use it
    sessionStorage.setItem('access_token', access_token);
    
    // Clean up PKCE values
    sessionStorage.removeItem('pkce_verifier');
    sessionStorage.removeItem('pkce_state');
    
    console.log('Login successful');
    return true;
    
  } catch (error) {
    console.error('Token exchange error:', error);
    return false;
  }
}

// Use access token
async function callAPI(endpoint) {
  const token = sessionStorage.getItem('access_token');
  
  if (!token) {
    console.error('No access token found');
    return null;
  }
  
  try {
    const response = await fetch(`https://api.${GENESYS_REGION}/api/v2${endpoint}`, {
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    });
    
    if (response.status === 401) {
      console.error('Token expired, user must log in again');
      login();
      return null;
    }
    
    if (!response.ok) {
      throw new Error(`API error: ${response.status}`);
    }
    
    return await response.json();
    
  } catch (error) {
    console.error('API call failed:', error);
    return null;
  }
}

// Example: Login button click handler
document.getElementById('loginBtn').addEventListener('click', login);

// Example: Handle callback on return from auth server
if (window.location.pathname === '/callback') {
  handleCallback().then(success => {
    if (success) {
      window.location.href = '/dashboard';
    } else {
      window.location.href = '/error';
    }
  });
}

// Example: Call API
async function getConversations() {
  const data = await callAPI('/conversations?pageSize=100&pageNumber=1');
  console.log('Conversations:', data);
}
```

---

## Why Migrate from Implicit Grant to PKCE?

```
Implicit Grant (DEPRECATED):
├─ Status: Deprecated November 2025
├─ New clients: Blocked March 2026
├─ Existing clients: Must migrate by May 2027
├─ Issues: Token in URL, browser history, no protection
└─ Replacement: PKCE

PKCE (RECOMMENDED):
├─ Status: Supported and recommended
├─ Security: Enhanced via proof mechanism
├─ Suitable for: All client types
├─ Implementation: Slightly more complex
└─ Future-proof: Long-term standard

Migration Path:

Step 1: Update OAuth Client
├─ Delete old Implicit Grant client (or keep if needed)
├─ Create new Authorization Code + PKCE client
└─ Note new client_id

Step 2: Update Application Code
├─ Implement PKCE proof generation
├─ Add code_challenge to authorization
├─ Include code_verifier in token exchange
└─ Remove implicit grant references

Step 3: Test Thoroughly
├─ Test login flow end-to-end
├─ Test token exchange
├─ Test API calls
└─ Verify error handling

Step 4: Deploy
├─ Update production code
├─ Verify working in production
├─ Monitor for errors
└─ Keep old client available briefly

Step 5: Cleanup
├─ Remove old Implicit Grant client
├─ Update documentation
├─ Notify users if applicable
└─ Archive old implementation

Timeline:
├─ Now (March 2026): Implement PKCE
├─ Before May 2027: Migration required
├─ May 2027: Implicit Grant stopped working
└─ Plan ahead to avoid outages!
```

---

## PKCE vs Implicit Grant

```
Security Comparison:

Token Exposure:
├─ Implicit: Token in URL fragment (visible)
├─ PKCE: Token in body, 200 response (hidden)
└─ PKCE wins: Less exposed

Token Lifetime:
├─ Implicit: No refresh, static lifetime
├─ PKCE: Can have refresh tokens
└─ PKCE wins: Better UX

Browser History:
├─ Implicit: Token in URL history (risk)
├─ PKCE: No URL tokens (safe)
└─ PKCE wins: No history exposure

Code Interception:
├─ Implicit: Not applicable (no code)
├─ PKCE: Protected by proof (secure)
└─ PKCE wins: Protected exchange

CSRF Protection:
├─ Implicit: State parameter only
├─ PKCE: State + cryptographic proof
└─ PKCE wins: Multiple protections

Standards Alignment:
├─ Implicit: Deprecated OAuth 2.0
├─ PKCE: Modern OAuth 2.0 best practice
└─ PKCE wins: Future-proof

Complexity:
├─ Implicit: Simple (but insecure)
├─ PKCE: Slightly more complex (much more secure)
└─ Trade-off: Worth the extra complexity
```

---

## Security Checklist for PKCE

```
PKCE Implementation Security:

□ Generate cryptographically secure code_verifier
  ├─ Use crypto.getRandomValues() (not Math.random)
  ├─ 64 bytes minimum (not shorter)
  └─ Different every request

□ Compute SHA256 hash of verifier
  ├─ Use crypto.subtle.digest()
  ├─ Encode to BASE64URL format
  └─ Do not use plain method

□ Store code_verifier securely
  ├─ Memory only (not localStorage)
  ├─ Clear after token exchange
  ├─ Not persisted
  └─ Lost on page reload

□ Use S256 method always
  ├─ Never use "plain" method
  ├─ Only S256 recommended
  └─ Specify in authorization

□ Include state parameter
  ├─ Separate CSRF protection
  ├─ Random and unpredictable
  ├─ Verify in callback
  └─ Different from verifier

□ Validate SSL certificates
  ├─ HTTPS always
  ├─ Check cert validity
  └─ Reject self-signed

□ Do not expose secrets
  ├─ code_verifier: Memory only
  ├─ access_token: SessionStorage or memory
  ├─ Never in localStorage
  └─ Clean up after logout

□ Handle errors gracefully
  ├─ Catch network errors
  ├─ Retry on failure
  ├─ Don't expose verifier in errors
  └─ Log safely
```

---

## Key Takeaways: Chapter 4

- **Enhanced Security** - Cryptographic proof prevents authorization code interception
- **No Client Secret** - Suitable for public clients (SPAs, mobile, desktop)
- **Proof Mechanism** - Verifier prevents code theft (cannot reverse-engineer from hash)
- **RFC 7636 Standard** - Modern OAuth 2.0 best practice
- **Implicit Replacement** - Use PKCE instead of deprecated Implicit Grant
- **Migration Deadline** - May 2027 cutoff for Implicit Grant
- **Slightly Complex** - More code than Implicit, but much more secure
- **Future-Proof** - Long-term standard, recommended by OAuth 2.0 experts

---

## Interview Prep: PKCE

| Question | Answer |
|---|---|
| What is PKCE? | Proof Key for Code Exchange - enhanced OAuth Code grant security |
| Why PKCE needed? | Prevents authorization code interception attacks |
| code_verifier? | Random string (43-128 chars) generated by client, never sent over network |
| code_challenge? | SHA256(code_verifier), BASE64URL encoded, sent to auth server |
| How prevent intercept? | Only original code_verifier can exchange the code, attacker lacks verifier |
| Why not reverse? | SHA256 is one-way hash function, cannot derive verifier from challenge |
| S256 vs plain? | S256 (SHA256) is secure, plain is deprecated, always use S256 |
| When use PKCE? | Public clients (SPAs, mobile, desktop) that cannot store client_secret |
| Migration deadline? | May 2027 for Implicit Grant existing clients |
| State parameter? | Separate CSRF protection, still needed with PKCE |

---

## Document Version
**Chapter**: 4 of 8  
**Last Updated**: March 2026  
**Status**: Current with RFC 7636  
**Scope**: PKCE Flow, Security, Implementation, Migration from Implicit