I have followed some guides from MS and others on protecting a .Net core 7 controller based API. I have created roles inside the app registration and assigned users to those roles. Inside Program.cs I have generated some policies I plan to use, however, regardless of whether I check for policy or role on the API controller, I always get a 403. The 403 returns both inside Swagger when testing the API and in postman when calling the API.
I have confirmed the MS Identity bearer token in JWT.io and everything is correct including signature, and I confirm the expected roles are listed for the token gatherer. I think I am confusing myself as I am taking advice from multiple sources.
In my head, and please correct if I am wrong, the benefit of using Azure AD for a single tenant API is when called (eventually from a separate web app) the web app ensures the user is authenticated, gathers a token and then sends it in the request to the API. If the user has roles these are sent in the token. Using the authorize decorators on the web API, the API would check for the existence of these roles in the token and grant or deny access accordingly. I may be completely over-simplifying the process, but from what the docs have said that is the process I envisage.
Here is a portion of the Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"))
.AddInMemoryTokenCaches();
builder.Services.AddControllers();
builder.Services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters.RoleClaimType = "roles";
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ElevatedRights", policy => policy.RequireRole("Admin.Senior","HR.Manager","Payroll.Manager"));
options.AddPolicy("RequireAdminRole", policy => policy.RequireRole("Admin.Senior"));
options.AddPolicy("RequireManagerRole", policy => policy.RequireRole("Branch.Manager","Operations.Manager"));
});
... unrelated others
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
So regardless of whether I use
[Route("api/[controller]")]
[ApiController]
[Authorize(Policy = "ElevatedRights")]
public class ColleaguesController : ControllerBase
{
// GET: api/<ColleaguesController>
[HttpGet]
public async Task<IResult> Get(IColleagueData data)
{
try
{
return Results.Ok(await data.GetColleagues());
}
catch (Exception ex)
{
return Results.Problem(ex.Message);
}
}
or
[Route("api/[controller]")]
[ApiController]
[Authorize(Roles = "Admin.Senior")]
public class BranchesController : ControllerBase
{
// GET: api/<BranchesController>
[HttpGet]
public async Task<IResult> Get(IBranchData data)
{
try
{
return Results.Ok(await data.GetBranches());
}
catch (Exception ex)
{
return Results.Problem(ex.Message);
}
}
I get a 403 response.
I am sure it is me not understanding something as opposed to it being misconfigured, but I would be really grateful for any insight. As mentioned before I can confirm that the logged in user has the role Admin.Senior and that value is passed inside the token by the key "roles". If it helps confirm the API is configured correctly, I can add [AllowAnonymous] to a method e.g. Get A Branch (as opposed to all) and that returns the data with a 200.
EDIT
Token added in web app using MSAL library
string[] scopes = new string[] {"api://6d93b2cc-6928-4123-a27e-XXXXXXXXX/access_as_user" };
string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
var client = _clientFactory.CreateClient("davidsons");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
Roles in Bearer token
"roles": [
"Admin.Senior"
],