ASP.NET Core Web API + Swagger + Azure B2C
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
But I only use the Program.cs and the appsettings.json currently.
Used for TENANT_NAME
Used for CLIENT_ID:
Used as CLIENT_SECRET:
Used for B2C_1_AdAZURE_B2C_TENANT_NAME:
The SCOPE I tried before:
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:
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
Copy the token
Authenticate in Swagger
The dialog closes. Testing "/test":
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"
- Try
{
"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.)