Appearance
Authentication Guide
Table of Contents
- Overview
- Authentication Flows
- Authorization Code Flow
- Client Credentials Flow
- Token Management
- Session Management
- Security Best Practices
- Code Examples
- Error Scenarios
Overview
The Engagifii Identity API implements OAuth 2.0 and OpenID Connect protocols to provide secure authentication and authorization. This guide covers all supported authentication flows and best practices for implementation.
Key Security Features
- OAuth 2.0 Compliance: Full RFC 6749 implementation
- OpenID Connect: Identity layer with ID tokens
- JWT Tokens: Self-contained, cryptographically signed tokens
- PKCE Support: Enhanced security for public clients
- Multi-tenant Isolation: Organization-specific access controls
- Token Introspection: Validate and inspect token claims
- Session Management: Secure SSO with logout capabilities
Authentication Flows
Supported Grant Types
| Grant Type | Use Case | Client Type |
|---|---|---|
| Authorization Code | Web applications with server backend | Confidential |
| Authorization Code + PKCE | SPAs and mobile applications | Public |
| Client Credentials | Service-to-service communication | Confidential |
| Refresh Token | Long-lived sessions | Both |
Choosing the Right Flow
- Web Applications: Use Authorization Code flow with server-side token storage
- Single Page Applications (SPAs): Use Authorization Code with PKCE
- Mobile Applications: Use Authorization Code with PKCE
- Service APIs: Use Client Credentials flow
- Microservices: Use Client Credentials flow
Authorization Code Flow
The Authorization Code flow is the most common OAuth 2.0 flow, suitable for web applications with a backend server.
Step 1: Authorization Request
Redirect the user to the authorization endpoint:
http
GET /connect/authorize HTTP/1.1
Host: engagifii-identity-live.azurewebsites.net
Parameters:
client_id=YOUR_CLIENT_ID
response_type=code
redirect_uri=https://yourapp.com/callback
scope=openid profile email
state=random_state_valueExample URL:
https://engagifii-identity-live.azurewebsites.net/connect/authorize?
client_id=webapp&
response_type=code&
redirect_uri=https://yourapp.com/callback&
scope=openid profile email&
state=abc123Step 2: User Authentication
The user will:
- Be presented with a login page
- Enter credentials
- Optionally consent to requested scopes
- Be redirected back to your application
Step 3: Authorization Code Receipt
After successful authentication, the user is redirected to your callback URL:
https://yourapp.com/callback?
code=AUTHORIZATION_CODE&
state=abc123Step 4: Token Exchange
Exchange the authorization code for tokens:
http
POST /connect/token HTTP/1.1
Host: engagifii-identity-live.azurewebsites.net
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=https://yourapp.com/callback&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRETResponse:
json
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "def50200a7f9f5d3...",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"scope": "openid profile email"
}Authorization Code with PKCE
For public clients (SPAs, mobile apps), use PKCE for enhanced security:
Step 1: Generate PKCE Challenge
javascript
// Generate code verifier
const codeVerifier = generateRandomString(128);
// Generate code challenge
const codeChallenge = base64UrlEncode(sha256(codeVerifier));Step 2: Authorization Request with PKCE
https://engagifii-identity-live.azurewebsites.net/connect/authorize?
client_id=spa_app&
response_type=code&
redirect_uri=https://yourapp.com/callback&
scope=openid profile&
state=abc123&
code_challenge=GENERATED_CHALLENGE&
code_challenge_method=S256Step 3: Token Exchange with PKCE
http
POST /connect/token HTTP/1.1
Host: engagifii-identity-live.azurewebsites.net
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=https://yourapp.com/callback&
client_id=spa_app&
code_verifier=ORIGINAL_CODE_VERIFIERClient Credentials Flow
The Client Credentials flow is used for service-to-service authentication where no user interaction is required.
Request Access Token
http
POST /connect/token HTTP/1.1
Host: engagifii-identity-live.azurewebsites.net
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=service_client&
client_secret=YOUR_CLIENT_SECRET&
scope=api.read api.writeResponse:
json
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "api.read api.write"
}Using the Access Token
Include the token in API requests:
http
GET /api/resource HTTP/1.1
Host: api.engagifii.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...Token Management
Token Types
Access Token
- Purpose: Grants access to protected resources
- Format: JWT (JSON Web Token)
- Lifetime: 1 hour (3600 seconds)
- Usage: Include in Authorization header
ID Token
- Purpose: Contains user identity information
- Format: JWT with user claims
- Lifetime: 1 hour
- Usage: Client-side user information
Refresh Token
- Purpose: Obtain new access tokens
- Format: Opaque string
- Lifetime: 30 days (configurable)
- Usage: Exchange for new tokens
Token Refresh
Use refresh tokens to obtain new access tokens without user interaction:
http
POST /connect/token HTTP/1.1
Host: engagifii-identity-live.azurewebsites.net
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&
refresh_token=YOUR_REFRESH_TOKEN&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRETResponse:
json
{
"access_token": "new_access_token...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "new_refresh_token...",
"id_token": "new_id_token..."
}Token Validation
Local Validation (JWT)
javascript
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
const client = jwksClient({
jwksUri: 'https://engagifii-identity-live.azurewebsites.net/.well-known/openid-configuration/jwks'
});
function getKey(header, callback){
client.getSigningKey(header.kid, function(err, key) {
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
jwt.verify(token, getKey, options, function(err, decoded) {
if (err) {
console.error('Token validation failed:', err);
} else {
console.log('Token is valid:', decoded);
}
});Remote Validation (Introspection)
http
POST /connect/introspect HTTP/1.1
Host: engagifii-identity-live.azurewebsites.net
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
token=ACCESS_TOKEN_TO_VALIDATEToken Revocation
Revoke tokens when they're no longer needed:
http
POST /connect/revocation HTTP/1.1
Host: engagifii-identity-live.azurewebsites.net
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
token=TOKEN_TO_REVOKE&
token_type_hint=access_tokenSession Management
Single Sign-On (SSO)
Users authenticated with Engagifii Identity can access all connected applications without re-entering credentials.
Check Session Status
http
GET /connect/checksession HTTP/1.1
Host: engagifii-identity-live.azurewebsites.netLogout
Initiate Logout
http
GET /connect/endsession HTTP/1.1
Host: engagifii-identity-live.azurewebsites.net
Parameters:
id_token_hint=USER_ID_TOKEN
post_logout_redirect_uri=https://yourapp.com/logout-completeExample:
https://engagifii-identity-live.azurewebsites.net/connect/endsession?
id_token_hint=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...&
post_logout_redirect_uri=https://yourapp.com/logout-completeSecurity Best Practices
1. Client Credentials Security
- Never expose secrets: Keep client secrets server-side only
- Use environment variables: Store credentials securely
- Rotate secrets regularly: Implement periodic credential rotation
- Restrict access: Limit credential access to authorized personnel
2. Token Security
- Use HTTPS only: Never transmit tokens over unencrypted connections
- Secure storage: Store tokens in httpOnly cookies or secure server storage
- Validate tokens: Always validate tokens before trusting claims
- Implement expiry: Respect token expiration times
3. PKCE Implementation
- Always use PKCE for public clients: SPAs and mobile apps must use PKCE
- Generate secure verifiers: Use cryptographically secure random generators
- Store verifiers securely: Keep code verifiers in secure client storage
4. Scope Management
- Request minimal scopes: Only request necessary permissions
- Validate scope usage: Ensure tokens have required scopes
- Document scope requirements: Clearly specify needed scopes
Code Examples
JavaScript/Node.js (Express)
javascript
const express = require('express');
const session = require('express-session');
const fetch = require('node-fetch');
const app = express();
// Configure session
app.use(session({
secret: 'your-session-secret',
resave: false,
saveUninitialized: true,
}));
const config = {
authorizationUri: 'https://engagifii-identity-live.azurewebsites.net/connect/authorize',
tokenUri: 'https://engagifii-identity-live.azurewebsites.net/connect/token',
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
redirectUri: 'https://yourapp.com/callback',
scope: 'openid profile email'
};
// Initiate login
app.get('/login', (req, res) => {
const authUrl = `${config.authorizationUri}?` +
`client_id=${config.clientId}&` +
`scope=${encodeURIComponent(config.scope)}&` +
`redirect_uri=${encodeURIComponent(config.redirectUri)}&` +
`response_type=code&` +
`state=${generateState()}`;
res.redirect(authUrl);
});
// Handle callback
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
if (!code) {
return res.status(400).send('Missing authorization code');
}
try {
// Exchange code for tokens
const tokenResponse = await fetch(config.tokenUri, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: config.redirectUri,
client_id: config.clientId,
client_secret: config.clientSecret
})
});
const tokens = await tokenResponse.json();
// Store tokens in session
req.session.tokens = tokens;
// Get user info
const userResponse = await fetch('https://engagifii-identity-live.azurewebsites.net/connect/userinfo', {
headers: {
'Authorization': `Bearer ${tokens.access_token}`
}
});
const user = await userResponse.json();
req.session.user = user;
res.redirect('/dashboard');
} catch (error) {
console.error('Token exchange failed:', error);
res.status(500).send('Authentication failed');
}
});
// Logout
app.get('/logout', (req, res) => {
const idToken = req.session.tokens?.id_token;
req.session.destroy(() => {
const logoutUrl = `https://engagifii-identity-live.azurewebsites.net/connect/endsession?` +
`id_token_hint=${idToken || ''}&` +
`post_logout_redirect_uri=${encodeURIComponent('https://yourapp.com')}`;
res.redirect(logoutUrl);
});
});
// Protected route
app.get('/api/protected', requireAuth, async (req, res) => {
// Access token available in req.session.tokens.access_token
res.json({ message: 'Protected resource', user: req.session.user });
});
function requireAuth(req, res, next) {
if (!req.session.tokens?.access_token) {
return res.redirect('/login');
}
// Check token expiry
const tokenExpiry = req.session.tokens.expires_at;
if (Date.now() >= tokenExpiry) {
// Token expired, refresh or re-authenticate
return res.redirect('/login');
}
next();
}
function generateState() {
return Math.random().toString(36).substring(7);
}C# (.NET Core)
csharp
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://engagifii-identity-live.azurewebsites.net";
options.ClientId = "your-client-id";
options.ClientSecret = "your-client-secret";
options.ResponseType = "code";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
});
}
// Controller
[Authorize]
public class SecureController : Controller
{
public async Task<IActionResult> Index()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var idToken = await HttpContext.GetTokenAsync("id_token");
// Use tokens for API calls
return View();
}
public IActionResult Logout()
{
return SignOut("Cookies", "oidc");
}
}Python (Flask)
python
from flask import Flask, redirect, url_for, session, request
from authlib.integrations.flask_client import OAuth
import os
app = Flask(__name__)
app.secret_key = os.urandom(24)
oauth = OAuth(app)
oauth.register(
name='engagifii',
client_id='your-client-id',
client_secret='your-client-secret',
server_metadata_url='https://engagifii-identity-live.azurewebsites.net/.well-known/openid-configuration',
client_kwargs={
'scope': 'openid profile email'
}
)
@app.route('/login')
def login():
redirect_uri = url_for('callback', _external=True)
return oauth.engagifii.authorize_redirect(redirect_uri)
@app.route('/callback')
def callback():
token = oauth.engagifii.authorize_access_token()
user = oauth.engagifii.parse_id_token(token)
session['user'] = user
session['token'] = token
return redirect('/dashboard')
@app.route('/logout')
def logout():
session.clear()
return redirect('https://engagifii-identity-live.azurewebsites.net/connect/endsession')
@app.route('/protected')
def protected():
if 'token' not in session:
return redirect('/login')
# Use session['token']['access_token'] for API calls
return 'Protected content'Error Scenarios
Common OAuth Errors
Invalid Client
Error: invalid_client
json
{
"error": "invalid_client",
"error_description": "Client authentication failed"
}Solution: Verify client_id and client_secret
Invalid Grant
Error: invalid_grant
json
{
"error": "invalid_grant",
"error_description": "The provided authorization code is invalid or has expired"
}Solution: Ensure code is used within expiry time and only once
Invalid Scope
Error: invalid_scope
json
{
"error": "invalid_scope",
"error_description": "The requested scope is invalid, unknown, or malformed"
}Solution: Request only configured scopes for your client
Unauthorized Client
Error: unauthorized_client
json
{
"error": "unauthorized_client",
"error_description": "The client is not authorized to use this grant type"
}Solution: Ensure client is configured for the requested grant type
Token Errors
Expired Token
Status: 401 Unauthorized
json
{
"error": "invalid_token",
"error_description": "The access token has expired"
}Solution: Use refresh token to obtain new access token
Invalid Token Format
Status: 401 Unauthorized
json
{
"error": "invalid_token",
"error_description": "The access token format is invalid"
}Solution: Include "Bearer " prefix in Authorization header
Troubleshooting Tips
- Enable Logging: Log OAuth requests/responses (excluding sensitive data)
- Check Discovery Document: Verify endpoints via
/.well-known/openid-configuration - Validate Redirect URIs: Ensure exact match with registered URIs
- Test with Postman: Use our collection to isolate issues
- Verify Clock Sync: Ensure server time is synchronized (for JWT validation)
Next Steps: Proceed to the Getting Started Guide for step-by-step integration instructions.
