MS ENTRA ID PROTECTED WEB API – JWT Token Validation with MSAL & Testing with Postman

Introduction:

This article provides a comprehensive, step-by-step guide for the API project – an ASP.NET Core 8.0 Web API that is protected by Microsoft EntraID (formerly Azure Active Directory) using the Microsoft Identity Web (MSAL) library. The API validates incoming JWT bearer tokens issued by Azure AD and enforces scope-based authorization before allowing access to protected endpoints.

Authentication Flow (Conceptual)

  1) The client application (e.g., a SPA, mobile app, or Postman) requests an access token from Microsoft Entra ID by authenticating with its Client ID and Client Secret / Certificate.

  2) Microsoft Entra ID validates the credentials and issues a JWT access token containing the requested scopes (e.g., “weather.read”).

  3) The client sends an HTTP request to the Web API with the access token in the Authorization header as a Bearer token.

  4) The ASP.NET Core middleware (Microsoft.Identity.Web) validates the JWT token – checking the signature, issuer, audience, and expiry.

  5) The authorization policy (“AuthZPolicy”) verifies that the token contains the required scope (“weather.read”).

  6) If validation succeeds, the API processes the request and returns the weather forecast data. Otherwise, it returns a 401 Unauthorized or 403 Forbidden response.

Pre-requistes

  – .NET 8.0 + SDK installed

  – Visual Studio 2022 (or VS Code with C# Dev Kit)

  – An App Registration created in the Entra ID Admin portal for the Web API

  – A separate App Registration for the client application (to obtain tokens)

  – Postman installed for API testing

NuGet Package Manager

  • Microsoft.AspNetCore.Authentication.JwtBearer
  • Microsoft.Identity.Web

MS Entra ID Configuration

The appsettings.json file contains the Azure AD configuration that

Microsoft.Identity.Web uses to validate incoming JWT tokens.

File: appsettings.json

  {
    "AzureAd": {
      "Instance": "https://login.microsoftonline.com/",
      "TenantId": "<your-tenant-id>",
      "ClientId": "<your-client-id>",
      "Scopes": "weather.read"
    },
    "AllowedHosts": "*"
  }

Configuration Property Explanation:

  Instance   -> The Azure AD authority URL base. For public Azure AD, this is always “https://login.microsoftonline.com/&#8221;.

  TenantId  -> The Directory (Tenant) ID of your Azure AD tenant. This identifies which tenant issued the token. The middleware validates that the token’s “tid” claim matches this value, ensuring tokens from other tenants are rejected.             

  ClientId   -> The Application (Client) ID of the Web API app registration. This is used as the expected “audience” (aud claim) in the JWT token. Only tokens intended for this specific API will be   

  Scopes    ->The required scope(s) that must be present in the token’s   “scp” claim. In this project, “weather.read” is required. The authorization policy references this configuration key to enforce scope validation

The TenantId and ClientId values shown above are specific to this project’s Azure AD app registration. ECS team will provide those values

Authentication & Authorization Middleware

Program.cs is the application entry point where the authentication and authorization middleware is configured. This is where MSAL-based JWT token validation is set up.

Code block explanation

Adding Authentication with MSAL

  builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddMicrosoftIdentityWebApi(builder.Configuration.GetSection(“AzureAd”));

  This is the core MSAL integration code. Here is what happens under the hood:

  1) AddAuthentication(JwtBearerDefaults.AuthenticationScheme) registers the authentication services and sets “Bearer” as the default scheme. This means all [Authorize] attributes will expect a JWT Bearer token by default.

  2) AddMicrosoftIdentityWebApi() reads the “AzureAd” configuration section and configures the JWT Bearer handler with the following validation

     parameters:

     – Authority: Constructed from Instance + TenantId

       (e.g., “https://login.microsoftonline.com/0496c54d-…/v2.0&#8221;)

     – Audience: Set to the ClientId value (“7627c0e7-…”)

     – Issuer: Validated against the Azure AD metadata endpoint

     – Signing Keys: Automatically downloaded from the Azure AD JWKS endpoint

       (https://login.microsoftonline.com/{tenantId}/discovery/v2.0/keys)

     – Token Lifetime: Validated to ensure the token has not expired

     – Token Type: Must be an access token (not an ID token)

Adding Authorization with Scope Policy

builder.Services.AddAuthorization(config =>
  {
      config.AddPolicy("AuthZPolicy", policyBuilder =>
          policyBuilder.Requirements.Add(
              new ScopeAuthorizationRequirement()
              {
                  RequiredScopesConfigurationKey = "AzureAd:Scopes"
              }));
  });

This code creates a named authorization policy called “AuthZPolicy” that:

  1) Creates a ScopeAuthorizationRequirement – a class from Microsoft.Identity.Web that checks the “scp” (scope) claim in the JWT token.

  2) The RequiredScopesConfigurationKey property points to “AzureAd:Scopes” in appsettings.json, which resolves to “weather.read”.

  3) When a request hits a controller decorated with

     [Authorize(Policy = “AuthZPolicy”)], the middleware checks that the JWT

     token’s “scp” claim contains “weather.read”. If the scope is missing, a 403 Forbidden response is returned

Middleware Pipeline Order (CRITICAL)

  • app.UseAuthentication();
  •   app.UseHttpsRedirection();
  •   app.UseAuthorization();
  •   app.MapControllers();

The order of middleware is critical:

1) UseAuthentication() must come BEFORE UseAuthorization(). It reads the

     Bearer token from the Authorization header, validates it using the MSAL-configured handler, and populates HttpContext.User with the claims principal.

  2) UseHttpsRedirection() redirects HTTP requests to HTTPS for security.

  3) UseAuthorization() evaluates the authorization policies (including “AuthZPolicy”) against the authenticated user’s claims.

  4) MapControllers() maps the controller endpoints to the routing pipeline.

Protecting the API Controller

The WeatherForecastController is the protected API endpoint. It uses the

[Authorize] attribute with the “AuthZPolicy” policy to enforce both

authentication and scope-based authorization.

Code block explanation

[Authorize(Policy = “AuthZPolicy”)]

This attribute is applied at the controller level, meaning every action in this controller requires:

    1) A valid JWT Bearer token in the Authorization header (authentication).

    2) The token must contain the “weather.read” scope (authorization via AuthZPolicy).

  If no token is provided     –> API returns 401 Unauthorized

  If the token lacks the scope    –> API returns 403 Forbidden

How JWT Token Validation Works

When a client sends a request to the protected API, the following validation steps occur in sequence:

Step 1: Token Extraction

The JwtBearer middleware extracts the token from the HTTP Authorization header. The header must be in the format:

    Authorization: Bearer <token>

If the header is missing or malformed, a 401 Unauthorized response is returned immediately.

Step 2: Token Parsing & Signature Validation

The middleware parses the JWT token (which is a Base64URL-encoded string with three parts: Header, Payload, and Signature). It then downloads the Azure AD public signing keys from the JWKS (JSON Web Key Set) endpoint and verifies the token’s digital signature using RSA. This ensures the token was actually issued by Microsoft Entra ID and has not been tampered with.

Step 3: Issuer Validation

The middleware validates the “iss” (issuer) claim in the token. It must match the expected issuer URL:

https://login.microsoftonline.com/{TenantId}/v2.0

This prevents tokens issued by other identity providers from being accepted.

Step 4: Audience Validation

The middleware validates the “aud” (audience) claim. It must match the ClientId configured in appsettings.json (7627c0e7-8633-49a2-a92a-cd6912aa7b2b). This ensures the token was intended for this specific API and not some other application.

Step 5: Token Lifetime Validation

The middleware checks the “exp” (expiration) and “nbf” (not before) claims to ensure the token is currently valid and has not expired. Azure AD tokens Typically have a lifetime of 60-90 minutes.

Step 6: Scope Authorization

After the token passes all validation checks, the authorization middleware evaluates the “AuthZPolicy” policy. The ScopeAuthorizationRequirement checks that the “scp” claim in the token contains “weather.read”. If the scope is not present, a 403 Forbidden response is returned.

Step 7: Request Processing

Only after ALL validation steps pass does the request reach the controller’s action. The authenticated user’s claims are available via HttpContext.User throughout the request pipeline.

Testing the API with POSTMAN

This section explains how to obtain a JWT access token from Azure AD and use it to call the protected WeatherForecast API using Postman.

Obtain an Access Token

 Using Postman’s Built-in OAuth 2.0 (Recommended)

  1) Open Postman and create a new HTTP request.

  2) Go to the “Authorization” tab.

  3) Set the “Auth Type” to “OAuth 2.0”.

  4) Click “Configure New Token” and fill in the following details:

  • Token Name        EntraID-Token (or any descriptive name)                     
  • Grant Type         Authorization Code (with PKCE) or Client Credentials        
  • Callback URL       Turn on Authorize using browser.

Add the callback URL as a redirect URI of your client app registered in MS Entra ID App registration

  • Auth URL          https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize 
  • Access Token URL   https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token         
  • Client ID                       <Your Client App’s Client ID>                               
  • Client Secret              <Your Client App’s Client Secret>                                  
  • Scope                            api://{appid}/weather.read   

Click “Get New Access Token”. Postman will redirect you to the Microsoft login page (for Authorization Code flow)

After authentication, the access token will appear in Postman. Click “Use Token”.

Call the Protected API

  1) Create a new GET request in Postman.

  2) Set the URL to:

https://localhost:7100/WeatherForecast

  3) Go to the “Authorization” tab:

     – If you used Option A (OAuth 2.0), the token is already attached.

       Make sure “Bearer Token” is selected and the token is populated.

     – If you used Option B (manual), select “Bearer Token” as the auth

       type and paste the access_token value you copied.

  4) Alternatively, you can set the header manually:

         Key:   Authorization

         Value: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs…

  5) Click “Send”.

Summary:

This article explains how to secure a .NET Web API using Microsoft Entra ID by implementing JSON Web Token validation. It walks through the complete setup, including prerequisites, project structure, required NuGet packages, and Entra ID configuration in appsettings.json. The document demonstrates how to configure authentication and authorization middleware in the application, protect API controllers, and understand the end-to-end JWT token validation flow. It also shows how to obtain an access token using the Microsoft Authentication Library (MSAL) and test secured API endpoints in Postman, helping developers verify that only authenticated requests can access protected resources.

Check my repo

gowthamk91

Leave a Reply

Discover more from Gowtham K

Subscribe now to keep reading and get access to the full archive.

Continue reading