Hello! Let's dive into the next crucial step in securing your Azure applications.
In the last lesson, you successfully configured your client-side .NET application to sign in a user and acquire an access token for your web API. This was a major milestone! However, your API is still open to the public; it doesn't yet check for that token.
Today, we'll complete the security loop. This lesson addresses the learning outcome: Secure a .NET web API to require a valid access token from Microsoft Entra ID. You will configure your ASP.NET Core web API to inspect every incoming request, validate the JSON Web Token (JWT), and reject any call that doesn't present a valid token with the correct permissions.
1. The API Security Middleware
When a client application sends a request to your API, it includes the access token in the Authorization HTTP header, formatted as a "Bearer" token.
GET /api/games HTTP/1.1
Host: your-api.azurewebsites.net
Authorization: Bearer eyJ0eXAi...a_very_long_token_string...
Your API needs to intercept this request and perform several critical validation checks on the token before it ever reaches your business logic. This is handled by authentication middleware. For ASP.NET Core applications integrating with Microsoft Entra ID, the recommended library is Microsoft.Identity.Web.
This middleware, once configured, automatically performs the validation steps outlined in the OAuth 2.0 and OpenID Connect standards.
To understand the theory behind this, you can refer to the Microsoft documentation on token validation.
Configure protected web API apps - Microsoft identity platform
The document 'Configure protected web API apps' explains the token validation process. It details the checks that the middleware performs to ensure the token is authentic and intended for your API.
Read the section titled 'Token validation'. Focus on the list of checks performed: Audience, Issuer, Expiry, and Signature. This will give you a solid conceptual understanding of what's happening under the hood.
The key checks are:
- Signature: Verifies the token was signed by the private key of the identity provider (your Microsoft Entra ID tenant) and hasn't been tampered with.
- Audience (
aud): Confirms the token was issued for your specific API. This prevents a token meant for another API (e.g., Microsoft Graph) from being used to access yours. - Issuer (
iss): Confirms the token was issued by your trusted tenant. - Lifetime (
exp): Ensures the token has not expired.
2. Hands-On: Securing Your Web API
Now, let's implement this in your API project. We will follow a complete video guide that walks through each step, from configuration to testing. Given your experience with C# and .NET, the process should feel familiar.
The following video provides a comprehensive walkthrough of securing an ASP.NET Core web API.
Secure ASP.NET Web API with Microsoft Entra ID or Azure MSAL
This video, 'Secure ASP.NET Web API with Microsoft Entra ID', will be our primary guide for this hands-on section. It covers everything from project setup to endpoint protection and testing with Postman.
Watch from 05:21 to 13:56. We will break this down into steps below, but this segment covers the entire implementation process. Follow along with your own web API project.
Let's break down the process shown in the video into clear, actionable steps.
Step 1: Install Microsoft.Identity.Web
First, ensure the necessary library is added to your web API project. This package contains all the logic for validating tokens from the Microsoft Identity Platform.
dotnet add package Microsoft.Identity.Web
Step 2: Configure appsettings.json
Just as you did for your client app, you need to connect your API to its app registration in Microsoft Entra ID. Open appsettings.json in your API project and add an AzureAd section.
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "[Enter your Tenant (Directory) ID]",
"ClientId": "[Enter the Application (client) ID of your Web API registration]"
},
//... other settings
}
ClientId: This is the Application (client) ID of the API's app registration, not the client's. This is how the middleware identifies which application it is.TenantId: Your directory ID.
The video covers this from 06:35 to 08:22.
Step 3: Add Authentication and Authorization Services
In your Program.cs file, you need to configure the authentication and authorization services.
- Add Authentication: Use the
AddMicrosoftIdentityWebApiextension method. This is the "magic" that reads yourappsettings.jsonconfiguration and sets up the JWT Bearer token handler with all the correct validation parameters (audience, issuer, etc.). - Use Middleware: Add
app.UseAuthentication()andapp.UseAuthorization()to the HTTP request pipeline. The order is important:UseAuthenticationmust come beforeUseAuthorization.
Your Program.cs should look something like this:
// using Microsoft.AspNetCore.Authentication.JwtBearer;
// using Microsoft.Identity.Web;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// This adds the authentication services and configures the JWT bearer handler.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddControllers();
// ... other services
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
// Add the authentication and authorization middleware to the pipeline.
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
This part is covered in the video from 08:43 to 11:08.
Step 4: Protect Your Endpoints
With the middleware in place, the final step is to "decorate" your controller actions with attributes that enforce security.
-
[Authorize]: Add this attribute at the controller level. This is the first line of defense; it ensures that a request must have a valid, authenticated identity (a valid token) to access any endpoint in this controller. -
[RequiredScope(...)]: This attribute, provided byMicrosoft.Identity.Web, enforces authorization. It checks if the validated token contains the specific permission scope required for that action. This is where you use the scopes you defined in the API's app registration (e.g.,Games.Read).
Here is an example of a secured controller:
// using Microsoft.AspNetCore.Authorization;
// using Microsoft.Identity.Web.Resource;
[ApiController]
[Route("api/[controller]")]
[Authorize] // 1. Requires authentication for all actions in this controller.
public class GamesController : ControllerBase
{
// ... constructor and other code ...
private static readonly string[] scopeRequiredByApi = new string[] { "Games.Read" };
[HttpGet]
// 2. Requires the specific "Games.Read" scope for this action.
[RequiredScope(scopeRequiredByApi)]
public IEnumerable<Game> Get()
{
// ... logic to return games
}
}
If a client calls this endpoint with a valid token that lacks the Games.Read scope, the middleware will reject the request with a 403 Forbidden status code. If they call it with no token, they will get a 401 Unauthorized.
This is demonstrated in the video from 12:00 to 13:56.
3. Testing the Full Flow
You now have a secured API. You can test it in two ways:
Option A: Using Your Client Application
This is the ideal test, as it validates the entire flow you've built across the last two lessons.
- Run both your API and your client web application projects.
- Navigate to the page in your client app that calls the API.
- Since your client is already configured to acquire a token with the correct scope, the call should now succeed.
Option B: Using Postman (for Isolated Testing)
As an experienced developer, you know the value of tools like Postman for testing APIs in isolation. The video provides an excellent, detailed guide on how to configure Postman to get a token from Microsoft Entra ID and call your secured API. This is a very valuable skill for debugging.
Secure ASP.NET Web API with Microsoft Entra ID or Azure MSAL
Watch this section to learn how to configure Postman's OAuth 2.0 helper to acquire a token for your API. This is a great way to test your API independently of your client application.
Watch from 14:42 to 24:31. The video explains how to set up the Authorization Code flow in Postman, including configuring the callback URL, auth/token endpoints, client ID, client secret, and scope.
To reinforce the concepts, try these two scenarios:
- Call the endpoint from Postman without a token. You should receive a
401 Unauthorizedresponse. - Follow the video to get a token, but deliberately remove the required scope from the "Scope" field in Postman. When you use this token, you should receive a
403 Forbiddenresponse. The video demonstrates this exact scenario from27:10to28:43.
Conclusion
You have now fully implemented a standard, secure, token-based authentication and authorization flow. Your API is no longer open to the public; it demands credentials, validates them, and checks for the necessary permissions before granting access.
Key Takeaways:
- The
Microsoft.Identity.WebNuGet package greatly simplifies securing an ASP.NET Core API. - The
AddMicrosoftIdentityWebApi()method configures the authentication middleware to validate JWTs based on settings inappsettings.json. - The
[Authorize]attribute enforces authentication (Is the user who they say they are? Is the token valid?). - The
[RequiredScope]attribute enforces authorization (Does this authenticated user have permission to perform this action?). - The
aud(audience) claim is critical. It ensures a token intended for one API cannot be used to access another.
Next Steps
In this and previous lessons, you've placed sensitive information like Client IDs, Tenant IDs, and even client secrets directly in appsettings.json or Postman. This is fine for local development but is a significant security risk in production.
In our next lesson, we will address this by exploring Azure's dedicated service for secrets management. You will learn how to describe the purpose of Azure Key Vault for managing secrets, keys, and certificates, which is the professional standard for handling application secrets securely.