Implementing Authorization in .NET APIs Using Azure Entra ID

Somebody who likes to code
Search for a command to run...

Somebody who likes to code
No comments yet. Be the first to comment.
Google processes tens of thousands of code changes every day across a codebase of hundreds of millions of lines. The secret isn't magic tooling — it's a documented, principled approach to code review

Amazon Simple Notification Service (SNS) looks straightforward on the surface — publish a message, deliver it to subscribers — but the gap between a topic that "works in dev" and one that is productio

Amazon Simple Queue Service (SQS) is deceptively simple to get started with, but surprisingly easy to misconfigure in ways that cause silent message loss, runaway costs, duplicate processing storms, o

HTTP API is the right default choice for the majority of .NET serverless workloads. At 70% less cost than REST API, lower latency, and native JWT authorization, it covers most production requirements

AWS Lambda has become a cornerstone of modern serverless architectures, but writing a function that "just works" is very different from writing one that is performant, cost-effective, secure, and read

Authorization is a critical aspect of securing APIs, ensuring that only authenticated users with the right permissions can access specific resources. Unauthorized access to APIs can lead to significant issues, such as data breaches, unauthorized changes, and compliance violations. Azure Entra ID provides app roles, simplifying role-based access control (RBAC) or attribute-based access control (ABAC) for our applications.
App roles allow applications to define specific roles that can be assigned to users, groups, or other applications. These roles are embedded within the access token, allowing for fine-grained authorization within an application.
Typically, applications use groups for authorization, but this approach has several drawbacks:
The groups claim includes the object ID instead of its name.
By default, if a user belongs to more than 200 groups (the limit for JWT tokens), the groups claim is omitted from the token. Instead, _claim_names and _claim_sources claims are included, indicating that the groups must be queried from the Microsoft Graph API.
Groups are broad and shared across multiple applications, complicating the management of fine-grained application authorization.
In contrast, app roles address these issues:
The user's roles are included in the roles claim, with the content being the role's name.
App roles are specific to each application, ensuring better granularity.
There is no strict limit on the number of app roles that can be included in the token, although the token size cannot exceed 4 KB.
To see app roles in action, follow the steps in the article Securing .NET API with Microsoft Entra ID and then proceed with the steps listed below.
Open our App Registration and go to the App Roles option. Click Create app role:

The allowed member type property specifies whether the app role can be assigned to users/groups, applications, or both. The value property specifies the content included in the roles claim.
Go to Enterprise Applications and choose the application where the app role was created. Go to Users and Groups and Click Add User. Choose the user and select the Reader role:

Go to the Program.cs file and update the content as follows:
using Microsoft.AspNetCore.Authentication.JwtBearer;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddCors();
builder.Logging.SetMinimumLevel(LogLevel.Debug);
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = "https://login.microsoftonline.com/<MY_TENANT>/";
options.Audience = "api://<MY_API_CLIENT_ID>";
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("Reader", policy => policy.RequireRole("WeatherForecast.Reader"));
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthentication();
app.UseAuthorization();
app.UseHttpsRedirection();
app.UseCors(cp => cp
.AllowAnyHeader()
.SetIsOriginAllowed(origin => true)
.AllowCredentials()
);
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.RequireAuthorization("Reader")
.WithOpenApi();
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
The implementation is straightforward and requires minimal additional code. We add a Reader policy in the AddAuthorization method and apply that policy in the RequireAuthorization method. The SPA does not need any changes to function as expected. Press F5 to start the API, and run npx serve . inside the Client folder to launch the SPA:

The library Microsoft.Identity.Web can be used instead of directly setting up the
AddJwtBearermethod. The main difference is that this library offers a broader integration between our applications and Azure Entra ID. Examples can be found here.
All the code can be found here. Thanks, and happy coding.