Skip to content

Getting Started with Engagifii Identity API

Table of Contents

Prerequisites

Before you begin integrating with the Engagifii Identity API, ensure you have:

Required Access

  • Client Credentials: Client ID and Client Secret from your system administrator
  • Application Details: Your application's redirect URIs and allowed scopes
  • Base URL: The correct Identity endpoint for your environment
  • Network Access: Ability to make HTTPS requests to Identity endpoints

Development Tools

  • HTTP Client: curl, Postman, or your preferred HTTP testing tool
  • Development Environment: Node.js, .NET, Python, or your preferred platform
  • HTTPS Support: Your application must support secure HTTPS connections
  • Local Development: Tools like ngrok for testing OAuth callbacks locally
  • OAuth 2.0: Basic understanding of authorization flows
  • OpenID Connect: Knowledge of identity tokens and claims
  • JWT: Understanding of JSON Web Tokens
  • HTTP/REST: Familiarity with REST API concepts

Application Registration

Before implementing authentication, your application must be registered with Engagifii Identity.

Registration Requirements

Contact your system administrator with the following information:

  1. Application Name: Display name for your application
  2. Application Type:
    • Web Application (server-side)
    • Single Page Application (client-side)
    • Mobile Application
    • Service/API (machine-to-machine)
  3. Redirect URIs: Callback URLs for your application
  4. Logout URIs: Post-logout redirect URLs
  5. Required Scopes: Permissions your application needs
  6. Grant Types: OAuth flows your application will use

Client Configuration Examples

Web Application

json
{
  "clientId": "webapp-example",
  "clientName": "Example Web Application",
  "grantTypes": ["authorization_code", "refresh_token"],
  "redirectUris": [
    "https://app.example.com/callback",
    "https://localhost:3000/callback"
  ],
  "postLogoutRedirectUris": [
    "https://app.example.com/logout-complete"
  ],
  "scopes": ["openid", "profile", "email", "api"],
  "requireClientSecret": true,
  "requirePkce": false
}

Single Page Application

json
{
  "clientId": "spa-example",
  "clientName": "Example SPA",
  "grantTypes": ["authorization_code"],
  "redirectUris": [
    "https://spa.example.com/callback",
    "http://localhost:3000/callback"
  ],
  "scopes": ["openid", "profile", "api"],
  "requireClientSecret": false,
  "requirePkce": true
}

Service API

json
{
  "clientId": "service-example",
  "clientName": "Example Service",
  "grantTypes": ["client_credentials"],
  "scopes": ["api.read", "api.write"],
  "requireClientSecret": true
}

Authentication Setup

Step 1: Discovery Configuration

Retrieve the OpenID Connect discovery document to get all endpoints:

bash
curl https://engagifii-identity-live.azurewebsites.net/.well-known/openid-configuration

Response:

json
{
  "issuer": "https://engagifii-identity-live.azurewebsites.net",
  "authorization_endpoint": "https://engagifii-identity-live.azurewebsites.net/connect/authorize",
  "token_endpoint": "https://engagifii-identity-live.azurewebsites.net/connect/token",
  "userinfo_endpoint": "https://engagifii-identity-live.azurewebsites.net/connect/userinfo",
  "end_session_endpoint": "https://engagifii-identity-live.azurewebsites.net/connect/endsession",
  "jwks_uri": "https://engagifii-identity-live.azurewebsites.net/.well-known/openid-configuration/jwks",
  "response_types_supported": ["code", "token", "id_token", "code id_token", "code token", "id_token token", "code id_token token"],
  "grant_types_supported": ["authorization_code", "client_credentials", "refresh_token"],
  "scopes_supported": ["openid", "profile", "email", "api"],
  "code_challenge_methods_supported": ["S256"]
}

Step 2: Environment Configuration

Set up your environment variables:

.env file

bash
# Identity Configuration
IDENTITY_BASE_URL=https://engagifii-identity-live.azurewebsites.net
CLIENT_ID=your-client-id
CLIENT_SECRET=your-client-secret
REDIRECT_URI=https://yourapp.com/callback
POST_LOGOUT_REDIRECT_URI=https://yourapp.com
SCOPES=openid profile email api

# Session Configuration
SESSION_SECRET=your-secure-session-secret
SESSION_TIMEOUT=3600000

Step 3: Install Required Libraries

Node.js/JavaScript

bash
npm install express express-session node-fetch
# Optional: OAuth libraries
npm install openid-client passport passport-openidconnect

Python

bash
pip install flask authlib requests

.NET Core

bash
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
dotnet add package Microsoft.IdentityModel.Protocols.OpenIdConnect

Your First Integration

Let's build a simple integration step by step.

Example: Express.js Web Application

1. Project Setup

bash
mkdir engagifii-auth-demo
cd engagifii-auth-demo
npm init -y
npm install express express-session node-fetch dotenv

2. Basic Application Structure

app.js:

javascript
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const fetch = require('node-fetch');

const app = express();
const port = process.env.PORT || 3000;

// Session configuration
app.use(session({
  secret: process.env.SESSION_SECRET || 'dev-secret-change-in-production',
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    maxAge: parseInt(process.env.SESSION_TIMEOUT) || 3600000
  }
}));

// Identity configuration
const identityConfig = {
  baseUrl: process.env.IDENTITY_BASE_URL || 'https://engagifii-identity-live.azurewebsites.net',
  clientId: process.env.CLIENT_ID,
  clientSecret: process.env.CLIENT_SECRET,
  redirectUri: process.env.REDIRECT_URI || `http://localhost:${port}/callback`,
  postLogoutRedirectUri: process.env.POST_LOGOUT_REDIRECT_URI || `http://localhost:${port}`,
  scopes: process.env.SCOPES || 'openid profile email'
};

// Routes
app.get('/', (req, res) => {
  if (req.session.user) {
    res.send(`
      <h1>Welcome ${req.session.user.name || req.session.user.email}!</h1>
      <p>You are logged in.</p>
      <a href="/profile">View Profile</a> | 
      <a href="/logout">Logout</a>
    `);
  } else {
    res.send(`
      <h1>Engagifii Identity Demo</h1>
      <p>You are not logged in.</p>
      <a href="/login">Login</a>
    `);
  }
});

// Login route
app.get('/login', (req, res) => {
  const state = generateRandomString();
  req.session.state = state;
  
  const authUrl = new URL(`${identityConfig.baseUrl}/connect/authorize`);
  authUrl.searchParams.append('client_id', identityConfig.clientId);
  authUrl.searchParams.append('response_type', 'code');
  authUrl.searchParams.append('redirect_uri', identityConfig.redirectUri);
  authUrl.searchParams.append('scope', identityConfig.scopes);
  authUrl.searchParams.append('state', state);
  
  res.redirect(authUrl.toString());
});

// Callback route
app.get('/callback', async (req, res) => {
  const { code, state, error, error_description } = req.query;
  
  // Handle errors
  if (error) {
    console.error('OAuth error:', error, error_description);
    return res.status(400).send(`Authentication failed: ${error_description || error}`);
  }
  
  // Validate state
  if (state !== req.session.state) {
    return res.status(400).send('Invalid state parameter');
  }
  
  // Validate code
  if (!code) {
    return res.status(400).send('Missing authorization code');
  }
  
  try {
    // Exchange code for tokens
    const tokenResponse = await fetch(`${identityConfig.baseUrl}/connect/token`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        code: code,
        redirect_uri: identityConfig.redirectUri,
        client_id: identityConfig.clientId,
        client_secret: identityConfig.clientSecret
      })
    });
    
    if (!tokenResponse.ok) {
      const error = await tokenResponse.text();
      throw new Error(`Token exchange failed: ${error}`);
    }
    
    const tokens = await tokenResponse.json();
    
    // Store tokens
    req.session.tokens = {
      access_token: tokens.access_token,
      id_token: tokens.id_token,
      refresh_token: tokens.refresh_token,
      expires_at: Date.now() + (tokens.expires_in * 1000)
    };
    
    // Get user info
    const userResponse = await fetch(`${identityConfig.baseUrl}/connect/userinfo`, {
      headers: {
        'Authorization': `Bearer ${tokens.access_token}`
      }
    });
    
    if (userResponse.ok) {
      const user = await userResponse.json();
      req.session.user = user;
    }
    
    // Clear state
    delete req.session.state;
    
    res.redirect('/');
  } catch (error) {
    console.error('Authentication error:', error);
    res.status(500).send('Authentication failed');
  }
});

// Profile route (protected)
app.get('/profile', requireAuth, (req, res) => {
  res.json({
    user: req.session.user,
    tokenExpiry: new Date(req.session.tokens.expires_at).toISOString()
  });
});

// Logout route
app.get('/logout', (req, res) => {
  const idToken = req.session.tokens?.id_token;
  
  // Clear session
  req.session.destroy((err) => {
    if (err) {
      console.error('Session destruction error:', err);
    }
    
    // Redirect to Identity logout
    if (idToken) {
      const logoutUrl = new URL(`${identityConfig.baseUrl}/connect/endsession`);
      logoutUrl.searchParams.append('id_token_hint', idToken);
      logoutUrl.searchParams.append('post_logout_redirect_uri', identityConfig.postLogoutRedirectUri);
      res.redirect(logoutUrl.toString());
    } else {
      res.redirect('/');
    }
  });
});

// Middleware
function requireAuth(req, res, next) {
  if (!req.session.tokens?.access_token) {
    return res.redirect('/login');
  }
  
  // Check token expiry
  if (Date.now() >= req.session.tokens.expires_at) {
    // Token expired, could refresh here or re-authenticate
    return res.redirect('/login');
  }
  
  next();
}

// Utility functions
function generateRandomString(length = 32) {
  const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let text = '';
  for (let i = 0; i < length; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
}

// Start server
app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
  console.log('Identity Config:', {
    baseUrl: identityConfig.baseUrl,
    clientId: identityConfig.clientId,
    redirectUri: identityConfig.redirectUri
  });
});

3. Run the Application

bash
# Create .env file with your credentials
echo "CLIENT_ID=your-client-id" > .env
echo "CLIENT_SECRET=your-client-secret" >> .env

# Run the application
node app.js

Testing Your Integration

Local Development with ngrok

For local OAuth development, use ngrok to create HTTPS tunnels:

bash
# Install ngrok
npm install -g ngrok

# Start your application
node app.js

# In another terminal, create tunnel
ngrok http 3000

# Update your redirect URI to the ngrok URL
# Example: https://abc123.ngrok.io/callback

Testing Checklist

Authentication Flow

  • [ ] User can initiate login
  • [ ] Authorization redirect works correctly
  • [ ] Callback receives authorization code
  • [ ] Token exchange succeeds
  • [ ] User information is retrieved
  • [ ] Tokens are stored securely

Session Management

  • [ ] User session persists across requests
  • [ ] Protected routes require authentication
  • [ ] Token expiry is handled
  • [ ] Refresh tokens work (if applicable)

Logout Flow

  • [ ] Local session is cleared
  • [ ] Identity session is terminated
  • [ ] Post-logout redirect works
  • [ ] User cannot access protected routes after logout

Using Postman for Testing

1. Authorization Code Flow Test

Step 1: Get Authorization Code

GET https://engagifii-identity-live.azurewebsites.net/connect/authorize
  ?client_id=your-client-id
  &response_type=code
  &redirect_uri=https://oauth.pstmn.io/v1/callback
  &scope=openid profile
  &state=test123

Step 2: Exchange Code for Tokens

POST https://engagifii-identity-live.azurewebsites.net/connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=RECEIVED_CODE
&redirect_uri=https://oauth.pstmn.io/v1/callback
&client_id=your-client-id
&client_secret=your-client-secret

2. Client Credentials Flow Test

POST https://engagifii-identity-live.azurewebsites.net/connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=service-client-id
&client_secret=service-client-secret
&scope=api.read api.write

Common Integration Patterns

1. Token Refresh Pattern

javascript
async function getValidToken(session) {
  // Check if token exists and is not expired
  if (session.tokens?.access_token && Date.now() < session.tokens.expires_at) {
    return session.tokens.access_token;
  }
  
  // Try to refresh token
  if (session.tokens?.refresh_token) {
    try {
      const response = await fetch(`${identityConfig.baseUrl}/connect/token`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: new URLSearchParams({
          grant_type: 'refresh_token',
          refresh_token: session.tokens.refresh_token,
          client_id: identityConfig.clientId,
          client_secret: identityConfig.clientSecret
        })
      });
      
      if (response.ok) {
        const tokens = await response.json();
        session.tokens = {
          access_token: tokens.access_token,
          id_token: tokens.id_token,
          refresh_token: tokens.refresh_token || session.tokens.refresh_token,
          expires_at: Date.now() + (tokens.expires_in * 1000)
        };
        return tokens.access_token;
      }
    } catch (error) {
      console.error('Token refresh failed:', error);
    }
  }
  
  // No valid token available
  return null;
}

2. API Call Pattern

javascript
async function callProtectedAPI(endpoint, session) {
  const token = await getValidToken(session);
  
  if (!token) {
    throw new Error('No valid authentication token');
  }
  
  const response = await fetch(endpoint, {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    }
  });
  
  if (response.status === 401) {
    // Token might be invalid, clear session
    session.tokens = null;
    throw new Error('Authentication required');
  }
  
  return response.json();
}

3. Silent Authentication Pattern

javascript
// For SPAs - Silent token renewal in iframe
function silentRenew() {
  return new Promise((resolve, reject) => {
    const iframe = document.createElement('iframe');
    iframe.style.display = 'none';
    
    const params = new URLSearchParams({
      client_id: CLIENT_ID,
      response_type: 'code',
      redirect_uri: SILENT_REDIRECT_URI,
      scope: SCOPES,
      prompt: 'none' // Silent authentication
    });
    
    iframe.src = `${IDENTITY_URL}/connect/authorize?${params}`;
    
    window.addEventListener('message', function handler(e) {
      if (e.origin !== IDENTITY_URL) return;
      
      window.removeEventListener('message', handler);
      document.body.removeChild(iframe);
      
      if (e.data.code) {
        // Exchange code for tokens
        exchangeCodeForTokens(e.data.code).then(resolve).catch(reject);
      } else {
        reject(new Error('Silent authentication failed'));
      }
    });
    
    document.body.appendChild(iframe);
  });
}

Troubleshooting

Common Issues and Solutions

1. "Invalid redirect_uri" Error

Problem: OAuth callback fails with redirect URI mismatch Solutions:

  • Ensure redirect URI exactly matches registered URI (including trailing slashes)
  • Check for HTTP vs HTTPS mismatch
  • Verify port numbers match
  • URL decode/encode special characters properly

2. "Invalid client" Error

Problem: Client authentication fails Solutions:

  • Verify client ID and secret are correct
  • Check client secret hasn't expired or been rotated
  • Ensure client is enabled in Identity configuration
  • Verify grant type is allowed for client

3. CORS Issues (SPA)

Problem: Cross-origin requests blocked Solutions:

  • Ensure your origin is whitelisted in Identity CORS settings
  • Use Authorization Code flow with PKCE for SPAs
  • Implement proper CORS headers in your application

4. Token Expiry Handling

Problem: Users get logged out unexpectedly Solutions:

  • Implement token refresh logic before expiry
  • Use refresh tokens for long-lived sessions
  • Show warning before session expires
  • Implement silent authentication for SPAs

5. Session Loss

Problem: User session not persisting Solutions:

  • Ensure session middleware is configured correctly
  • Check cookie settings (secure, httpOnly, sameSite)
  • Verify session store is working (Redis, database)
  • Check for session timeout settings

Debugging Tips

Enable Detailed Logging

javascript
// Log all OAuth transactions
if (process.env.NODE_ENV === 'development') {
  console.log('OAuth Request:', {
    endpoint: authUrl.toString(),
    params: authUrl.searchParams.toString()
  });
  
  console.log('Token Response:', {
    status: tokenResponse.status,
    headers: tokenResponse.headers,
    body: await tokenResponse.text()
  });
}

Validate JWT Tokens

javascript
// Decode and inspect JWT tokens
function decodeJWT(token) {
  const parts = token.split('.');
  if (parts.length !== 3) {
    throw new Error('Invalid JWT format');
  }
  
  const header = JSON.parse(Buffer.from(parts[0], 'base64').toString());
  const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
  
  console.log('JWT Header:', header);
  console.log('JWT Payload:', payload);
  console.log('Token Expiry:', new Date(payload.exp * 1000));
  
  return { header, payload };
}

Network Inspection

Use browser developer tools or proxy tools:

  1. Check Network tab for OAuth redirects
  2. Inspect request/response headers
  3. Verify cookie settings
  4. Monitor console for errors

Getting Help

If you continue experiencing issues:

  1. Check Logs: Review application and Identity server logs
  2. Test with Postman: Isolate issues from application code
  3. Verify Configuration: Double-check all settings and credentials
  4. Contact Support: Provide specific error messages and logs

Next Steps: Once your integration is working, explore the API Reference for detailed endpoint documentation and advanced features.