# 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 ```mermaid %%{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
Authorization Server participant API as πŸ“Š Salesforce
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: {
"errorCode": "INVALID_SESSION_ID",
"message": "Session expired"
} A->>A: 3. Detect Token Expiration A->>SF: 4. POST /services/oauth2/token Note over A,SF: grant_type=refresh_token
client_id=CONSUMER_KEY
client_secret=CONSUMER_SECRET
refresh_token=REFRESH_TOKEN SF->>SF: 5. Validate Refresh Token Note over SF: β€’ Token not revoked
β€’ Client matches original
β€’ Scopes still valid SF->>SF: 6. Generate New Access Token Note over SF: May also rotate
refresh token SF->>A: 7. Return New Tokens Note over SF,A: {
"access_token": "NEW_TOKEN...",
"refresh_token": "ROTATED...",
"instance_url": "https://...",
"token_type": "Bearer",
"issued_at": "..."
} A->>A: 8. Update Stored Tokens Note over A: Replace old tokens
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 ```text β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 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 ```bash 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 ```json { "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 ```python 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: ```python 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 ```python 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 1. **Store refresh tokens securely** - Encrypted storage, secure keychain 2. **Handle rotation** - Always update stored refresh token 3. **Implement revocation** - Revoke tokens on logout 4. **Monitor for abuse** - Track refresh token usage patterns 5. **Use HTTPS only** - Never send tokens over HTTP ## Revoking Tokens When user logs out, revoke the refresh token: ```bash 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 Key - `CONSUMER_SECRET` β†’ Your Connected App's Consumer Secret - `REFRESH_TOKEN` β†’ The refresh token from initial authorization