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:
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
- withIsAuthenticated
: trueOnChallengeAysnc
- withIsAuthenticated
: false, andAuthenticationFailure
: null
As a comparison, if I let the JWT expire, I get:
OnMessageReceivedAsync
OnAuthenticationFailedAsync
- withIsAuthenticated
: falseOnChallengeAsync
- withIsAuthenticated
: false andAuthenticationFailure
: "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...