Site icon Gowtham K

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:

Prerequisites:

Microsoft Entra ID App Registrations:

Web API App Registration

Register a new app in Entra ID:

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

    Blazor App Registration

    Register a second app:

      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:

        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

        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

        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.

        Exit mobile version