mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-07-30 03:09:50 +08:00
7.6 KiB
7.6 KiB
Client Credentials Flow Template
OAuth 2.0 Client Credentials Grant for server-to-server authentication using client_id and client_secret.
When to Use
- Service accounts
- Background processes
- System-to-system integrations
- When no specific user context is needed
- External Client Apps (ECAs) with client credentials enabled
Prerequisites
- Connected App or External Client App configured
- Client Credentials flow enabled
- Execution user assigned via Permission Set (for ECAs)
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 C as 🖥️ Service<br/>(Backend Server)
end
box rgba(167,243,208,0.3) SALESFORCE
participant SF as ☁️ Salesforce<br/>Authorization Server
end
Note over C,SF: Client Credentials Flow (RFC 6749 Section 4.4)
C->>C: 1. Retrieve Client Credentials
Note over C: client_id = CONSUMER_KEY<br/>client_secret = CONSUMER_SECRET
C->>SF: 2. POST /services/oauth2/token
Note over C,SF: grant_type=client_credentials<br/>client_id=CONSUMER_KEY<br/>client_secret=CONSUMER_SECRET
SF->>SF: 3. Validate Client Credentials
Note over SF: Verify client_id exists<br/>Verify client_secret matches
SF->>SF: 4. Determine Execution User
Note over SF: For ECA: Use assigned<br/>Permission Set user<br/><br/>For Connected App: Use<br/>"Run As" user
SF->>SF: 5. Generate Access Token
Note over SF: Token runs in context<br/>of execution user
SF->>C: 6. Return Access Token
Note over SF,C: {<br/> "access_token": "...",<br/> "instance_url": "https://...",<br/> "token_type": "Bearer",<br/> "issued_at": "..."<br/>}
Note over C: ⚠️ No refresh_token returned<br/>⚠️ No user context (runs as service user)
C->>SF: 7. Make API Calls
Note over C,SF: Authorization: Bearer ACCESS_TOKEN
SF->>SF: 8. Execute as Service User
Note over SF: All operations run with<br/>execution user's permissions
SF->>C: 9. API Response
ASCII Fallback Template
┌───────────────────────┐ ┌────────────────────┐
│ Service/Backend │ │ Salesforce │
│ (client_id/secret) │ │ (Auth Server) │
└───────────┬───────────┘ └─────────┬──────────┘
│ │
│ 1. POST /token │
│ grant_type= │
│ client_credentials │
│ client_id=KEY │
│ client_secret=SECRET │
│──────────────────────────>│
│ │
│ 2. Validate │
│ credentials │
│ │
│ 3. Determine │
│ execution │
│ user │
│ │
│ 4. Generate │
│ access token │
│ │
│ 5. Access Token │
│ (NO refresh token!) │
│ (NO user context!) │
│<──────────────────────────│
│ │
│ 6. API Request │
│ (Bearer token) │
│──────────────────────────>│
│ │
│ 7. API Response │
│ (runs as svc user) │
│<──────────────────────────│
Token Request
curl -X POST https://login.salesforce.com/services/oauth2/token \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CONSUMER_KEY" \
-d "client_secret=YOUR_CONSUMER_SECRET"
Response Example
{
"access_token": "00D5g000001ABC...!ARcAQNlBrLGj...",
"instance_url": "https://mycompany.my.salesforce.com",
"token_type": "Bearer",
"issued_at": "1702123456789"
}
Code Examples
Python
import requests
response = requests.post(
'https://login.salesforce.com/services/oauth2/token',
data={
'grant_type': 'client_credentials',
'client_id': 'YOUR_CONSUMER_KEY',
'client_secret': 'YOUR_CONSUMER_SECRET'
}
)
token_data = response.json()
access_token = token_data['access_token']
instance_url = token_data['instance_url']
# Make API call
headers = {'Authorization': f'Bearer {access_token}'}
api_response = requests.get(
f'{instance_url}/services/data/v66.0/sobjects/Account',
headers=headers
)
Node.js
const axios = require('axios');
const response = await axios.post(
'https://login.salesforce.com/services/oauth2/token',
new URLSearchParams({
grant_type: 'client_credentials',
client_id: 'YOUR_CONSUMER_KEY',
client_secret: 'YOUR_CONSUMER_SECRET'
})
);
const { access_token, instance_url } = response.data;
// Make API call
const apiResponse = await axios.get(
`${instance_url}/services/data/v66.0/sobjects/Account`,
{ headers: { Authorization: `Bearer ${access_token}` } }
);
Connected App vs External Client App
| Feature | Connected App | External Client App (ECA) |
|---|---|---|
| Secret Management | Manual rotation | Automatic rotation supported |
| User Assignment | "Run As" user | Permission Set assignment |
| Configuration | Setup → App Manager | Setup → External Client Apps |
| Recommended | Legacy integrations | New integrations (2024+) |
Key Characteristics
| Aspect | Value |
|---|---|
| User Interaction | None required |
| Refresh Token | Not returned - re-authenticate |
| User Context | Runs as execution/service user |
| Scopes | Limited to service account permissions |
| Best For | System integrations, batch jobs |
Enabling Client Credentials
For Connected App
- Setup → App Manager → Edit Connected App
- Enable OAuth Settings
- Enable "Client Credentials Flow"
- Set "Run As" user
For External Client App
- Setup → External Client Apps → New
- Configure OAuth Settings
- Enable
isClientCredentialsEnabled - Assign Permission Set with user
Security Considerations
- Protect client_secret - Never expose in client-side code
- Use dedicated service user with minimal permissions
- Rotate secrets regularly (especially for Connected Apps)
- Monitor API usage - Set up event monitoring
- Restrict IP ranges if possible
Limitations
- No refresh tokens (must re-authenticate)
- No user context (cannot impersonate users)
- Limited to service user's permissions
- Cannot use for user-specific operations
Customization Points
Replace these placeholders:
CONSUMER_KEY→ Your Connected App's Consumer KeyCONSUMER_SECRET→ Your Connected App's Consumer Secretlogin.salesforce.com→ Ortest.salesforce.comfor sandbox