Skip to content

Authentication Guide ​

Table of Contents ​

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:

  1. Your application requests an access token using client credentials
  2. The authorization server validates credentials and issues a token
  3. Your application includes the token in subsequent API requests
  4. 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/json

OAuth2 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_SECRET

Success 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-code

Token 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 ​

  1. Check Network Connectivity: Verify you can reach the authentication endpoint
  2. Validate Credentials: Ensure Client ID and Secret are correctly configured
  3. Verify Token Format: Check that you're including "Bearer " prefix
  4. Test with Curl: Use curl to isolate issues from your application code
  5. Check Timestamps: Ensure your system clock is synchronized
  6. 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.