Authentication Guide
Authentication Guide
Section titled “Authentication Guide”The CRM M2M API uses OAuth 2.0 Client Credentials flow for secure machine-to-machine authentication. This guide explains how the authentication system works and best practices for implementation.
Getting Your API Endpoints
Section titled “Getting Your API Endpoints”Your two endpoints — the API base URL and the OAuth token endpoint — are issued with your M2M credentials. Your administrator provides them when they create your M2M client: both are shown under Settings → API Access in the CRM, and the token endpoint is also returned in the create-client response.
Set them as environment variables before running any examples in this guide:
# Values supplied with your M2M credentialsexport OAUTH_TOKEN_ENDPOINT="{token-endpoint}"export API_ENDPOINT="https://{api-base-url}"
# Verifyecho "Token Endpoint: $OAUTH_TOKEN_ENDPOINT"echo "API Endpoint: $API_ENDPOINT"OAuth 2.0 Client Credentials Flow
Section titled “OAuth 2.0 Client Credentials Flow”The Client Credentials flow is designed for server-to-server authentication where no user interaction is involved.
Flow Diagram
Section titled “Flow Diagram”┌─────────────────┐ ┌─────────────────┐│ │ │ ││ Your App │ (1) POST /oauth2/token │ Cognito ││ │ ─────────────────────────────> │ (Auth Server) ││ │ client_id + client_secret │ ││ │ │ ││ │ (2) access_token (JWT) │ ││ │ <───────────────────────────── │ │└─────────────────┘ └─────────────────┘ │ │ (3) API Request with Bearer token │ ▼┌─────────────────┐ ┌─────────────────┐│ │ │ ││ CRM API │ (4) Validate JWT + scopes │ Custom ││ Gateway │ ─────────────────────────────> │ Authorizer ││ │ │ (Lambda) ││ │ (5) Allow/Deny │ ││ │ <───────────────────────────── │ │└─────────────────┘ └─────────────────┘Step 1: Token Request
Section titled “Step 1: Token Request”Request Format
Section titled “Request Format”curl -X POST $OAUTH_TOKEN_ENDPOINT \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" \ -d "scope=crm-api/products.read crm-api/brands.read"Raw HTTP format:
POST /oauth2/token HTTP/1.1Host: {your-cognito-domain}Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&scope=crm-api/products.read%20crm-api/brands.readRequired Parameters
Section titled “Required Parameters”| Parameter | Description | Example |
|---|---|---|
grant_type | Must be client_credentials | client_credentials |
client_id | Your 26-character client ID | abc123xyz456def789ghi012 |
client_secret | Your client secret (shown once at creation) | qwertyuiop... |
scope | Space-separated list of requested scopes | crm-api/products.read crm-api/brands.read |
Success Response (200 OK)
Section titled “Success Response (200 OK)”{ "access_token": "eyJraWQiOiJxYzVPXC9cL3FqV...", "token_type": "Bearer", "expires_in": 3600}Error Responses
Section titled “Error Responses”Invalid Credentials (400 Bad Request)
Section titled “Invalid Credentials (400 Bad Request)”{ "error": "invalid_client", "error_description": "Client authentication failed"}Causes:
- Incorrect
client_idorclient_secret - Client has been revoked
- Credentials don’t match
Invalid Scope (400 Bad Request)
Section titled “Invalid Scope (400 Bad Request)”{ "error": "invalid_scope", "error_description": "Invalid scope"}Causes:
- Requested scope not granted to this client
- Typo in scope name (must be exact:
crm-api/products.read)
Step 2: Access Token Structure
Section titled “Step 2: Access Token Structure”Access tokens are JSON Web Tokens (JWT) with the following structure:
JWT Header
Section titled “JWT Header”{ "kid": "qc5O//qjV...", "alg": "RS256"}JWT Payload
Section titled “JWT Payload”{ "sub": "abc123xyz456def789ghi012", "token_use": "access", "scope": "crm-api/products.read crm-api/brands.read", "auth_time": 1732540800, "iss": "https://cognito-idp.{REGION}.amazonaws.com/{REGION}_XXXXXX", "exp": 1732544400, "iat": 1732540800, "version": 2, "jti": "uuid-here", "client_id": "abc123xyz456def789ghi012"}Key Claims
Section titled “Key Claims”| Claim | Description |
|---|---|
sub | Subject (client ID) |
scope | Granted scopes (space-separated) |
client_id | M2M client identifier |
exp | Expiration time (Unix timestamp) |
iat | Issued at time (Unix timestamp) |
iss | Issuer (Cognito User Pool) |
Step 3: Using the Access Token
Section titled “Step 3: Using the Access Token”Authorization Header
Section titled “Authorization Header”Include the access token in the Authorization header with the Bearer scheme:
GET /products?limit=10 HTTP/1.1Host: {api-base-url}Authorization: Bearer eyJraWQiOiJxYzVPXC9cL3FqV...Token Validation
Section titled “Token Validation”The API Gateway’s custom authorizer validates:
- Signature - JWT signature using Cognito public keys
- Expiration - Token must not be expired
- Issuer - Must match the Cognito User Pool
- Token Use - Must be an
accesstoken (notidtoken) - Client Status - Client must be active (not revoked)
- Scopes - Token must have required scope for the endpoint
Token Lifecycle
Section titled “Token Lifecycle”Token Expiration
Section titled “Token Expiration”- Lifetime: 1 hour (3600 seconds) for clients created from now on; clients created earlier keep 24 hours (86400 seconds). Always use
expires_infrom the token response. - Fixed: Cannot be extended or refreshed
- Solution: Request a new token when expired
Token Caching
Section titled “Token Caching”Best Practice: Cache tokens until they expire to minimize token requests.
class TokenCache { constructor() { this.token = null; this.expiresAt = null; }
async getToken() { // Return cached token if still valid (with 5-minute buffer) if (this.token && this.expiresAt > Date.now() + 300000) { return this.token; }
// Request new token const response = await this.requestToken(); this.token = response.access_token; this.expiresAt = Date.now() + (response.expires_in * 1000);
return this.token; }
async requestToken() { // ... token request implementation }}Token Revocation
Section titled “Token Revocation”When a client is revoked:
- Immediate: Client cannot request new tokens
- Existing tokens: Rejected with
403from the next API request (if the revocation check is temporarily unavailable, existing tokens are accepted until they expire)
Total revocation time: Immediate for new tokens; from the next API request for tokens already issued.
Security Best Practices
Section titled “Security Best Practices”Protect Client Secrets
Section titled “Protect Client Secrets”DO:
- ✅ Store secrets in environment variables or secret management systems (AWS Secrets Manager, HashiCorp Vault)
- ✅ Use encrypted storage for secrets
- ✅ Rotate secrets periodically
- ✅ Restrict access to secrets (need-to-know basis)
- ✅ Log secret access (audit trail)
DON’T:
- ❌ Hardcode secrets in source code
- ❌ Commit secrets to version control
- ❌ Share secrets via email or chat
- ❌ Log secrets in application logs
- ❌ Expose secrets in client-side code
Use HTTPS Only
Section titled “Use HTTPS Only”All API requests must use HTTPS. HTTP requests will be rejected.
Implement Retry Logic
Section titled “Implement Retry Logic”Handle transient failures with exponential backoff:
async function requestWithRetry(url, options, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await fetch(url, options); } catch (error) { if (attempt === maxRetries - 1) throw error;
// Exponential backoff: 1s, 2s, 4s const delay = Math.pow(2, attempt) * 1000; await new Promise(resolve => setTimeout(resolve, delay)); } }}Handle Token Expiration
Section titled “Handle Token Expiration”async function apiRequest(endpoint) { try { const response = await fetch(endpoint, { headers: { Authorization: `Bearer ${cachedToken}` } });
// Token expired or invalid if (response.status === 401) { cachedToken = await getNewToken(); return apiRequest(endpoint); // Retry with new token }
return response; } catch (error) { // Handle error }}Scope Management
Section titled “Scope Management”Principle of Least Privilege:
- Only request scopes your application actually needs
- Create separate clients for different integrations
- Review and audit scope grants periodically
Common Issues
Section titled “Common Issues””invalid_client” Error
Section titled “”invalid_client” Error”Causes:
- Wrong
client_idorclient_secret - Client has been revoked
- Typo in credentials
Solution:
- Verify credentials are correct
- Check client status in admin panel
- Regenerate client if secret is lost
”invalid_scope” Error
Section titled “”invalid_scope” Error”Causes:
- Requested scope not granted to client
- Incorrect scope format
Solution:
- Verify scope names match exactly (case-sensitive)
- Check granted scopes in admin panel:
crm-api/products.read(notproducts.read)
401 Unauthorized on API Calls
Section titled “401 Unauthorized on API Calls”Causes:
- Token expired (older than its
expires_inlifetime) - Token not included in request
- Wrong header format
Solution:
- Implement token caching with expiration checks
- Verify header format:
Authorization: Bearer <token> - Check token expiration time (
expclaim)
403 Forbidden on API Calls
Section titled “403 Forbidden on API Calls”Causes:
- Missing required scope
- Client revoked (its existing tokens are rejected from the next API request)
- Endpoint is not part of the M2M API surface
Solution:
- Verify client has required scope for endpoint
- If the client was revoked, its tokens are rejected and it cannot get new ones — create a new client to restore access
- Check CloudWatch logs for detailed error messages
Testing Authentication
Section titled “Testing Authentication”Using cURL
Section titled “Using cURL”# 1. Get tokenTOKEN=$(curl -s -X POST {token-endpoint} \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" \ -d "scope=crm-api/products.read" \ | jq -r '.access_token')
# 2. Use tokencurl -X GET https://{api-base-url}/products \ -H "Authorization: Bearer $TOKEN"Using Postman
Section titled “Using Postman”- Import the Postman Collection
- Set collection variables (
apiUrl,cognitoDomain,clientId,clientSecret) - Use “Get Access Token” request (automatically saves token)
- Other requests automatically use saved token
Next Steps
Section titled “Next Steps”- Client Management - Creating and managing M2M clients
- API Reference - Complete endpoint documentation
- Code Examples - Full implementation examples
- Troubleshooting - Solutions to common problems