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.