microsoft-authentication-library-for-js: Strategy.prototype.jwtVerify: cannot verify token. When using AzureAD v2.0
We are migrating our application from the AzureAD v1.0 endpoint to AzureAD v2.0, but after doing the proper changes I receive that response which you see in the title.
This is my configuration:
identityMetadata: OAUTH2_CONFIG.identityMetadata, clientID: OAUTH2_CONFIG.clientID, audience: OAUTH2_CONFIG.clientID, validateIssuer: OAUTH2_CONFIG.validateIssuer, //true passReqToCallback: OAUTH2_CONFIG.passReqToCallback, // true loggingLevel: OAUTH2_CONFIG.loggingLevel
Following the documentation the default value for audience is clientID, anyways I set it. But It can’t validate the Access Token.
Also I am sending the token in the Authorization header like this Bearer $thetokenIrecieve
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 1
- Comments: 62 (3 by maintainers)
Thanks @holwech I eventually figured it out, and literally punched the air in joy once it finally worked!
As you mention the “secret sauce” was adding a scope to my registered app in AAD and then requesting that scope. Doing so mysteriously transforms the token you get back, suddenly it will have your app client id as the
aud
, it will have the correctiss
set (pointing at login.microsoft) and will be a v2 token. Magic! More importantly this library will successfully validate itIn summary:
api://{app-guid}/myscope
. The name of the scope doesn’t matterapi://{app-guid}/myscope
to the scopes you request, i.e. alongsideuser.read
blah blahThe access token you get back will now be for your app and not the Graph API
As you mention, it’s poorly/barely explained in the docs how to set this up or why the tokens differ so much. So many of the code examples and samples have you requesting
user.read
and leaving it there, so this is why I think people are tripping over itThis section does mention it, but feels quite buried https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-overview
I have the same problem, despite having a v2.0 access token. If you haven’t figured it out by now already, to get a v2.0 access token go to portal.azure.com ->
Azure Active Directory
->App registrations (preview)
-> Find your app registration in the list ->Manifest
. In the manifest file you will find a field calledaccessTokenAcceptedVersion
, this is by default set tonull
, which makes the app default to v1.0 access token, even if it is registered as a v2 app (I don’t know why it works like this…). To get v2.0 access tokens, set this value to2
. The Azure documentation is super confusing about this, but it seems to do the trick…Did you manage to solve the issue in the end?
@timmyreilly In my case, the validation with the BearerStrategy worked as soon as I set loggingNoPII: false
@rocketraman
Thank you for the response!
Luckily i handled it by put
validateIssuer=false
😃 that is my config:One last note: I think there is still a bug in the library. The
validateIssuer
is supposed to work automatically when using the tenant-specific endpoint. However, that endpoint returns the v1 issuer e.g.:For the v2 endpoint, the issuer is different:
It looks like this library doesn’t adjust its logic to set the issuer depending on whether the access token is a v1 or v2 token.
Actually, never mind – in my case it just looks like I hadn’t set the issuer correctly. Logging at
loggingNoPII: false
produces far more useful information for debugging.Hello everyone , I’m currently getting the exactly same issue. After many time research, I found out the reason and also solved It. The reason come from the scope on SPA login. This access token is V1 and It only use for getting data on graph, I’ll wrong If we use It with passport ad… We need add a scope for our app, not scope for graph. After change the scope, we can get access_token which have decoded result like this. It in v2 version, the aud is our app id, then azure passport will work correctly. For more details, you can look at this link https://authguidance.com/2017/12/01/azure-ad-spa-code-sample/ . I’m sure It working because we are having a wrong understand about graph API.
Thank you to everyone that share in this issue!. I also wasted a couple of hours on this! I just want to add something I noticed, from your client you must pass the
api://app-id/read
scope (after you added it on azure, I put a screenshot at the end in case it helps anyone having problems setting it up), and make sure you do NOT pass theuser.read
from graph or any other scope from the graph because that will make the token be a v1.0 again.Now I was wondering, is it safe to just receive the JWT token on the API, and then just parse it using JWT libs and actually query GraphAPI to get current user data and use that as the verification that the token is valid. I was able to do this by making this request:
And this will return some user information like this:
Then just to be safe you could double check the reply info, email, and user id with the data encoded on the token, and if it’s all good take as a valid token??
In case it helps anyone, here is how you expose the api and add a scope:
To get the BerarerStratergy to work for me I followed @benc-uk’s suggestion and created an application scope
api://{app-guid}/myscope
. I added myscope to the scope list in config.js but I also had to make sure user.read was removed from the list. I also followed @oehm-smith’s suggestion and got my Azure AD application manifest changed to"accessTokenAcceptedVersion": 2
I’m just following the specific, recommended approach from both the javascript authentication client for the client (web browser) and this library which says to use the access token. It’s also written up to use the access token on the MS web site as a specific scenario for SPA clients and a web server backend. I’m beginning to suspect that if “ms graph” scopes are included in your access token request that it will default back to 1.0 version access tokens due to ms graph issues. The issue is that I need to access msgraph and my backend.
I tried to implement normal AD (not B2C) openid and bearer strategies.
The current problem is that I get a token with openid strategy, but I cannot verify it’s signature with bearer strategy.
Here the output:
My config for openid auth strategy:
My config for bearer strategy:
What am I doing wrong?
Coming back to this after a long time… after having just updated to MSAL 2.x, I can offer some additional guidance which I’m not sure anyone has mentioned above.
The main thing to note is that with MSAL you can pass one set of scopes (graph scopes like
openid
,profile
, anduser.read
) to the login call e.g.loginRedirect
, and a different set of scopes when retrieving an id token e.g.acquireTokenSilent
which can be verified by a backend like passport-azure-ad.The first set of scopes are Graph scopes, and as many people have noted, the token generated with those scopes is not meant to be validated by any system other than Graph. So, when generating the token that is needed for your backend API, pass only a custom scope (see https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-app-registration#expose-an-api). This custom scope can be pre-approved by the user at login time by adding it to
extraScopesToConsent
in theloginRedirect
/loginPopup
call options.NOTE: The custom scope recommended in those docs didn’t actually work for me – what did work is to pass an empty scope list to the
acquireTokenSilent
call, which works fine when all you need is an id token to pass to your backend, and not an access token that your frontend code needs to access some resource directly.See this comment for more info: https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/1040#issuecomment-540810355.
I don’t know why this is so poorly documented…
Things I have tried.
accessTokenAcceptedVersion
is set to 2 (setting does nothing, it makes no difference)@aappddeevv - Think you are right. Nothing I try gives me a v2.0 token, soon as you request “user.read” you get a Graph API scope and you always get a v1.0 token. And you need “user.read” for EVERYTHING! So I don’t see how you can ever validate any access token using this library
I’ve traced the source to be the step where the signature is validated with jws here If you remove that part, then the token validation passes and works
The problem might be in the AAD and the MS Graph platform itself, I’m not sure. But following the standard guidelines it’s not possible to get a client + backend working together
I’ve given up, I’m validating my id-token instead, that works, but it’s not the right thing to be doing
That’s too bad! I see now that MSAL.js returns a v1.0 access token when calling the Microsoft graph API. I get tokens for my own API and for that service I do get a v2.0 token. It might be that the graph API endpoint URL for v2.0 is different than for v1.0, and that MSAL defaults to the v1.0 endpoint. Either way, you shouldn’t really use an access token issued for the graph API to authenticate/authorize someone to access your API service. For that you should either use the id token, or better, issue your own access token for you API service (which is what I have done). I’m not sure this covers your case exactly, but hopefully it is of some help.
@pstepnowski I think I was wrong about the issuer url being incorrect, perhaps my endpoint was wrong at the time? Beats me – I changed so many things back then to get this working, who knows what happened.
In any case, my current options are just:
and this seems to work…
@aappddeevv this should be put everywhere! Finally fixed my issue after reading this. Why is this happening
I also got this working after several hours of fiddling around with the settings and reading documentation. Who said that authentication should be easy 😩
The solution that worked for me was also the one others wrote about regarding exposing an API. I only had to use this scope on the frontend application (React) tough when authenticating, no scopes had to be added to the bearer strategy options.
This is the final configuration that worked for me using the @azure/msal-browser package for the frontend with the custom scope and passport-azure-ad with the bearer strategy is the following:
For me, it is still generating a V1 token as long as
user.read
exists. This seems to be an issue with the MS graph API. If you removeuser.read
then it “works”, however, then you cannot use the graph API or get any user details so it defeats the purpose of the lib.This ticket has made clear that the real problem we faced was a version 1 token was being returned instead of a V2. Once I realised this the problem became easier!
MS / Azure need to update their doco.
Thanks everyone for this walk through of solving this problem. Other tips I have (possibly discussed above):
Once @benc-uk’s steps and part 1 of mine was done, the errors became obvious and easily solved.
I’m also hitting this.
App registered in AAD as v2, all v2 endpoints used. Token obtained via MSAL library (the new v1.0.0 version) - The returned token is valid when checked at jwt.ms
But this library never validates the tokens no mater what I try! I’ve put every combination of settings in
BearerStrategy
but nothing works. I’m pulling my hair outThe application is registered on azure under v2. MSAL in the web client obtains a v2 id_toKen as expected. It then gets a v1 access_token. The v1 access_token cannot be verified by passport_azure_ad. The v1 issuance seems to be a AAD problem not just a MSAL javascript problem. Unless passport_azure_ad can verify the v1 access_token using the bearer strategy, which it currently cannot, it appears, the bearer strategy implementation is inconsistent with AAD.
Here it is, retyped. Client then server (passport side)
Client side
with
Server side
My
accessToken
containsnonce
in it’s header, so I can’t decode it withpassport-azure-ad
(reference) - but validatingidToken
works perfectly.This blog post written by @tonesandtones may be helpful.
We are working on an update for the passport library that will encompass this ask, you can track the progress here.
The key insight for me was that after getting your identity token, if any scope for msgraph is included in your access token request, its a v1 token no matter what. Hence, I request a separate access token for msgraph and a separate access token for my API. I needed to use 2 tokens and token requests to keep the returned token version clean.
I got this to work, by stepping through these two relevant files.
bearerstrategy.js
jsonWebToken.js:
So fully working code is here:
The issuer comes straight from the B2C AAD (or just AAD) token issuer
I just looked at the manifest and the accessTokenAcceptedVersion was already set to 2. Hmm…
Jummmm, are you working with the v1 or v2 endpoint? Because if you are sendind v1 access_tokens to your server that works with v2 enpoint then It won’t work.
It looks likes the openid configuration at https://login.microsoftonline.com/common/.well-known/openid-configuration (my tenant value has something similar) has an property “jwks_uri”:“https://login.microsoftonline.com/common/discovery/keys” which when accessed has a document with a key entry that matches the kid value found in my access_token.
So it appears that the public key is available for validation.
It also looks like msal is issuing v1 access_token and that won’t change anytime soon.
It feels like this is a passport-azure-ad bug but it is still not clear. The wiki clearly points out supporting v1 metadata but it does not explicitly say that it supports v1 access_tokens.
I can’t get validation to work either. I’ve looked at AzureAD/passport-azure-ad#373 and https://stackoverflow.com/questions/52582547/passport-azure-ad-strategy-prototype-jwtverify-cannot-verify-token. I’m trying to do the v2 endpoint thing. My SPA passes the access token in the header back to a nodejs server where I am using passport BearerStrategy.
I believe that the access token does not give a signature error when decoding with jwt.ms but I’m not 100% sure jwt.ms is trying to verify.
I did notice in jwt.ms that I have a v1 of the token and my audience is set to https://graph.microsoft.com. I’m not sure if this has an effect. I have tried the v1 identityMetadata endpoint and setting audience in my config but that did not work either. Not sure why its v1 with this audience as that’s not in my SPA’s config anywhere and I signed up the app using the “v2” registration portal. My ID token is listed as v2, but the SPA MSAL generates a v1 access token. I’m using msal 0.2.4 in my SPA that sends the access token to my SPA data server.
Essentially what I am seeing is that based on AzureAD/passport-azure-ad#373, AzureAD/microsoft-authentication-library-for-js#3259, a major use case for this library does not work and it appears that the v1 access token issued by azure ad is part of the problem.
Another reference: https://stackoverflow.com/questions/54916165/passport-azure-ad-veriy-msal-js-token-with-bearer-strategy