Appearance
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
