Search code examples
.netasp.net-mvcazure-active-directoryjwtmicrosoft-identity-web

Validating a JWT from AzureAD using Microsoft.Identity.Web succeeds but then fails in the same call


I have an API that I need to secure with AzureAD so that it can use SSO.

The API has a Swagger UI, so I have (after reading many, many tutorials/explanations/issues):

  • Created an App Registration in Azure
  • Created a scope for my API
  • Added my scope as an API permission
  • Changed the "accessTokenAcceptedVersion" to 2 in the manifest
  • Set up Swagger to use the OAuth2 flow with appropriate config and values
  • Set up the API using Microsoft.Identity.Web and added configuration to allow sign-in to a single tenant

as per: https://github.com/AzureAD/microsoft-identity-web/wiki/web-apis, setup in the API is as follows:

builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration, subscribeToJwtBearerMiddlewareDiagnosticsEvents: true);

appsettings.json:

{
   ...
   "AzureAd": {
        "Instance": "https://login.microsoftonline.com/",
        "TenantId": "{guid for tenant ID from Azure portal}",
        "ClientId": "{guid for client ID from Azure portal}",
        "CallbackPath": "/signin-oidc",
        "SignedOutCallbackPath ": "/signout-callback-oidc"
    }
}

I now have the authorize button on swagger.

When I click the button I get a dialog:

Swagger Auth Dialog

Enter the client id, select the scope and click authorize. Azure AD sign-in window opens in a new tab, enter creds and submit, then we get redirected back to swagger, and we're authorized:

Authorized

So far so good.

I then try to call the API itself and it fails with a 401.

Checking the request I can see the JWT token being passed as a Bearer token.

So I stuck some breakpoints in JwtBearerMiddlewareDiagnostics to try and work out what is going wrong.

The following events fire in the following order:

  • OnMessageReceivedAsync
  • OnTokenValidatedAsync - with IsAuthenticated: true
  • OnChallengeAysnc - with IsAuthenticated: false, and AuthenticationFailure: null

As a comparison, if I let the JWT expire, I get:

  • OnMessageReceivedAsync
  • OnAuthenticationFailedAsync - with IsAuthenticated: false
  • OnChallengeAsync - with IsAuthenticated: false and AuthenticationFailure: "some message about the token being expired"

So, what I'm struggling with, is when my JWT is valid, OnTokenValidatedAsync fires and shows a valid UserPrinciple where the Identity shows that the user is validated.. but then almost immediately after that, an auth challenge fires where the UserPrinciple shows that the user is not validated... and then since I'm not handling the OnChallengeAsync event, a 401 is returned. My understanding is that OnChallengeAsync is called if there is no valid token (in a JWT flow), yet OnTokenValidatedAsync is called showing that the token was correctly passed and the user is valid?!

Also, when I have a "valid failure" (the token has expired), the context passed to OnChallengeAsync clearly shows the reason in the AuthenticationFailure property, yet when OnChallengeAsync fires for my (as far as I can tell) good token, AuthenticationFailure is null...


Solution

  • Finally found the issue.

    I was missing app.UseAuthentication();. Adding that line, fixes the issue and it works as expected :)