I am trying to authorize Azure Active Directory users logged into a NextJS client application (using msal-react) through my .NET 8 API so they can use [Authorize]
endpoints. However, upon sending a client JWT to my API, I receive a 401 response and my API logs a DenyAnonymousAuthorizationRequirement
.
I have registered both applications through Azure's App registrations. In my API registration, I have exposed my API to authorize my client app through a single scope, access_as_user
. I have confirmed this in my client's registration as a Delegated permission.
In my .NET API, I'm setting my AzureAd
configuration in appsettings.json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "qualified.domain.name",
"TenantId": "API-TENANT-ID",
"ClientId": "API-CLIENT-ID",
"Scopes": "access_as_user",
"CallbackPath": "/signin-oidc"
},
I then add authentication to my services in my Program.cs
...
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(config.GetSection("AzureAd"));
...
app.UseAuthorization();
app.UseAuthentication();
...
Finally, I've set up a simple controller with a single [Authorize]
endpoint.
[Controller]
[Route("api")]
public class GeneralController : Controller
{
[Authorize]
[HttpGet("get")]
public Task<IActionResult> Test()
{
return Task.FromResult<IActionResult>(Ok());
}
}
In my client application, I retrieve my token after a user logs in with Azure AD.
const { instance, accounts } = useMsal();
...
instance.acquireTokenSilent({
scopes: [
"api://API-CLIENT-ID/access_as_user",
],
account: account,
})
I'm able to log the token retrieved by the client app succesfully and, when using https://jwt.io/, I can decode the JWT to find the following relevant properties:
{
"aud": "API-CLIENT-ID",
"iss": "https://login.microsoftonline.com/CLIENT-TENANT-ID/v2.0",
"azp": "CLIENT-CLIENT-ID",
"name": "LOGGED-IN-USER-NAME",
"scp": "access_as_user",
"tid": "CLIENT-TENANT-ID",
"ver": "2.0"
}
However, when I include this token in a request's Authorization header to the general endpoint I set up, I receive a 401 response and the following logs from my API:
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed. These requirements were not met:
DenyAnonymousAuthorizationRequirement: Requires an authenticated user.
info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[12]
AuthenticationScheme: Bearer was challenged.
General speaking, by default, the web API protect by AAD requires a token which aud
claim value should match the value we defined in the appsettings.json, so that it's a valid token. If we didn't define Audience
property in appsetting, the default value for it is the ClientId
in appsetting.
According to your screenshot, you exposed API api://my_api_client_id/access_as_user
in your My API
app. And you already added this API permission into My client
, that should be ok. Therefore, in your client app, you should have configurations for MSAL that all use My Client
, and my_api_client_id
should only appear once when you define the scope you used to generate access token. Please make sure that you can obtain the access token, and decode it to see the value for aud
and scp
. If value of aud
is my_api_client_id
, then we don't need to change anything in your web api appsettings.json. If value of aud
is api://my_api_client_id
, pls add Audience
property like below.
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "qualified.domain.name",
"TenantId": "My-TENANT-ID",
"ClientId": "backend_aad_app-CLIENT-ID",
"Scopes": "access_as_user",
"Audience": "api://clientid_of_the_app_exposed_api_which_should_be_id_of_backend_aad_app"
},
Comparing the information you shared above, all codes look good, so that could you pls checking the app ids you used in each place to make sure they didn't mix up? In the mean time, we might use only one app which is the My API
both in the client and the backend api.
I followed the official sample and had a test in my side, it worked for me.