Appearance
Authentication Guide
Table of Contents
- Overview
- Authentication Methods
- OAuth2 Client Credentials Flow
- Token Management
- Security Best Practices
- Code Examples
- Error Scenarios
Overview
The Engagifii CRM API uses OAuth2 authentication to secure access to all endpoints. This guide provides comprehensive information on implementing authentication in your applications.
Key Security Features
- OAuth2 Client Credentials Grant: Industry-standard authentication flow
- Bearer Token Authentication: Secure token-based access
- Multi-tenant Isolation: Tenant-specific data access controls
- Token Expiration: Automatic token expiry for enhanced security
- HTTPS Enforcement: All communications encrypted in transit
Authentication Methods
Primary Method: OAuth2 Client Credentials
The API uses the OAuth2 Client Credentials grant type, which is ideal for server-to-server authentication where no user interaction is required.
Flow Overview:
- Your application requests an access token using client credentials
- The authorization server validates credentials and issues a token
- Your application includes the token in subsequent API requests
- The API validates the token and processes the request
Required Headers for All Requests
Every API request must include:
http
Authorization: Bearer {access_token}
tenant-code: {your_tenant_code}
Content-Type: application/jsonOAuth2 Client Credentials Flow
Step 1: Obtain Client Credentials
Contact your system administrator to receive:
- Client ID: Your application's unique identifier
- Client Secret: Your application's secret key (keep confidential)
- Tenant Code: Your organization's tenant identifier
- Token Endpoint: Usually
https://builtin-crm.azurewebsites.net/oauth/token
Step 2: Request Access Token
Make a POST request to the token endpoint:
Request Format:
http
POST /oauth/token HTTP/1.1
Host: builtin-crm.azurewebsites.net
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRETSuccess Response:
json
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "api:read api:write"
}Step 3: Use Access Token
Include the access token in the Authorization header of all API requests:
http
GET /api/v1/people HTTP/1.1
Host: builtin-crm.azurewebsites.net
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
tenant-code: your-tenant-codeToken Management
Token Properties
- Lifetime: 1 hour (3600 seconds)
- Type: JWT (JSON Web Token)
- Scope: Defines allowed operations (read/write permissions)
- Issuer: Engagifii Authorization Server
Token Refresh Strategy
Since the Client Credentials flow doesn't support refresh tokens, you must request a new access token before the current one expires.
Recommended Approach:
javascript
class TokenManager {
constructor(clientId, clientSecret) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.token = null;
this.tokenExpiry = null;
}
async getValidToken() {
if (!this.token || this.isTokenExpired()) {
await this.refreshToken();
}
return this.token;
}
isTokenExpired() {
if (!this.tokenExpiry) return true;
// Refresh 5 minutes before actual expiry
return Date.now() >= (this.tokenExpiry - 300000);
}
async refreshToken() {
const response = await fetch('https://builtin-crm.azurewebsites.net/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
'grant_type': 'client_credentials',
'client_id': this.clientId,
'client_secret': this.clientSecret
})
});
const tokenData = await response.json();
this.token = tokenData.access_token;
this.tokenExpiry = Date.now() + (tokenData.expires_in * 1000);
}
}Token Storage Recommendations
Do:
- Store tokens in memory for short-lived applications
- Use secure credential storage services for long-running applications
- Encrypt tokens if persisting to disk or database
- Implement proper token rotation
Don't:
- Store tokens in client-side code or browsers
- Log tokens in application logs
- Commit tokens to version control
- Share tokens between different applications
Security Best Practices
1. Credential Security
- Never expose client secrets: Keep client credentials secure and never include them in client-side code
- Use environment variables: Store credentials in environment variables or secure configuration systems
- Implement credential rotation: Regularly rotate client secrets according to your security policy
- Restrict access: Limit who has access to client credentials within your organization
2. Token Security
- Use HTTPS only: Never send tokens over unencrypted connections
- Implement token expiry: Always respect token expiration times
- Secure token storage: Use appropriate security measures for token storage
- Monitor token usage: Log and monitor token usage for security auditing
3. Network Security
- Validate SSL certificates: Ensure your HTTP client validates SSL certificates
- Use certificate pinning: Consider implementing certificate pinning for additional security
- Implement timeouts: Set appropriate connection and request timeouts
- Retry logic: Implement exponential backoff for failed authentication requests
4. Error Handling
- Don't log sensitive data: Avoid logging tokens or credentials in error messages
- Implement secure error responses: Provide minimal error information to prevent information disclosure
- Monitor authentication failures: Track and alert on authentication failure patterns
- Implement rate limiting: Protect against brute force authentication attempts
Code Examples
JavaScript/Node.js
javascript
const axios = require('axios');
class EngagifiiAuth {
constructor(clientId, clientSecret, tenantCode) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.tenantCode = tenantCode;
this.baseURL = 'https://builtin-crm.azurewebsites.net';
this.token = null;
this.tokenExpiry = null;
}
async authenticate() {
try {
const response = await axios.post(`${this.baseURL}/oauth/token`,
new URLSearchParams({
'grant_type': 'client_credentials',
'client_id': this.clientId,
'client_secret': this.clientSecret
}), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
this.token = response.data.access_token;
this.tokenExpiry = Date.now() + (response.data.expires_in * 1000);
return this.token;
} catch (error) {
throw new Error(`Authentication failed: ${error.response?.data?.error || error.message}`);
}
}
async getAuthenticatedClient() {
if (!this.token || Date.now() >= (this.tokenExpiry - 300000)) {
await this.authenticate();
}
return axios.create({
baseURL: this.baseURL,
headers: {
'Authorization': `Bearer ${this.token}`,
'tenant-code': this.tenantCode,
'Content-Type': 'application/json'
}
});
}
}
// Usage
const auth = new EngagifiiAuth('your-client-id', 'your-client-secret', 'your-tenant-code');
const client = await auth.getAuthenticatedClient();
const response = await client.get('/api/v1/people');C#
csharp
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using System.Collections.Generic;
public class EngagifiiAuthClient
{
private readonly HttpClient _httpClient;
private readonly string _clientId;
private readonly string _clientSecret;
private readonly string _tenantCode;
private string _accessToken;
private DateTime _tokenExpiry;
public EngagifiiAuthClient(string clientId, string clientSecret, string tenantCode)
{
_httpClient = new HttpClient { BaseAddress = new Uri("https://builtin-crm.azurewebsites.net") };
_clientId = clientId;
_clientSecret = clientSecret;
_tenantCode = tenantCode;
}
public async Task<string> GetAccessTokenAsync()
{
if (string.IsNullOrEmpty(_accessToken) || DateTime.UtcNow >= _tokenExpiry.AddMinutes(-5))
{
await AuthenticateAsync();
}
return _accessToken;
}
private async Task AuthenticateAsync()
{
var tokenRequest = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("client_id", _clientId),
new KeyValuePair<string, string>("client_secret", _clientSecret)
});
var response = await _httpClient.PostAsync("/oauth/token", tokenRequest);
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
var tokenResponse = JsonSerializer.Deserialize<TokenResponse>(responseContent);
_accessToken = tokenResponse.AccessToken;
_tokenExpiry = DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn);
}
else
{
throw new HttpRequestException($"Authentication failed: {response.StatusCode}");
}
}
public async Task<HttpClient> GetAuthenticatedHttpClientAsync()
{
var token = await GetAccessTokenAsync();
var client = new HttpClient { BaseAddress = new Uri("https://builtin-crm.azurewebsites.net") };
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
client.DefaultRequestHeaders.Add("tenant-code", _tenantCode);
return client;
}
}
public class TokenResponse
{
[JsonPropertyName("access_token")]
public string AccessToken { get; set; }
[JsonPropertyName("token_type")]
public string TokenType { get; set; }
[JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; }
}Python
python
import requests
import time
from datetime import datetime, timedelta
class EngagifiiAuth:
def __init__(self, client_id, client_secret, tenant_code):
self.client_id = client_id
self.client_secret = client_secret
self.tenant_code = tenant_code
self.base_url = "https://builtin-crm.azurewebsites.net"
self.access_token = None
self.token_expiry = None
def authenticate(self):
token_url = f"{self.base_url}/oauth/token"
payload = {
'grant_type': 'client_credentials',
'client_id': self.client_id,
'client_secret': self.client_secret
}
response = requests.post(token_url, data=payload)
if response.status_code == 200:
token_data = response.json()
self.access_token = token_data['access_token']
expires_in = token_data['expires_in']
self.token_expiry = datetime.now() + timedelta(seconds=expires_in)
return self.access_token
else:
raise Exception(f"Authentication failed: {response.status_code} - {response.text}")
def get_valid_token(self):
if not self.access_token or datetime.now() >= (self.token_expiry - timedelta(minutes=5)):
self.authenticate()
return self.access_token
def get_authenticated_session(self):
token = self.get_valid_token()
session = requests.Session()
session.headers.update({
'Authorization': f'Bearer {token}',
'tenant-code': self.tenant_code,
'Content-Type': 'application/json'
})
return session
# Usage
auth = EngagifiiAuth('your-client-id', 'your-client-secret', 'your-tenant-code')
session = auth.get_authenticated_session()
response = session.get(f"{auth.base_url}/api/v1/people")Error Scenarios
Common Authentication Errors
1. Invalid Client Credentials
Status Code: 401 UnauthorizedResponse:
json
{
"error": "invalid_client",
"error_description": "Client authentication failed"
}Solution: Verify your Client ID and Client Secret are correct
2. Expired Access Token
Status Code: 401 UnauthorizedResponse:
json
{
"error": "invalid_token",
"error_description": "The access token has expired"
}Solution: Request a new access token
3. Missing Tenant Code
Status Code: 400 Bad RequestResponse:
json
{
"error": "missing_tenant_code",
"error_description": "The tenant-code header is required"
}Solution: Include the tenant-code header in your request
4. Invalid Tenant Code
Status Code: 403 ForbiddenResponse:
json
{
"error": "invalid_tenant",
"error_description": "The specified tenant code is not valid"
}Solution: Verify your tenant code with your system administrator
5. Token Format Error
Status Code: 401 UnauthorizedResponse:
json
{
"error": "invalid_token",
"error_description": "The access token format is invalid"
}Solution: Ensure you're including the "Bearer " prefix in the Authorization header
Error Handling Best Practices
javascript
async function makeAuthenticatedRequest(url, options = {}) {
try {
const client = await auth.getAuthenticatedClient();
const response = await client.request(url, options);
return response.data;
} catch (error) {
if (error.response?.status === 401) {
// Token expired or invalid, try to refresh
await auth.authenticate();
const client = await auth.getAuthenticatedClient();
const retryResponse = await client.request(url, options);
return retryResponse.data;
} else if (error.response?.status === 403) {
throw new Error('Access forbidden: Check your tenant code and permissions');
} else if (error.response?.status === 429) {
// Rate limited, implement backoff
await new Promise(resolve => setTimeout(resolve, 5000));
return makeAuthenticatedRequest(url, options);
} else {
throw error;
}
}
}Debugging Authentication Issues
- Check Network Connectivity: Verify you can reach the authentication endpoint
- Validate Credentials: Ensure Client ID and Secret are correctly configured
- Verify Token Format: Check that you're including "Bearer " prefix
- Test with Curl: Use curl to isolate issues from your application code
- Check Timestamps: Ensure your system clock is synchronized
- Review Logs: Examine HTTP request/response logs (without exposing tokens)
Next Steps: Once authentication is working, explore the API Reference to learn about available endpoints and their usage.
