Skip to content

Nodejs Express

This example demonstrates how to integrate with the Engagifii API using JavaScript.

Code Example

javascript
/**
 * Engagifii Identity Integration Example
 * Platform: Node.js with Express
 * 
 * This example demonstrates OAuth 2.0 Authorization Code flow
 * with session management and token refresh.
 * 
 * Prerequisites:
 * - npm install express express-session node-fetch dotenv
 * - Configure .env file with your credentials
 */

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

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

// ===========================================
// Configuration
// ===========================================

const config = {
  // Identity Server URLs (use appropriate environment)
  identityUrl: process.env.IDENTITY_URL || 'https://engagifii-identity-live.azurewebsites.net',
  
  // Client Configuration
  clientId: process.env.CLIENT_ID,
  clientSecret: process.env.CLIENT_SECRET,
  
  // Application URLs
  redirectUri: process.env.REDIRECT_URI || `http://localhost:${port}/callback`,
  postLogoutRedirectUri: process.env.POST_LOGOUT_URI || `http://localhost:${port}`,
  
  // OAuth Scopes
  scopes: process.env.SCOPES || 'openid profile email api',
  
  // Session Configuration
  sessionSecret: process.env.SESSION_SECRET || crypto.randomBytes(32).toString('hex')
};

// Validate configuration
if (!config.clientId || !config.clientSecret) {
  console.error('ERROR: Missing CLIENT_ID or CLIENT_SECRET in environment variables');
  process.exit(1);
}

// ===========================================
// Middleware Configuration
// ===========================================

// Session middleware
app.use(session({
  secret: config.sessionSecret,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production', // HTTPS in production
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000 // 24 hours
  }
}));

// JSON body parser
app.use(express.json());

// ===========================================
// Authentication Utilities
// ===========================================

/**
 * Generate random string for state parameter
 */
function generateRandomString(length = 32) {
  return crypto.randomBytes(length).toString('base64url');
}

/**
 * Build authorization URL
 */
function buildAuthorizationUrl(state) {
  const params = new URLSearchParams({
    client_id: config.clientId,
    response_type: 'code',
    redirect_uri: config.redirectUri,
    scope: config.scopes,
    state: state,
    nonce: generateRandomString(16) // For OpenID Connect
  });
  
  return `${config.identityUrl}/connect/authorize?${params}`;
}

/**
 * Exchange authorization code for tokens
 */
async function exchangeCodeForTokens(code) {
  const tokenEndpoint = `${config.identityUrl}/connect/token`;
  
  const params = new URLSearchParams({
    grant_type: 'authorization_code',
    code: code,
    redirect_uri: config.redirectUri,
    client_id: config.clientId,
    client_secret: config.clientSecret
  });
  
  const response = await fetch(tokenEndpoint, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: params
  });
  
  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Token exchange failed: ${error}`);
  }
  
  return await response.json();
}

/**
 * Refresh access token
 */
async function refreshAccessToken(refreshToken) {
  const tokenEndpoint = `${config.identityUrl}/connect/token`;
  
  const params = new URLSearchParams({
    grant_type: 'refresh_token',
    refresh_token: refreshToken,
    client_id: config.clientId,
    client_secret: config.clientSecret
  });
  
  const response = await fetch(tokenEndpoint, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: params
  });
  
  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Token refresh failed: ${error}`);
  }
  
  return await response.json();
}

/**
 * Get user information from Identity server
 */
async function getUserInfo(accessToken) {
  const userInfoEndpoint = `${config.identityUrl}/connect/userinfo`;
  
  const response = await fetch(userInfoEndpoint, {
    headers: {
      'Authorization': `Bearer ${accessToken}`
    }
  });
  
  if (!response.ok) {
    throw new Error(`Failed to get user info: ${response.statusText}`);
  }
  
  return await response.json();
}

/**
 * Check if token is expired
 */
function isTokenExpired(session) {
  if (!session.tokens?.expires_at) {
    return true;
  }
  
  // Check if token expires in next 5 minutes
  const expiresAt = session.tokens.expires_at;
  const now = Date.now();
  const fiveMinutes = 5 * 60 * 1000;
  
  return now >= (expiresAt - fiveMinutes);
}

/**
 * Middleware to require authentication
 */
async function requireAuth(req, res, next) {
  // Check if user has valid session
  if (!req.session.tokens?.access_token) {
    return res.redirect('/login');
  }
  
  // Check if token needs refresh
  if (isTokenExpired(req.session)) {
    try {
      if (req.session.tokens.refresh_token) {
        console.log('Token expired, refreshing...');
        const newTokens = await refreshAccessToken(req.session.tokens.refresh_token);
        
        req.session.tokens = {
          access_token: newTokens.access_token,
          id_token: newTokens.id_token,
          refresh_token: newTokens.refresh_token || req.session.tokens.refresh_token,
          expires_at: Date.now() + (newTokens.expires_in * 1000)
        };
        
        console.log('Token refreshed successfully');
      } else {
        // No refresh token, need to re-authenticate
        return res.redirect('/login');
      }
    } catch (error) {
      console.error('Token refresh failed:', error);
      return res.redirect('/login');
    }
  }
  
  next();
}

// ===========================================
// Routes
// ===========================================

/**
 * Home page
 */
app.get('/', (req, res) => {
  const isAuthenticated = !!req.session.user;
  
  res.send(`
    <!DOCTYPE html>
    <html>
    <head>
      <title>Engagifii Identity Integration</title>
      <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        .user-info { background: #f0f0f0; padding: 20px; border-radius: 5px; }
        button { padding: 10px 20px; margin: 10px 0; cursor: pointer; }
      </style>
    </head>
    <body>
      <h1>Engagifii Identity Integration Example</h1>
      
      ${isAuthenticated ? `
        <div class="user-info">
          <h2>Welcome, ${req.session.user.name || req.session.user.email}!</h2>
          <p><strong>Email:</strong> ${req.session.user.email}</p>
          <p><strong>Subject:</strong> ${req.session.user.sub}</p>
          <p><strong>Session expires:</strong> ${new Date(req.session.tokens.expires_at).toLocaleString()}</p>
        </div>
        <button onclick="location.href='/profile'">View Full Profile</button>
        <button onclick="location.href='/api/test'">Test API Call</button>
        <button onclick="location.href='/logout'">Logout</button>
      ` : `
        <p>You are not logged in.</p>
        <button onclick="location.href='/login'">Login with Engagifii</button>
      `}
      
      <hr>
      <p><small>Client ID: ${config.clientId}</small></p>
      <p><small>Identity URL: ${config.identityUrl}</small></p>
    </body>
    </html>
  `);
});

/**
 * Initiate login
 */
app.get('/login', (req, res) => {
  // Generate and store state for CSRF protection
  const state = generateRandomString();
  req.session.oauthState = state;
  
  // Redirect to authorization endpoint
  const authUrl = buildAuthorizationUrl(state);
  console.log('Redirecting to:', authUrl);
  
  res.redirect(authUrl);
});

/**
 * OAuth callback
 */
app.get('/callback', async (req, res) => {
  const { code, state, error, error_description } = req.query;
  
  // Handle OAuth errors
  if (error) {
    console.error('OAuth error:', error, error_description);
    return res.status(400).send(`
      <h1>Authentication Failed</h1>
      <p>Error: ${error}</p>
      <p>${error_description}</p>
      <a href="/">Go Home</a>
    `);
  }
  
  // Validate state parameter
  if (!state || state !== req.session.oauthState) {
    console.error('Invalid state parameter');
    return res.status(400).send('Invalid state parameter');
  }
  
  // Clear stored state
  delete req.session.oauthState;
  
  // Check for authorization code
  if (!code) {
    return res.status(400).send('Missing authorization code');
  }
  
  try {
    // Exchange code for tokens
    console.log('Exchanging authorization code for tokens...');
    const tokens = await exchangeCodeForTokens(code);
    
    // Store tokens in session
    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 information
    console.log('Getting user information...');
    const userInfo = await getUserInfo(tokens.access_token);
    req.session.user = userInfo;
    
    console.log('Authentication successful for user:', userInfo.email || userInfo.sub);
    
    // Redirect to home page
    res.redirect('/');
  } catch (error) {
    console.error('Authentication error:', error);
    res.status(500).send(`
      <h1>Authentication Failed</h1>
      <p>${error.message}</p>
      <a href="/">Go Home</a>
    `);
  }
});

/**
 * User profile (protected)
 */
app.get('/profile', requireAuth, async (req, res) => {
  try {
    // Get fresh user info
    const userInfo = await getUserInfo(req.session.tokens.access_token);
    
    res.json({
      user: userInfo,
      token_expires_at: new Date(req.session.tokens.expires_at).toISOString(),
      has_refresh_token: !!req.session.tokens.refresh_token,
      scopes: config.scopes
    });
  } catch (error) {
    console.error('Error getting profile:', error);
    res.status(500).json({ error: 'Failed to get user profile' });
  }
});

/**
 * Test API call (protected)
 */
app.get('/api/test', requireAuth, async (req, res) => {
  // This would be a call to your actual API
  // Using the access token for authorization
  
  res.json({
    message: 'This is a protected API endpoint',
    user: req.session.user.email,
    token_valid: !isTokenExpired(req.session)
  });
});

/**
 * Logout
 */
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 server logout
    if (idToken) {
      const logoutParams = new URLSearchParams({
        id_token_hint: idToken,
        post_logout_redirect_uri: config.postLogoutRedirectUri
      });
      
      const logoutUrl = `${config.identityUrl}/connect/endsession?${logoutParams}`;
      console.log('Redirecting to logout:', logoutUrl);
      res.redirect(logoutUrl);
    } else {
      res.redirect('/');
    }
  });
});

/**
 * Logout callback
 */
app.get('/logout-callback', (req, res) => {
  res.send(`
    <h1>Logged Out</h1>
    <p>You have been successfully logged out.</p>
    <a href="/">Go Home</a>
  `);
});

// ===========================================
// Error Handling
// ===========================================

app.use((err, req, res, next) => {
  console.error('Application error:', err);
  res.status(500).send('Internal Server Error');
});

// ===========================================
// Start Server
// ===========================================

app.listen(port, () => {
  console.log('===========================================');
  console.log('Engagifii Identity Integration Example');
  console.log('===========================================');
  console.log(`Server running at: http://localhost:${port}`);
  console.log(`Identity Server: ${config.identityUrl}`);
  console.log(`Client ID: ${config.clientId}`);
  console.log(`Redirect URI: ${config.redirectUri}`);
  console.log('===========================================');
  console.log('Environment Variables Required:');
  console.log('- CLIENT_ID: Your client identifier');
  console.log('- CLIENT_SECRET: Your client secret');
  console.log('- IDENTITY_URL: Identity server URL (optional)');
  console.log('- REDIRECT_URI: OAuth callback URL (optional)');
  console.log('===========================================');
});

// Handle graceful shutdown
process.on('SIGTERM', () => {
  console.log('SIGTERM received, shutting down gracefully');
  process.exit(0);
});

module.exports = app;

Usage Notes

  • Make sure to replace placeholder values with your actual API credentials
  • Install required dependencies before running this code
  • Refer to the main API documentation for detailed endpoint information

Download

Download this example: nodejs-express.js