Skip to content

Authentication Guide

Table of Contents

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 TypeUse CaseClient Type
Authorization CodeWeb applications with server backendConfidential
Authorization Code + PKCESPAs and mobile applicationsPublic
Client CredentialsService-to-service communicationConfidential
Refresh TokenLong-lived sessionsBoth

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_value

Example 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=abc123

Step 2: User Authentication

The user will:

  1. Be presented with a login page
  2. Enter credentials
  3. Optionally consent to requested scopes
  4. 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=abc123

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

Response:

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=S256

Step 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_VERIFIER

Client 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.write

Response:

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_SECRET

Response:

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_VALIDATE

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

Session 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.net

Logout

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-complete

Example:

https://engagifii-identity-live.azurewebsites.net/connect/endsession?
  id_token_hint=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...&
  post_logout_redirect_uri=https://yourapp.com/logout-complete

Security 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

  1. Enable Logging: Log OAuth requests/responses (excluding sensitive data)
  2. Check Discovery Document: Verify endpoints via /.well-known/openid-configuration
  3. Validate Redirect URIs: Ensure exact match with registered URIs
  4. Test with Postman: Use our collection to isolate issues
  5. 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.