Appearance
Getting Started with Engagifii Identity API
Table of Contents
- Prerequisites
- Application Registration
- Authentication Setup
- Your First Integration
- Testing Your Integration
- Common Integration Patterns
- Troubleshooting
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
Recommended Knowledge
- 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:
- Application Name: Display name for your application
- Application Type:
- Web Application (server-side)
- Single Page Application (client-side)
- Mobile Application
- Service/API (machine-to-machine)
- Redirect URIs: Callback URLs for your application
- Logout URIs: Post-logout redirect URLs
- Required Scopes: Permissions your application needs
- 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-configurationResponse:
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=3600000Step 3: Install Required Libraries
Node.js/JavaScript
bash
npm install express express-session node-fetch
# Optional: OAuth libraries
npm install openid-client passport passport-openidconnectPython
bash
pip install flask authlib requests.NET Core
bash
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
dotnet add package Microsoft.IdentityModel.Protocols.OpenIdConnectYour 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 dotenv2. 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.jsTesting 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/callbackTesting 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=test123Step 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-secret2. 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.writeCommon 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:
- Check Network tab for OAuth redirects
- Inspect request/response headers
- Verify cookie settings
- Monitor console for errors
Getting Help
If you continue experiencing issues:
- Check Logs: Review application and Identity server logs
- Test with Postman: Isolate issues from application code
- Verify Configuration: Double-check all settings and credentials
- 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.
