How to use Azure AD B2C to protect WebAPI endpoints in .NET 8

62 Views Asked by At

I have 2 applications, WebApi(ASP.NET Core Web API) and WebApp(Vite.js), they will be all hosted in Azure. I would like to use Azure AD B2C to secure the endpoints in the WebApi project, the grant type should be client_credentials, so that when the WebApp call the WebApi endpoints, it calls the Auth endpoint first to get the access token and then use the access token in its following request header.

What I have done so far:

  1. Register Web API in Azure AD B2C:

Define the Redirect URI enter image description here

and generate a client secret for Web API.

  1. Configure Authentication in Your ASP.NET WebAPI: enter image description here

Also exposed the API by adding the scopes enter image description here

Added the Microsoft Identity Web library to simplify Azure AD B2C authentication. Initiated the authentication libraries in your startup class. Added the necessary app settings for Azure AD B2C in your appsettings.json file.

  1. Register Your Web APP in Azure AD B2C: Registered Web APP (Vite.js) Configure the Redirect URI enter image description here

and generate a client secret for the Web APP.

  1. Implement Application-to-Application Protection: and configured the permission scope for the WebApp enter image description here

  2. Set Up Federation Between Azure B2C and Azure AD Single Tenant: Federate your Azure B2C identity provider with the Azure AD single tenant App registration. enter image description here

The Client ID is the frontend Web App Client ID. The User ID is the Subject ID I got from decoding the JWT token which is the Backend Web API Subject ID.

Now I would like to test calling the endpoints in my local dev environment by using Swagger UI.

The code:

Prohgram.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
using Microsoft.OpenApi.Models;
using Serilog;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Host.UseSerilog();

builder.Services.AddControllers();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c => {
    c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
    {
        Title = "JWTToken_Auth_API",
        Version = " v1"
    });

    c.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme()
    {
        Name = "Authorization",
        Type = Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey,
        Scheme = "Bearer",
        BearerFormat = "JWT",
        In = Microsoft.OpenApi.Models.ParameterLocation.Header,
        Description = "Wahaha",
        OpenIdConnectUrl= new Uri("https://mycomp.b2clogin.com/mycomp.onmicrosoft.com/B2C_1_SignInAPI/v2.0/.well-known/openid-configuration")
    });

    c.AddSecurityRequirement(new OpenApiSecurityRequirement {
        {
            new OpenApiSecurityScheme {
                Reference = new OpenApiReference {
                    Type = ReferenceType.SecurityScheme,
                        Id = "Bearer"
                }
            },
            new string[] {}
        }
    });
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.Run();

ADB2CAuthController.cs


using Microsoft.AspNetCore.Mvc;

namespace MyCompBackend.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ADB2CAuthController : ControllerBase
    {
        [HttpPost]
        public async Task<string> Post()
        {
            var token = await AuthGetter.GetJWTTokenAsync();
            return token;
        }
    }
}

WeatherForecastController.cs


using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web.Resource;

namespace MyCompBackend.Controllers
{
    [Authorize]//(Policy = "WebAppPolicy")
    //(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    [ApiController]
    [Route("[controller]")]
    [RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
    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();
        }
    }
}

appsettings.json and appsettings.Development.json, they are same for now


{
  "AzureAd": {
    "Instance": "https://mycomp.b2clogin.com/",
    "Domain": "mycomp.onmicrosoft.com",
    "TenantId": "a15e2b5d-affa-4fdc-a947-xxxxxxxxxxxx",
    "ClientId": "49f6d1e2-943b-4b88-bfa1-xxxxxxxxxxxx",
    "CallbackPath": "/signin-oidc",
    "Scopes": "access_api_only",
    "SignUpSignInPolicyId": "B2C_1_susi",
    "SingInAPIPolicyId": "B2C_1_SignInAPI",
    "SignedOutCallbackPath": "/signout/B2C_1_susi",
    "ResetPasswordPolicyId": "b2c_1_reset",
    "EditProfilePolicyId": "b2c_1_edit_profile",
    "EnablePiiLogging": true,
    "WebApiClientSecret": "xxxxxxxxxxxx",
    "WebAppClientSecret": "xxxxxxxxxxxx"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Where I am:

I am able to call the Auth endpoint and retrieve a access token, [enter image description here]

I then use the Authorize button at the top right corner and put in the value of the token like: Bearer [enter image description here]

Then I scroll to the Weather Forecast endpoint and try to test it: [enter image description here]

But as you can see that it always return 401.

(https://i.stack.imgur.com/owOhC.png) (https://i.stack.imgur.com/eDCXH.png) (https://i.stack.imgur.com/KJPhV.png)

I have tried to Google and tried so many different ways.

What could be the issues? And how to fix them?

0

There are 0 best solutions below