Back to home
dc

Daily Chronicle

Backend Development

Implementing Microsoft Entra SSO in Node.js Applications

Practical using Microsoft Entra SSO in the Node.js project

November 16, 2025

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:

  1. Go to Azure Portal → Microsoft Entra ID
  2. Navigate to "App registrations" → "New registration"
  3. Configure redirect URI (e.g., http://localhost:3000/auth/callback)
  4. Generate a client secret
  5. 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.