Skip to content
Learni
View all tutorials
Sécurité & Identité

How to Secure an API with Microsoft Entra ID in 2026

Lire en français

Introduction

Microsoft Entra ID (formerly Azure Active Directory) is Microsoft’s cloud identity solution. In 2026, enterprises require centralized authentication, short-lived tokens, Conditional Access, and fine-grained permission management. This tutorial walks you through securing a Node.js/TypeScript API with Entra ID step by step. You will learn how to register an application, configure scopes, issue JWT tokens, and validate them server-side. Each step includes ready-to-use code and concrete explanations to help you avoid common production configuration mistakes.

Prerequisites

  • Azure account with Global Administrator or Application Administrator permissions
  • Node.js 20+ and TypeScript 5.4+
  • Solid knowledge of JWT and OAuth 2.0
  • Azure CLI installed (version 2.60+)

Create the Application in Entra ID

terminal
az login
az ad app create --display-name "MonAPI-Production" --sign-in-audience "AzureADMyOrg" --is-fallback-public-client false

This command creates the Entra ID application. Note the returned AppId and ObjectId. Always use AzureADMyOrg for enterprise environments and avoid uncontrolled multi-tenant applications.

Define Scopes and Permissions

app-manifest.json
{
  "id": "00000000-0000-0000-0000-000000000000",
  "appId": "votre-app-id",
  "displayName": "MonAPI-Production",
  "api": {
    "requestedAccessTokenVersion": 2,
    "oauth2PermissionScopes": [
      {
        "adminConsentDisplayName": "Accès complet à l'API",
        "adminConsentDescription": "Permet à l'application d'accéder à toutes les fonctionnalités de l'API",
        "userConsentDisplayName": "Accès à l'API",
        "userConsentDescription": "Permet à l'application d'accéder à vos données via l'API",
        "value": "access_as_user",
        "type": "User",
        "id": "11111111-1111-1111-1111-111111111111"
      }
    ]
  }
}

This JSON manifest configures the 'access_as_user' scope. Import it with az ad app update --id your-app-id --app-manifest. Token version 2 is required to support modern claims.

MSAL Client-Side Configuration

src/auth/msalConfig.ts
import { Configuration } from '@azure/msal-browser';

export const msalConfig: Configuration = {
  auth: {
    clientId: 'votre-app-id',
    authority: 'https://login.microsoftonline.com/votre-tenant-id',
    redirectUri: 'http://localhost:3000/auth/callback'
  },
  cache: { cacheLocation: 'sessionStorage', storeAuthStateInCookie: false }
};

This MSAL configuration uses the tenant-specific authority. Replace the IDs with your actual values. The sessionStorage cache limits token persistence.

Server-Side JWT Token Validation

src/middleware/auth.ts
import { jwtVerify } from 'jose';
import { createRemoteJWKSet } from 'jose';

const JWKS = createRemoteJWKSet(new URL('https://login.microsoftonline.com/votre-tenant-id/discovery/v2.0/keys'));

export async function validateToken(token: string) {
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: 'https://login.microsoftonline.com/votre-tenant-id/v2.0',
    audience: 'api://votre-app-id'
  });
  if (!payload.scp?.includes('access_as_user')) throw new Error('Scope insuffisant');
  return payload;
}

Strict validation with dynamic JWKS. Always verify the audience, issuer, and custom scope. This method rejects tokens issued for other applications.

Complete Express Middleware

src/server.ts
import express from 'express';
import { validateToken } from './middleware/auth';

const app = express();
app.use(express.json());
app.use(async (req, res, next) => {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) return res.status(401).send('Token manquant');
  try {
    req.user = await validateToken(authHeader.split(' ')[1]);
    next();
  } catch (e) {
    res.status(401).send('Token invalide');
  }
});
app.get('/api/protected', (req, res) => res.json({ message: 'Accès autorisé', user: req.user }));
app.listen(4000);

Express middleware that protects all routes. Validation errors return 401 without details to prevent information leakage.

Best Practices

  • Always use version 2 tokens and specific scopes instead of broad roles
  • Enable Conditional Access with MFA and location policies for critical applications
  • Store secrets in Azure Key Vault, not in code
  • Implement automatic key rotation via Entra ID
  • Log only the necessary claims for debugging

Common Errors to Avoid

  • Forgetting to set the exact audience in JWT validation
  • Using v1 tokens instead of v2 with custom scopes
  • Not configuring redirect URIs with HTTPS in production
  • Ignoring token revocation on logout

Further Reading

Discover our advanced training on identity governance and Entra ID integration with Zero Trust architectures at Learni Group.