mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-07-30 03:09:50 +08:00
8.5 KiB
8.5 KiB
Authorization Code + PKCE Flow Template
OAuth 2.0 Authorization Code flow with Proof Key for Code Exchange (PKCE) for public clients.
When to Use
- Single Page Applications (SPAs)
- Mobile native applications
- Desktop applications
- Any client that cannot securely store a client_secret
Mermaid Template
%%{init: {'theme': 'base', 'themeVariables': {
'actorBkg': '#ddd6fe',
'actorTextColor': '#1f2937',
'actorBorder': '#6d28d9',
'signalColor': '#334155',
'signalTextColor': '#1f2937',
'noteBkgColor': '#f8fafc',
'noteTextColor': '#1f2937',
'noteBorderColor': '#334155'
}}}%%
sequenceDiagram
autonumber
box rgba(165,243,252,0.3) USER DEVICE
participant U as 👤 User
participant App as 📱 Mobile App<br/>or SPA
end
box rgba(167,243,208,0.3) SALESFORCE
participant SF as ☁️ Salesforce<br/>Authorization Server
end
Note over U,SF: Authorization Code + PKCE Flow (RFC 7636)
U->>App: 1. Tap "Login with Salesforce"
App->>App: 2. Generate PKCE Parameters
Note over App: code_verifier = random(128 bytes)<br/>code_challenge = BASE64URL(SHA256(code_verifier))
App->>App: 3. Generate state (CSRF protection)
App->>SF: 4. Open browser/webview to /authorize
Note over App,SF: response_type=code<br/>client_id=CONSUMER_KEY<br/>redirect_uri=CALLBACK_URL<br/>scope=api refresh_token<br/>state=RANDOM_STATE<br/>code_challenge=CODE_CHALLENGE<br/>code_challenge_method=S256
SF->>U: 5. Display Login Page
U->>SF: 6. Enter Username & Password
SF->>SF: 7. Authenticate User
SF->>U: 8. Display Consent Screen
Note over SF,U: "App requests access to:<br/>• API Access<br/>• Refresh Token"
U->>SF: 9. Grant Consent (Allow)
SF->>SF: 10. Generate Authorization Code
Note over SF: Store code_challenge with code
SF->>App: 11. Redirect to callback with code
Note over SF,App: scheme://callback?code=AUTH_CODE<br/>&state=RANDOM_STATE
App->>App: 12. Verify state matches
App->>SF: 13. POST /services/oauth2/token
Note over App,SF: grant_type=authorization_code<br/>code=AUTH_CODE<br/>client_id=CONSUMER_KEY<br/>redirect_uri=CALLBACK_URL<br/>code_verifier=CODE_VERIFIER
SF->>SF: 14. Validate Code & PKCE
Note over SF: Verify: BASE64URL(SHA256(code_verifier))<br/>== stored code_challenge
SF->>App: 15. Return Tokens
Note over SF,App: {<br/> access_token: "...",<br/> refresh_token: "...",<br/> instance_url: "https://...",<br/> token_type: "Bearer"<br/>}
App->>App: 16. Store tokens securely
Note over App: Use Keychain (iOS)<br/>or Keystore (Android)
App->>U: 17. ✅ Successfully Logged In
ASCII Fallback Template
┌──────────┐ ┌───────────────┐ ┌────────────────────┐
│ User │ │ Mobile App │ │ Salesforce │
│ │ │ or SPA │ │ (Auth Server) │
└────┬─────┘ └───────┬───────┘ └─────────┬──────────┘
│ │ │
│ 1. Tap Login │ │
│──────────────────>│ │
│ │ │
│ │ 2. Generate PKCE: │
│ │ code_verifier │
│ │ code_challenge │
│ │ │
│ 3. Open Browser/WebView to /authorize │
│<──────────────────│ │
│ │ │
│ 4. GET /authorize │
│ (client_id, code_challenge, state) │
│───────────────────────────────────────────────────────>│
│ │ │
│ 5. Login Page │
│<───────────────────────────────────────────────────────│
│ │ │
│ 6. Enter Credentials │
│───────────────────────────────────────────────────────>│
│ │ │
│ 7. Consent Screen │
│<───────────────────────────────────────────────────────│
│ │ │
│ 8. Grant Consent │
│───────────────────────────────────────────────────────>│
│ │ │
│ 9. Redirect with ?code=ABC123 │
│<───────────────────────────────────────────────────────│
│ │ │
│ 10. Deliver Code │ │
│──────────────────>│ │
│ │ │
│ │ 11. POST /token │
│ │ (code_verifier) │
│ │──────────────────────>│
│ │ │
│ │ 12. Verify PKCE: │
│ │ SHA256(verifier) │
│ │ == challenge │
│ │ │
│ │ 13. Access Token + │
│ │ Refresh Token │
│ │<──────────────────────│
│ │ │
│ 14. Logged In ✅ │ │
│<──────────────────│ │
PKCE Parameter Generation
JavaScript/TypeScript
// Generate code_verifier (43-128 characters)
const codeVerifier = base64URLEncode(crypto.getRandomValues(new Uint8Array(32)));
// Generate code_challenge
const encoder = new TextEncoder();
const data = encoder.encode(codeVerifier);
const digest = await crypto.subtle.digest('SHA-256', data);
const codeChallenge = base64URLEncode(new Uint8Array(digest));
function base64URLEncode(buffer) {
return btoa(String.fromCharCode(...buffer))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
Python
import hashlib
import base64
import secrets
# Generate code_verifier
code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b'=').decode()
# Generate code_challenge
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b'=').decode()
Key Differences from Standard Auth Code
| Aspect | Standard | PKCE |
|---|---|---|
| Client Secret | Required | Not required |
| code_challenge | Not used | Required in /authorize |
| code_verifier | Not used | Required in /token |
| Security | Secret-based | Cryptographic proof |
| Client Type | Confidential | Public |
Security Considerations
- Generate new PKCE values for each authorization request
- Use S256 method (SHA-256), never "plain"
- Store code_verifier securely until token exchange
- Use secure storage for tokens (Keychain/Keystore)
- Implement refresh token rotation for additional security
Customization Points
Replace these placeholders:
CONSUMER_KEY→ Your Connected App's Consumer KeyCALLBACK_URL→ Your registered callback URL (custom scheme for mobile)CODE_VERIFIER→ Generated 43-128 character random stringCODE_CHALLENGE→ BASE64URL(SHA256(CODE_VERIFIER))