ASP.NET Core Web API + Swagger + Azure B2C

Bernhard S 126 Reputation points
2024-10-19T09:44:49.0766667+00:00

Hello experts,

since weeks (with on and off phases) I try to protect my ASP.Net project with the Azure B2C. For testing if that works I want to use Swagger. But I am too stupid to make it a success. I got all kinds of error messages but I am unable to get a valid token to test the authorization. I require code help because I am blind for my mistakes. My frustration level is already high. But let me show you my status:

My project structure

User's image

But I only use the Program.cs and the appsettings.json currently.

Used for TENANT_NAME

User's image

Used for CLIENT_ID:

User's image

Used as CLIENT_SECRET:

User's image

Used for B2C_1_AdAZURE_B2C_TENANT_NAME:

User's image

The SCOPE I tried before:

User's image

But this caused: "Response status code does not indicate success: 400 (Bad Request)". So I switched it to "https://TENANT_NAME.onmicrosoft.com/CLIENT_ID/.default".

Settings of AUTHENTICATION:

User's image

My appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "AzureTableStorage": ""
  },
  "AzureAdB2C": {
    "Instance": "AZURE_B2C_TENANT_NAME.b2clogin.com",
    "Domain": "AZURE_B2C_TENANT_NAME.onmicrosoft.com",
    "ClientId": "CLIENT_ID",
    "ClientSecret": "CLIENT_SECRET",
    "Tenant": "AZURE_B2C_TENANT_NAME",
    "SignUpSignInPolicyId": "B2C_1_AdAZURE_B2C_TENANT_NAME",
    "Scope": "https://AZURE_B2C_TENANT_NAME.onmicrosoft.com/CLIENT_ID/.default",     "AuthorizationUrl": "https://AZURE_B2C_TENANT_NAME.b2clogin.com/AZURE_B2C_TENANT_NAME.onmicrosoft.com/oauth2/v2.0/authorize?p=B2C_1_AdAZURE_B2C_TENANT_NAME",
    "TokenUrl": "https://AZURE_B2C_TENANT_NAME.b2clogin.com/AZURE_B2C_TENANT_NAME.onmicrosoft.com/oauth2/v2.0/token?p=B2C_1_AdAZURE_B2C_TENANT_NAME",
    "RedirectUri": "https://localhost:7169/swagger/oauth2-redirect.html"
  },
  "Cors": {
    "AllowedOrigins": [ "https://localhost:7169" ]
  }
}

The Program.cs:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
using Microsoft.OpenApi.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Identity.Client;
using System.Net.Http.Headers;
using Swashbuckle.AspNetCore.Annotations;
var builder = WebApplication.CreateBuilder(args);
string tenantName = builder.Configuration["AzureAdB2C:Tenant"];
string instance = builder.Configuration["AzureAdB2C:Instance"];
string domain = builder.Configuration["AzureAdB2C:Domain"];
string clientId = builder.Configuration["AzureAdB2C:ClientId"];
string clientSecret = builder.Configuration["AzureAdB2C:ClientSecret"];
string authority = $"https://{instance}/tfp/{domain}/{builder.Configuration["AzureAdB2C:SignUpSignInPolicyId"]}/v2.0/";
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(options =>
    {
        options.Authority = authority;
        options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = $"https://{instance}/{domain}/v2.0/",
            ValidateAudience = true,
            ValidAudience = clientId,
            ValidateLifetime = true
        };
        options.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = context =>
            {
                Console.WriteLine($"Authentication failed: {context.Exception}");
                return Task.CompletedTask;
            }
        };
    }, options => { builder.Configuration.Bind("AzureAdB2C", options); });
builder.Services.AddAuthorization();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Type = SecuritySchemeType.Http,
        Scheme = "bearer",
        BearerFormat = "JWT",
        In = ParameterLocation.Header,
        Name = "Authorization",
        Description = "JWT Authorization header using the Bearer scheme."
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" }
            },
            Array.Empty<string>()
        }
    });
});
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(builder =>
    {
        builder.AllowAnyOrigin()
               .AllowAnyMethod()
               .AllowAnyHeader();
    });
});
var app = builder.Build();
async Task<string> GetAccessTokenAsync()
{
    var app = ConfidentialClientApplicationBuilder
        .Create(clientId)
        .WithAuthority(authority)
        .WithClientSecret(clientSecret)
        .Build();
    try
    {
        var result = await app.AcquireTokenForClient(new[] { builder.Configuration["AzureAdB2C:Scope"] }).ExecuteAsync();
        return result.AccessToken;
    }
    catch (MsalServiceException ex)
    {
        Console.WriteLine($"MSAL Service Exception: {ex.Message}");
        return null;
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Unexpected error: {ex.Message}");
        return null;
    }
}
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
        c.OAuthClientId(clientId);
        c.OAuthAppName(tenantName);
        c.OAuthUsePkce();
        c.OAuthScopes(builder.Configuration["AzureAdB2C:Scope"]);
        c.RoutePrefix = string.Empty;
    });
}
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", () => "Hello World!")
   .WithName("GetHelloWorld")
   .WithMetadata(new SwaggerOperationAttribute("Get Hello World", "Returns a simple hello world message"));
app.MapGet("/test", [Authorize] () => "Authorization successful!")
   .WithName("GetTest")
   .WithMetadata(new SwaggerOperationAttribute("Test Endpoint", "Returns a success message if authorized"));
app.MapGet("/testtoken", async (HttpContext httpContext) =>
{
    var token = await GetAccessTokenAsync();
    if (token == null)
    {
        return Results.Problem("Failed to acquire token");
    }
    using var client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
    var response = await client.GetAsync("https://localhost:7169/test");  // Replace with your actual URL
    if (response.IsSuccessStatusCode)
    {
        return Results.Ok("Token acquisition and API call successful");
    }
    return Results.Problem($"API call failed: {response.StatusCode}");
})
.WithName("GetTestToken")
.WithMetadata(new SwaggerOperationAttribute("Test Token Acquisition", "Acquires a token and calls the test endpoint"));
app.MapGet("/claims", (HttpContext httpContext) =>
{
    return Results.Ok(httpContext.User.Claims.Select(c => new { c.Type, c.Value }));
})
.WithName("GetClaims")
.WithMetadata(new SwaggerOperationAttribute("Claims Endpoint", "Returns the claims of the authenticated user"));
app.Run();

FAILS/RESULTS:

1.) Try

Create a Test Method to get a token

User's image

User's image

Copy the token

User's image

User's image

Authenticate in Swagger

User's image

The dialog closes. Testing "/test":

User's image

Server response
Code	Details
401
Undocumented
Error: response status is 401

Response headers
 content-length: 0 
 date: Sat,19 Oct 2024 09:30:09 GMT 
 server: Kestrel 
 www-authenticate: Bearer error="invalid_token" 

  1. Try

User's imageUser's image

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
  "title": "An error occurred while processing your request.",
  "status": 500,
  "detail": "API call failed: Unauthorized"
}

Any ideas?

My code is a mess and I don't need to keep it. All I want to achive is to make a authorized GET request as a test to verify that my Api endpoint is secured by the Bearer token.

(I have anonymized confidential information. If I forget something, please let me know.)

Developer technologies | ASP.NET | ASP.NET Core
Developer technologies | ASP.NET | ASP.NET API
Microsoft Security | Microsoft Entra | Microsoft Entra External ID
Microsoft Security | Microsoft Entra | Microsoft Entra ID
{count} votes

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.