The browser app
I have a browser app (CRA, TypeScript) which is issuing, after successfully authenticating to Azure AD, a request to my API:
public async acquireAccessToken(): Promise<string | undefined> {
let res: AuthResponse | undefined = undefined;
const params: AuthenticationParameters = {
scopes: ["Users.Read"],
};
try {
res = await this.msal.acquireTokenSilent(params);
} catch (error) {
res = await this.msal.acquireTokenPopup(params);
}
return !res || !res.accessToken ? undefined : res.accessToken;
}
The one before is a utility method to get the access token to contact the API, the actual call is here:
const token = await acquireAccessToken();
const res = await fetch("/controller/test", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
});
console.log(res.text());
Where msal
is the UserAgentApplication
I am using as client to handle authentication and authorization in my browser app.
I have everything correctly set up in Azure where a registration app is used to represent the browser app, and another registration app is used to describe the API I need to contact.
The API
The API server is an ASP.NET Core 3.1 C# application whose Startup.cs
is:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();
app.UseAuthorization();
}
}
I have removed all the extra code and left the parts that concern auth.
The controller I am contacting is:
[ApiController]
[Route("controller")]
public class MyController : ControllerBase
{
[HttpGet("test/")]
[Authorize(Roles = "Admin")]
public async Task<string> Test()
{
return "Ok";
}
[HttpGet("test2/")]
[Authorize]
public async Task<string> Test2()
{
return "Ok";
}
[HttpGet("test3/")]
public async Task<string> Test3()
{
return "Ok";
}
}
Azure
The setup in Azure is simple: apart from the two app registrations for the browser app and the API, I have set in the browser app registration some custom roles and assigned them to some users.
I authenticate in the browser app using a user who has the
Admin
app role assigned to it.
The problem
When my client app tries to fetch data using these endpoints:
/controller/test3
/controller/test2
Everything is fine as one is unprotected and the other one uses a simple [Authorize]
.
However when trying to fetch from /controller/test
, I get 403 (Forbidden)
.
Why can't I make the roles work?
More info
While debugging when fetching test2
, I can see, in the controller, that this.User
is present and there are several claims. Among those claims, I cannot see anything relating to the role. The access token I get has the following form:
{
"aud": "api://xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"iss": "https://sts.windows.net/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/",
"iat": 1607034050,
"nbf": 1607034050,
"exp": 1607037950,
"acr": "1",
"aio": "AWQAm/8RAAAAH1j5tZzINJFi5fsMsgf99gcrnqQA+dOhWBpFmsgy3jsr0pFJ0AxvenqthiNLmRqKzqx6l+9SuLlRniAVCTOoqEE7MonnOetO3h7g1/Bm520rS0qiX/gpCCWYm/UwDlJ+",
"amr": [
"pwd"
],
"appid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"appidacr": "0",
"email": "xxx@xxx.xxx",
"idp": "https://sts.windows.net/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/",
"ipaddr": "xxx.xxx.xxx.xxx",
"name": "XXX",
"oid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"rh": "0.AAAASupzDdEU8EyBI3R6nFeJQHVORvhJZ2hDjJoEO5yPUcZ0AEU.",
"scp": "Users.Read",
"sub": "frR45l2dTAIyXZ-3Yn2mGNbBcBX9CrGisgJ4L8zOCd4",
"tid": "0d73ea4a-14d1-4cf0-8123-747a9c578940",
"unique_name": "xxx@xxx.xxx",
"uti": "39dk-rAAP0KiJN5dwhs4AA",
"ver": "1.0"
}
As you can see, no claim relating to roles.
But note that I can successfully get the role in the user token I get when authenticating. I need that claim to flow in the access token too when I use it to contact the API. How?