Building a .NET 8 Web API Secured with Microsoft Entra ID and a Blazor App

Introduction:

Secure authentication and authorization mechanisms are essential in modern application development. Microsoft Entra ID (formerly Azure AD) provides a powerful and scalable identity platform that integrates seamlessly with  .NET. In this article, we’ll walk through building a secure .NET 8 Web API and a Blazor App that authenticates and calls the API using Microsoft Entra ID.

We’ll be creating:

  • .NET 8 Web API – Protected using Microsoft Entra ID for authorization.
  • Blazor App – Authenticates users and accesses the protected API securely using access tokens.

Prerequisites:

Microsoft Entra ID App Registrations:

Web API App Registration

Register a new app in Entra ID:

  • Name: Provide a name for your application. I named it Azure-MS-EntraID-Management – API
  • Platform: None (for now)
  • Supported account type – I went with Accounts in this organizational directory only (Default Directory only – Single tenant). Based on your requirement, switch between the different account type options
  • Select Expose an API from the Manage blade, click on Add a scope api://<client-id>/UserRole.Read  as shown in the figure below
Entra Id application expose API

    Save the Application (client) ID and Directory (tenant) ID

    Blazor App Registration

    Register a second app:

    • Name: Provide a name for your application. I named it MS-EntraID-Workflow
    • Platform: Web
    • Redirect URI: Add the redirect url, In my case it is https://localhost:7173/signin-oidc

      signin-oidc is the callback endpoint used for processing the login response from Microsoft Entra ID. It’s part of the ASP.NET Core OIDC authentication flow.

      Authentication settings:

      • Check ID tokens and Access tokens
      • Add api://<Azure-MS-EntraID-Management – API>/UserRole.Read  to API permissions
      • Grant admin consent, as shown in the figure below
      Entra ID application API permissions

        Build the .NET 8 Web API

        Create a .NET 8 Web API project with  Visual studio or VS Code

        Add Microsoft Identity Packages

        Microsoft.Identity.Web

        Microsoft.AspNetCore.Authentication.JwtBearer

        Configure appsettings.json

          "AzureAd": {
            "Instance": "https://login.microsoftonline.com/",
            "TenantId": "<Your-MS EntraID Tenant ID >",
            "ClientId": "<Your Registere Application Client ID>",
            "Scopes": "UserRole.Read"
          },	

        Program.cs

        builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                        .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
        
        builder.Services.AddAuthorization(config =>
        {
            config.AddPolicy("AuthZPolicy", policyBuilder =>
                policyBuilder.Requirements.Add(new ScopeAuthorizationRequirement() { RequiredScopesConfigurationKey = $"AzureAd:Scopes" }));
        });
        

        The above statements will register the OpenId Authentication Authorization policies

        And use app.UseAuthorization() middleware

        Create WeatherForecastController.cs and add the below Action

          [ApiController]
         [Route("[controller]")]
         [Authorize(Policy = "AuthZPolicy")]
         public class WeatherForecastController : ControllerBase
         {
             private static readonly string[] Summaries = new[]
             {
                 "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
             };
        
             private readonly ILogger<WeatherForecastController> _logger;
        
             public WeatherForecastController(ILogger<WeatherForecastController> logger)
             {
                 _logger = logger;
             }
        
             [HttpGet(Name = "GetWeatherForecast")]
             public IEnumerable<WeatherForecast> Get()
             {
                 return Enumerable.Range(1, 5).Select(index => new WeatherForecast
                 {
                     Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                     TemperatureC = Random.Shared.Next(-20, 55),
                     Summary = Summaries[Random.Shared.Next(Summaries.Length)]
                 })
                 .ToArray();
             }
         }
        

        The AuthZPolicy will protect the GetWeatherForecast action, which will only allow requests with the UserRole.Read scope.

        Create the Blazor Server Application

        Create a Project using Visual Studio or VS Code

        Add the following Microsoft Identity Packages

        • dotnet add package Microsoft.Identity.Web
        • dotnet add package Microsoft.Identity.Web.UI
        • dotnet add package Microsoft.Identity.Web.TokenAcquisition

        Configure appsettings.json

          "AzureAd": {
            "Instance": "https://login.microsoftonline.com/",
            "Domain": "<Your domain>",
            "TenantId": "<Your Tenant Id>",
            "ClientId": "<Your MS Entra ID application client ID>",
            "CallbackPath": "/signin-oidc"
          },
          "DownstreamApi": {
            "Scopes": [ "api://<Your MS Entra ID API App Client ID>/UserRole.Read" ],
            "BaseUrl": "https://localhost:7100/",
            "RelativePath": "WeatherForecast"
        

        Use Secret.json service to configure the client secret

        "AzureAd":{"ClientSecret": "<Your MS Entra ID application client ID>"}

        Program.cs

        builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
            .AddMicrosoftIdentityWebApp(configureMicrosoftIdentityOptions =>
            {
                builder.Configuration.Bind("AzureAd", configureMicrosoftIdentityOptions);
                            
            })
            .EnableTokenAcquisitionToCallDownstreamApi(new string[] { builder.Configuration["DownstreamApi:Scopes"] })
            .AddDownstreamApi("WeatherList", builder.Configuration.GetSection("DownstreamApi"))
            .AddInMemoryTokenCaches();
        builder.Services.AddControllersWithViews()
            .AddMicrosoftIdentityUI();
        builder.Services.AddAuthorization(options =>
        {
            options.FallbackPolicy = options.DefaultPolicy;
        });
        

        The above statement will register the authentication and authorization services.

        app.UseAuthentication();
        app.UseAuthorization();

        User Authentication and Authorization Middleware

        Call the API

        I created a page to render the weather forecast data

        @page "/weatherList"
        @inherits WeatherBase
        <table class="table">
            <thead>
                <tr>
                    <th>
                        Date
                    </th>
                    <th>
                        Temperature
                    </th>
                    <th>
                        Summary
                    </th>
        
                </tr>
            </thead>
            <tbody>
                @foreach (var item in weatherList)
                {
                    <tr>
                        <td>
                            @item.Date
                        </td>
                        <td>
                            @item.TemperatureC
                        </td>
                        <td>
                            @item.Summary
                        </td>
        
                    </tr>
                }
            </tbody>
        </table>
        

        WeatherBase.cs

         public class WeatherBase: ComponentBase
         {
             [Inject]
             MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler { get; set; }
             [Inject]
             IDownstreamApi _downstreamApi { get; set; }
             protected IEnumerable<WeatherModel> weatherList = new List<WeatherModel>();
        
             protected WeatherModel toDo = new WeatherModel();
        
             protected override async Task OnInitializedAsync()
             {
                 await GetToDoListService();  
             }
                  /// <summary>
             /// Gets all todo list items.
             /// </summary>
             /// <returns></returns>
             [AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")]
             private async Task GetToDoListService()
             {
                 try
                 {
                     weatherList = await _downstreamApi.GetForUserAsync<IEnumerable<WeatherModel>>("WeatherList");
                 }
                 catch (Exception ex)
                 {
                     Console.WriteLine(ex.Message);
        
                     // Process the exception from a user challenge
                     ConsentHandler.HandleException(ex);
                 }
             }
         }
        
        

        The above function GetToDoListService will be executed only if the user has the required scope. It is called the WeatherList Service, which is registered in theProgram.cs file using downstreamApi GetForUserAsync function.

        WeatherModel.cs

            public class WeatherModel
            {
                public DateOnly Date { get; set; }
        
                public int TemperatureC { get; set; }
        
                public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
        
                public string? Summary { get; set; }
            }
        

        Run both the applications, navigate to WeatherReport page from the blazor application, it will fetch the weatherReport based on application scope

        Blazor app weather report

        Conclusion:

        With this setup, you’ve integrated Microsoft Entra ID authentication into both a .NET 8 Web API and a Blazor Server app using secure OAuth 2.0 flows. The Blazor Server app acquires tokens on the user’s behalf to call protected endpoints in the API.

        gowthamk91

        Leave a Reply

        Discover more from Gowtham K

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

        Continue reading