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):
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:
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:
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
: trueOnChallengeAysnc
- with IsAuthenticated
: false, and AuthenticationFailure
: nullAs a comparison, if I let the JWT expire, I get:
OnMessageReceivedAsync
OnAuthenticationFailedAsync
- with IsAuthenticated
: falseOnChallengeAsync
- 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...
Finally found the issue.
I was missing app.UseAuthentication();
. Adding that line, fixes the issue and it works as expected :)