Implementing Microsoft Entra SSO in Node.js Applications
Practical using Microsoft Entra SSO in the Node.js project
Implementing Microsoft Entra SSO in Node.js Applications
Why SSO Matters in Modern Applications
Building and maintaining a custom authentication system is complex, time-consuming, and risky. You need to handle password hashing, storage, reset mechanisms, security patches, and compliance requirements. Each of these areas requires specialized knowledge and ongoing maintenance.
In enterprise environments, this complexity multiplies. Organizations need centralized control over user accounts—who has access, when they can access it, and from where. With custom authentication systems, you're responsible for implementing features like multi-factor authentication, password policies, account lockouts, and audit logs from scratch.
Single Sign-On (SSO) solves these challenges by delegating authentication to a centralized identity provider. For organizations, this means complete control over user accounts across all applications. IT administrators can instantly revoke access when an employee leaves, enforce security policies company-wide, and monitor authentication attempts from a single dashboard.
For developers, SSO eliminates the burden of building and securing authentication infrastructure. Instead of reinventing the wheel with custom login forms, password recovery flows, and security updates, you leverage battle-tested systems built by security experts. You can focus on your application's core business logic while the identity provider handles the security-critical authentication layer.
Microsoft Entra SSO is particularly valuable because it integrates seamlessly with existing Microsoft ecosystems that many organizations already use—Office 365, Teams, SharePoint, and more. This means users authenticate once in the morning and access all their tools without repeated logins.
What is Microsoft Entra SSO?
Microsoft Entra (formerly Azure Active Directory) is Microsoft's cloud-based identity and access management service. It provides SSO capabilities that integrate seamlessly with thousands of applications.
Key Benefits of Using Microsoft Entra SSO
Enhanced Security Features
- Multi-factor authentication (MFA) support out of the box
- Centralized security monitoring across all applications
- Conditional access policies (location-based, device-based, risk-based access)
- And many more enterprise-grade security features managed by Microsoft
Organizational Control
- Centralized user provisioning and deprovisioning
- Instant access revocation across all integrated applications
- Group-based access management
- Self-service capabilities reducing IT workload
Developer Benefits
- No need to build authentication from scratch
- Regular security updates handled by Microsoft
- Comprehensive documentation and support
- Compliance certifications included
Architecture Overview
When integrating Microsoft Entra SSO with a Node.js application, the typical architecture follows the OAuth 2.0 and OpenID Connect protocols.
High-Level Authentication Flow
Here's how the authentication process works:
sequenceDiagram
participant User
participant NodeApp as Node.js Application
participant Entra as Microsoft Entra
participant Graph as Microsoft Graph API
User->>NodeApp: Access protected resource
NodeApp->>NodeApp: Check session
NodeApp->>User: Redirect to login
User->>Entra: Click login, redirected
Entra->>User: Show Microsoft login page
User->>Entra: Enter credentials (+ MFA if required)
Entra->>Entra: Validate credentials
Entra->>NodeApp: Redirect with authorization code
NodeApp->>Entra: Exchange code for tokens
Entra->>NodeApp: Return access token & ID token
NodeApp->>NodeApp: Validate tokens & create session
NodeApp->>User: Grant access to protected resource
Note over User,NodeApp: User is now authenticated
User->>NodeApp: Request user profile
NodeApp->>Graph: Call Graph API with access token
Graph->>NodeApp: Return user data
NodeApp->>User: Display user information
Components
Microsoft Entra Application Registration
- Client ID and Client Secret for your application
- Redirect URIs (where Microsoft sends users after authentication)
- API permissions (what data your app can access)
- Token configuration
Node.js Application
- Authentication middleware to protect routes
- Token validation logic
- Session management
- Protected routes and resources
Implementation with Node.js
Let me walk you through a practical implementation using Express.js and the Microsoft Authentication Library (MSAL).
Prerequisites
First, you need to register your application in Microsoft Entra. Follow the official Microsoft documentation to:
- Go to Azure Portal → Microsoft Entra ID
- Navigate to "App registrations" → "New registration"
- Configure redirect URI (e.g.,
http://localhost:3000/auth/callback) - Generate a client secret
- Note down your Client ID, Tenant ID, and Client Secret
For detailed guidance, see Microsoft's app registration guide.
Installation
npm install express express-session @azure/msal-node dotenv
The @azure/msal-node package is Microsoft's official authentication library for Node.js. See the MSAL Node documentation for more details.
Environment Configuration
Create a .env file:
CLIENT_ID=your-client-id
CLIENT_SECRET=your-client-secret
TENANT_ID=your-tenant-id
REDIRECT_URI=http://localhost:3000/auth/callback
SESSION_SECRET=your-session-secret
MSAL Configuration
// config/authConfig.js
require('dotenv').config();
const msalConfig = {
auth: {
clientId: process.env.CLIENT_ID,
authority: `https://login.microsoftonline.com/${process.env.TENANT_ID}`,
clientSecret: process.env.CLIENT_SECRET,
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: 'Info',
},
},
};
const REDIRECT_URI = process.env.REDIRECT_URI;
const POST_LOGOUT_REDIRECT_URI = 'http://localhost:3000';
module.exports = {
msalConfig,
REDIRECT_URI,
POST_LOGOUT_REDIRECT_URI,
};
Learn more about MSAL configuration options.
Authentication Controller
// controllers/authController.js
const msal = require('@azure/msal-node');
const { msalConfig, REDIRECT_URI, POST_LOGOUT_REDIRECT_URI } = require('../config/authConfig');
const msalInstance = new msal.ConfidentialClientApplication(msalConfig);
const authController = {
// Initiate login
login: async (req, res) => {
const authCodeUrlParameters = {
scopes: ['user.read'],
redirectUri: REDIRECT_URI,
};
try {
const authCodeUrl = await msalInstance.getAuthCodeUrl(authCodeUrlParameters);
res.redirect(authCodeUrl);
} catch (error) {
console.error('Error generating auth URL:', error);
res.status(500).send('Authentication error');
}
},
// Handle callback from Microsoft Entra
callback: async (req, res) => {
const tokenRequest = {
code: req.query.code,
scopes: ['user.read'],
redirectUri: REDIRECT_URI,
};
try {
const response = await msalInstance.acquireTokenByCode(tokenRequest);
// Store user information in session
req.session.user = {
username: response.account.username,
name: response.account.name,
homeAccountId: response.account.homeAccountId,
};
req.session.accessToken = response.accessToken;
res.redirect('/dashboard');
} catch (error) {
console.error('Error acquiring token:', error);
res.status(500).send('Authentication failed');
}
},
// Logout
logout: (req, res) => {
const logoutUri = `https://login.microsoftonline.com/${process.env.TENANT_ID}/oauth2/v2.0/logout?post_logout_redirect_uri=${POST_LOGOUT_REDIRECT_URI}`;
req.session.destroy(() => {
res.redirect(logoutUri);
});
},
};
module.exports = authController;
For more information on handling authentication flows, check out Microsoft's authentication flow documentation.
Authentication Middleware
// middleware/authMiddleware.js
const isAuthenticated = (req, res, next) => {
if (req.session.user) {
return next();
}
res.redirect('/auth/login');
};
module.exports = { isAuthenticated };
Express Application Setup
// app.js
const express = require('express');
const session = require('express-session');
const authController = require('./controllers/authController');
const { isAuthenticated } = require('./middleware/authMiddleware');
const app = express();
// Session configuration
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // 24 hours
},
})
);
// Authentication routes
app.get('/auth/login', authController.login);
app.get('/auth/callback', authController.callback);
app.get('/auth/logout', authController.logout);
// Protected routes
app.get('/dashboard', isAuthenticated, (req, res) => {
res.json({
message: 'Welcome to your dashboard',
user: req.session.user,
});
});
// Public route
app.get('/', (req, res) => {
res.send('Home Page - <a href="/auth/login">Login</a>');
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
Calling Microsoft Graph API
Once authenticated, you can use the access token to call Microsoft Graph API to retrieve user information and other data. See the Microsoft Graph API documentation for available endpoints.
// services/graphService.js
const axios = require('axios');
const graphService = {
getUserProfile: async (accessToken) => {
try {
const response = await axios.get('https://graph.microsoft.com/v1.0/me', {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return response.data;
} catch (error) {
console.error('Error fetching user profile:', error);
throw error;
}
},
getUserPhoto: async (accessToken) => {
try {
const response = await axios.get('https://graph.microsoft.com/v1.0/me/photo/$value', {
headers: {
Authorization: `Bearer ${accessToken}`,
},
responseType: 'arraybuffer',
});
return Buffer.from(response.data, 'binary').toString('base64');
} catch (error) {
console.error('Error fetching user photo:', error);
return null;
}
},
};
module.exports = graphService;
Explore more Graph API capabilities for accessing user data, calendars, emails, and more.
Using Graph Service in Routes
// Update dashboard route
const graphService = require('./services/graphService');
app.get('/dashboard', isAuthenticated, async (req, res) => {
try {
const profile = await graphService.getUserProfile(req.session.accessToken);
res.json({
message: 'Welcome to your dashboard',
user: req.session.user,
profile: profile,
});
} catch (error) {
res.status(500).json({ error: 'Failed to fetch profile' });
}
});
Best Practices
Token Management
- Store tokens securely (never in localStorage for sensitive apps)
- Implement token refresh logic using MSAL's token caching
- Use short-lived access tokens
- Consider using Redis for session storage in production
Security Considerations
- Always use HTTPS in production
- Implement CSRF protection
- Validate tokens on every request
- Set secure cookie flags
- Implement proper error handling without exposing sensitive information
Performance Optimization
- Cache user profiles when appropriate
- Use connection pooling for database operations
- Implement rate limiting
- Consider using a distributed cache for sessions
Error Handling
- Provide meaningful error messages
- Log errors for monitoring
- Implement retry logic for transient failures
- Handle token expiration gracefully
For production deployment considerations, review Microsoft's best practices guide.
Conclusion
Implementing Microsoft Entra SSO in your Node.js application eliminates the complexity and risk of building custom authentication. Organizations gain centralized control over user accounts, enhanced security features, and compliance benefits—all managed by Microsoft's identity platform.
For developers, this means less time wrestling with authentication code and more time building features that matter to your users. The MSAL library handles the OAuth complexity, allowing you to integrate enterprise-grade authentication in hours rather than weeks.
Start with the basic implementation shown here, then enhance it based on your specific requirements such as role-based access control, multi-tenant support, or custom claims. The Microsoft Entra platform grows with your needs, from simple authentication to sophisticated identity management scenarios.
Remember to thoroughly test your authentication flow, especially the token refresh mechanism and error scenarios, before deploying to production.