We use Azure AD tokens as well. We have the following policies in place to do this:
1.Service callout to get token from azure:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ServiceCallout async="false" continueOnError="false" enabled="true" name="GetAccessToken"> <DisplayName>GetAccessToken</DisplayName> <Properties/> <Request clearPayload="true" variable="oauthRequest">
<Headers>
<Header name="Content-Type">application/x-www-form-urlencoded</Header>
</Headers>
<FormParams>
<FormParam name="client_id">*********</FormParam>
<FormParam name="client_secret">***************</FormParam>
<FormParam name="grant_type">client_credentials</FormParam>
<FormParam name="scope">https://graph.microsoft.com/.default</FormParam>
</FormParams> <Set> <Verb>POST</Verb> </Set> <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables> </Request> <Response>calloutResponse</Response> <HTTPTargetConnection> <Properties/> <URL>https://login.microsoftonline.com/XXXX-TENNANTID-XXXX/oauth/v2.0/token</URL> </HTTPTargetConnection> </ServiceCallout>
I get the access token successfully.
I have even tried using "https://login.microsoftonline.com/common/discovery/v2.0/keys"
But it got keys and it failed in validation
2. Retrieve keys from MS:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ServiceCallout async="false" continueOnError="false" enabled="true" name="SC-RetrieveMicrosoftKeys"> <DisplayName>SC-RetrieveMicrosoftKeys</DisplayName> <Properties/> <Request clearPayload="true" variable="myRequest"> <Set> <Verb>GET</Verb> </Set> <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables> </Request> <Response>msKeys</Response> <HTTPTargetConnection> <Properties/> <URL>https://login.microsoftonline.com/XXXX-TENNANTID-XXXX/discovery/v2.0/keys</URL> </HTTPTargetConnection> </ServiceCallout>
I recieve the keys success fully
2. Extract JWT from header:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ExtractVariables name="Extract-JWT-Assign-Message" enabled="true" async="false" continueOnError="false"> <Source>calloutResponse</Source> <JSONPayload>
<Variable name="access_token">$.access_token</Variable>
</JSONPayload> <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables> </ExtractVariables>
Access_token is extracted successfully
3. Verify JWT:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <VerifyJWT async="false" continueOnError="true" enabled="true" name="VerifyJWT"> <DisplayName>VerifyJWT</DisplayName> <Algorithm>RS256</Algorithm> <Source>authn.jwt</Source> <PublicKey> <JWKS ref="msKeys.content"/> </PublicKey> <Issuer>https://sts.windows.net/XXXX-TENNANTID-XXXX/</Issuer> <Audience ref="aud"/> <AdditionalClaims> <Claim name="roles" ref="active-directory.jwt.roles" type="string" array="true"/> </AdditionalClaims> </VerifyJWT>
Here I get error saying
{ "fault": { "faultstring": "Invalid token: policy(Verify-JWT-1)", "detail": { "errorcode": "steps.jwt.InvalidToken" } } }
Solved! Go to Solution.
I guess you are facing this problem , which is not a problem in Apigee, nor is it a problem in Azure per se, but rather, a misunderstanding of the token Azure creates and hands out to you. In my opinion the misunderstanding is Microsoft's fault, because ... they could have been clearer. But I guess that's irrelevant when you 're just trying to solve the problem.
When you request a token from Entra ID with a scope of https://graph.microsoft.com/.default
, you get a token that looks like a JWT, and can be decoded in the same way a JWT can be decoded, but it cannot be verified like a JWT. Despite appearances, it is not actually a JWT, so VerifyJWT will never work to "verify it". If that seems confusing to you, you are not alone. I find it confusing too. And that's the part I think Microsoft is responsible for. You can read in more detail than you probably want, here.
The solution is one of these two things:
https://graph.microsoft.com/.default
when requesting the token.https://graph.microsoft.com/.default
, but do not try to use VerifyJWT on the subsequently issued token.When you use the special scope of https://graph.microsoft.com/.default
, you're telling Azure AD (Entra ID): I want you to issue a token that will be used for the Graph API. That token looks-like-a-JWT-but-is-not. You should not try to verify it. Any necessary verification will be performed by Microsoft, when you present that token when invoking the graph API.
Check out this repo that validates both GCP & Azue JWT tokens in conditional steps, maybe helpful - https://github.com/tyayers/apigee-gcp-azure-auth-example
Invalid JWT means that something is incorrect in the configuration somewhere..
I guess you are facing this problem , which is not a problem in Apigee, nor is it a problem in Azure per se, but rather, a misunderstanding of the token Azure creates and hands out to you. In my opinion the misunderstanding is Microsoft's fault, because ... they could have been clearer. But I guess that's irrelevant when you 're just trying to solve the problem.
When you request a token from Entra ID with a scope of https://graph.microsoft.com/.default
, you get a token that looks like a JWT, and can be decoded in the same way a JWT can be decoded, but it cannot be verified like a JWT. Despite appearances, it is not actually a JWT, so VerifyJWT will never work to "verify it". If that seems confusing to you, you are not alone. I find it confusing too. And that's the part I think Microsoft is responsible for. You can read in more detail than you probably want, here.
The solution is one of these two things:
https://graph.microsoft.com/.default
when requesting the token.https://graph.microsoft.com/.default
, but do not try to use VerifyJWT on the subsequently issued token.When you use the special scope of https://graph.microsoft.com/.default
, you're telling Azure AD (Entra ID): I want you to issue a token that will be used for the Graph API. That token looks-like-a-JWT-but-is-not. You should not try to verify it. Any necessary verification will be performed by Microsoft, when you present that token when invoking the graph API.
Thank you this answers why it is failing!