mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-07-30 03:09:50 +08:00
9.5 KiB
9.5 KiB
Refresh Token Flow Template
OAuth 2.0 Refresh Token Grant for obtaining new access tokens without user interaction.
When to Use
- Access token has expired
- Extending session without re-authentication
- Background token renewal
- Any scenario requiring long-lived access
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(221,214,254,0.3) CLIENT APPLICATION
participant A as 🖥️ Application
end
box rgba(167,243,208,0.3) SALESFORCE
participant SF as ☁️ Salesforce<br/>Authorization Server
participant API as 📊 Salesforce<br/>REST API
end
Note over A,API: Refresh Token Flow (RFC 6749 Section 6)
A->>API: 1. API Request with Access Token
Note over A,API: Authorization: Bearer ACCESS_TOKEN
API->>A: 2. 401 Unauthorized
Note over API,A: {<br/> "errorCode": "INVALID_SESSION_ID",<br/> "message": "Session expired"<br/>}
A->>A: 3. Detect Token Expiration
A->>SF: 4. POST /services/oauth2/token
Note over A,SF: grant_type=refresh_token<br/>client_id=CONSUMER_KEY<br/>client_secret=CONSUMER_SECRET<br/>refresh_token=REFRESH_TOKEN
SF->>SF: 5. Validate Refresh Token
Note over SF: • Token not revoked<br/>• Client matches original<br/>• Scopes still valid
SF->>SF: 6. Generate New Access Token
Note over SF: May also rotate<br/>refresh token
SF->>A: 7. Return New Tokens
Note over SF,A: {<br/> "access_token": "NEW_TOKEN...",<br/> "refresh_token": "ROTATED...",<br/> "instance_url": "https://...",<br/> "token_type": "Bearer",<br/> "issued_at": "..."<br/>}
A->>A: 8. Update Stored Tokens
Note over A: Replace old tokens<br/>with new ones
A->>API: 9. Retry API Request
Note over A,API: Authorization: Bearer NEW_ACCESS_TOKEN
API->>A: 10. ✅ Successful Response
ASCII Fallback Template
┌───────────────────────┐ ┌────────────────────┐
│ Application │ │ Salesforce │
│ (stored tokens) │ │ (Auth Server) │
└───────────┬───────────┘ └─────────┬──────────┘
│ │
│ 1. API Request │
│ (old access_token) │
│──────────────────────────>│
│ │
│ 2. 401 Unauthorized │
│ (session expired) │
│<──────────────────────────│
│ │
│ 3. POST /token │
│ grant_type= │
│ refresh_token │
│ refresh_token=XXX │
│──────────────────────────>│
│ │
│ 4. Validate │
│ refresh token│
│ │
│ 5. Generate new │
│ access token │
│ │
│ 6. New Tokens │
│ - access_token (new) │
│ - refresh_token │
│ (possibly rotated) │
│<──────────────────────────│
│ │
│ 7. Retry API Request │
│ (new access_token) │
│──────────────────────────>│
│ │
│ 8. Success ✅ │
│<──────────────────────────│
Token Request
curl -X POST https://login.salesforce.com/services/oauth2/token \
-d "grant_type=refresh_token" \
-d "client_id=YOUR_CONSUMER_KEY" \
-d "client_secret=YOUR_CONSUMER_SECRET" \
-d "refresh_token=YOUR_REFRESH_TOKEN"
Response Example
{
"access_token": "00D5g000001ABC...!ARcAQNlBrLGj...",
"refresh_token": "5Aep8617...",
"instance_url": "https://mycompany.my.salesforce.com",
"token_type": "Bearer",
"issued_at": "1702123456789",
"signature": "..."
}
Code Example with Auto-Refresh
import requests
from functools import wraps
class SalesforceClient:
def __init__(self, client_id, client_secret, tokens):
self.client_id = client_id
self.client_secret = client_secret
self.access_token = tokens['access_token']
self.refresh_token = tokens['refresh_token']
self.instance_url = tokens['instance_url']
def refresh_access_token(self):
"""Refresh the access token using the refresh token."""
response = requests.post(
'https://login.salesforce.com/services/oauth2/token',
data={
'grant_type': 'refresh_token',
'client_id': self.client_id,
'client_secret': self.client_secret,
'refresh_token': self.refresh_token
}
)
if response.status_code == 200:
tokens = response.json()
self.access_token = tokens['access_token']
# Handle potential refresh token rotation
if 'refresh_token' in tokens:
self.refresh_token = tokens['refresh_token']
return True
return False
def api_request(self, endpoint, method='GET', **kwargs):
"""Make an API request with automatic token refresh."""
url = f"{self.instance_url}/services/data/v66.0/{endpoint}"
headers = {
'Authorization': f'Bearer {self.access_token}',
'Content-Type': 'application/json'
}
response = requests.request(method, url, headers=headers, **kwargs)
# If token expired, refresh and retry
if response.status_code == 401:
if self.refresh_access_token():
headers['Authorization'] = f'Bearer {self.access_token}'
response = requests.request(method, url, headers=headers, **kwargs)
return response
# Usage
client = SalesforceClient(
client_id='YOUR_CONSUMER_KEY',
client_secret='YOUR_CONSUMER_SECRET',
tokens={
'access_token': 'initial_access_token',
'refresh_token': 'initial_refresh_token',
'instance_url': 'https://mycompany.my.salesforce.com'
}
)
# Automatic refresh on 401
response = client.api_request('sobjects/Account')
Refresh Token Policies
Connected App Settings
| Policy | Description |
|---|---|
Refresh token is valid until revoked |
Never expires (default) |
Immediately expire refresh token |
Single use |
Expire refresh token if not used for N |
Idle timeout |
Expire refresh token after N |
Absolute timeout |
Token Rotation
When configured, Salesforce may return a new refresh token with each refresh. Always check for and store the new refresh token:
if 'refresh_token' in response:
# New refresh token issued - old one is now invalid!
stored_refresh_token = response['refresh_token']
Error Responses
| Error | Cause | Solution |
|---|---|---|
invalid_grant |
Refresh token revoked or expired | Re-authenticate user |
invalid_client |
Client credentials invalid | Check client_id/secret |
invalid_request |
Missing required parameter | Check request format |
Key Characteristics
| Aspect | Value |
|---|---|
| User Interaction | None required |
| Token Rotation | May occur (check response) |
| Best Practice | Refresh proactively before expiration |
| Storage | Securely store refresh token |
Proactive Refresh Strategy
import time
import jwt
def should_refresh_token(access_token, buffer_seconds=300):
"""Check if token should be refreshed (5 min buffer)."""
try:
# Decode without verification to read exp claim
decoded = jwt.decode(access_token, options={"verify_signature": False})
exp_time = decoded.get('exp', 0)
return time.time() > (exp_time - buffer_seconds)
except:
return True # Refresh if we can't decode
# Proactive refresh before API call
if should_refresh_token(access_token):
refresh_access_token()
Security Considerations
- Store refresh tokens securely - Encrypted storage, secure keychain
- Handle rotation - Always update stored refresh token
- Implement revocation - Revoke tokens on logout
- Monitor for abuse - Track refresh token usage patterns
- Use HTTPS only - Never send tokens over HTTP
Revoking Tokens
When user logs out, revoke the refresh token:
curl -X POST https://login.salesforce.com/services/oauth2/revoke \
-d "token=YOUR_REFRESH_TOKEN"
Customization Points
Replace these placeholders:
CONSUMER_KEY→ Your Connected App's Consumer KeyCONSUMER_SECRET→ Your Connected App's Consumer SecretREFRESH_TOKEN→ The refresh token from initial authorization