ASP.NET Core Web API + Swagger + Azure B2C

Bernhard 116 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.)

ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,594 questions
ASP.NET API
ASP.NET API
ASP.NET: A set of technologies in the .NET Framework for building web applications and XML web services.API: A software intermediary that allows two applications to interact with each other.
341 questions
Microsoft Entra External ID
Microsoft Entra External ID
A modern identity solution for securing access to customer, citizen and partner-facing apps and services. It is the converged platform of Azure AD External Identities B2B and B2C. Replaces Azure Active Directory External Identities.
2,907 questions
Microsoft Entra ID
Microsoft Entra ID
A Microsoft Entra identity service that provides identity management and access control capabilities. Replaces Azure Active Directory.
22,061 questions
{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.