JWT validation Failed with Invalid Key configuration error

Hi All,

 

I am facing below issue in verifying the JWT in APIGEE. I am using below code in Verify JWT policy 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<VerifyJWT async="false" continueOnError="false" enabled="true" name="Verify-JWT-1">
<DisplayName>Verify JWT-1</DisplayName>
<Algorithm>RS256</Algorithm>
<PublicKey>
<JWKS ref="jwksResponse.content"/>
</PublicKey>
</VerifyJWT>

 

Before that i am getting the JWKS values using Service Callout policy

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ServiceCallout async="false" continueOnError="false" enabled="true" name="SC-Get-JWT-Public-Key">
<DisplayName>SC Get JWT Public Key</DisplayName>
<Request clearPayload="false" variable="jwksRequest">
<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
</Request>
<Response>jwksResponse</Response>
<HTTPTargetConnection>
<Properties/>
<URL>https://accounts.eu1.gigya.com/accounts.getJWTPublicKey</URL>
</HTTPTargetConnection>
</ServiceCallout>

 

error i am getting as like below 

 

{"fault":{"faultstring":"Invalid Key configuration : policy(Verify-JWT-1) element(PublicKey)","detail":{"errorcode":"steps.jwt.InvalidKeyConfiguration"}}}

 

I tried many ways but i am not able to find the solution. Could anyone help on this please.

 

Thanks in advance

Vignesh 

 

I followed @dchiesa1 video to create JWT flow for validation   

Solved Solved
0 13 3,025
1 ACCEPTED SOLUTION

Yes, I'm sorry, there's a mismatch between what Apigee is expecting, and what Gigya is sending. 

Apigee is expecting a JWKS, a JWK Set.  A Set of JWK things.  Each JWK looks like: 

{ 
 "kty": "RSA",
 "n": "qoQah4...",
 "e": "AQAB",
 "alg": "RS256",
 "use": "sig",
 "kid": "REQ0M...",
  ...other fields possible here...
}

And a JWKS is just a set of those.  It looks like this: 

{
  "keys": [
    {"kty":"RSA","n":"...", "e" : "..." ... }, 
    {"kty":"RSA","n":"...", "e" : "..." ... }, 
    {"kty":"RSA","n":"...", "e" : "..." ... }, 
      ...
   ]
}

In other words, Apigee is expecting a JSON that contains a single member "keys", which has an array of JWK.  Your endpoint is sending a single JWK.  That's the reason for the problem .  The key that Gigya is sending is valid and good, but Apigee isn't smart enough to figure out that it's exactly one key.  Apigee is looking for a JWKS. 

How to fix this? 

The way I might do it: use ServiceCallout to the Gigya URL to obtain the JWK.  Then use a Javascript policy to build a JWKS from that.  It would look like this: 

// In the line below, replace serviceCalloutResponse with the name of the 
// response variable you used in your ServiceCallout policy.  
var r = JSON.parse(context.getVariable('serviceCalloutResponse.content'));
var jwks = { keys : [ r ] } ; 
context.setVariable('myjwks', JSON.stringify(jwks));

Then, specify in your VerifyJWT policy: 

<VerifyJWT name="Verify-JWT-1">
  <Algorithm>RS256</Algorithm>
  <PublicKey>
    <JWKS ref="myjwks"/>
  </PublicKey>
</VerifyJWT>

This will "work" , but if you implement it naively, then ServiceCallout will invoke the public key endpoint EVERY TIME you verify a JWT.  Which is unnecessary.  So to be smarter, you will need to insert the response into a cache.  Use PopulateCache and LookupCache for that purpose.

 

The other alternative is to somehow persuade Gigya to return a JWKS directly, instead of a single JWK.  I don't know Gigya and I don't know if that is possible.  The VerifyJWT policy will automatically cache the JWKS when you specify the JWKS URL directly. 

 

 

View solution in original post

13 REPLIES 13

are you certain that the response you get from https://accounts.eu1.gigya.com/accounts.getJWTPublicKey holds a JWKS ?

When I invoke that url, I get this response: 

{
  "callId": "331a3a777ac94490b2add134e50d47c2",
  "errorCode": 500001,
  "errorMessage": "General Server Error",
  "apiVersion": 2,
  "statusCode": 500,
  "statusReason": "Internal Server Error",
  "time": "2021-08-06T23:42:47.415Z"
}

So I suggest you check that URL, and the response you are getting.  It should be a JWKS.  As in this format: 

{
  "keys": [
    {"kty":"EC","crv":"P-256","kid":"0df83598","x":"xbgFmnePOYSPqIACzK-OQtgq1PLlyGYX2AXV2mr5BWg","y":"ofxJZqAaYEuP3_5TaM7XHwD9lCZT-1sBLF0X-EAQI4U"},
    {"kty":"RSA","e":"AQAB","kid":"1694fced","n":"o9ZsVE6Ch918fRJovTVQVlZdECIvUrbwwlR99ZuNHOEF7qMQBTaBAPDL6Qd3t4NtpyXEQD7cz7AG0NI9yrbBBdbGyEkLRMLqzn6AmwY5cGOiW6-5tZ9_W9rbnUWs5QPI9Qv7c8SLuG8_Vkn8ns4PmtxZCes4CWO3neLpkCpi9y6gqXpd0Uru3eNJ_BNFJa37F4Ylqti0tBTJaDsgmfNyEL4INlD87GgKWQJn0wNsK6TYyqd7_aHO_ixCXOVD3NeCn1uLuu2Vftc4Ymfg6Llfl9HDB1O793JvYx4H8D5h2UrCqEY6zN9H9R2-crqhYB0xz4n_NXUv-dIcEkjOetvfUHcTp8YVnZW982E_jvxDqYjHrQMki77oBEc4lTcCDG_ldiSlpjhbOiA1fHB5FmZrvskuoRvQWyjY2vx2A1v9u6rtSOVHxiPLxPeCVckEdV5_gB1Dk1yKW4bHddURqVEugZ8C8zJ6xEN_0VuV4KWkIepAzzBV2WxKcX8riyv9Eb0_Xm_z2sVkjts1eJoJ9Y2fLY_o3JPg1bXGNYpNndaAQHLQ9NJGeRC1enhjQAQhMWYrljMJTcJIp5-vbbyYrIYiFeCGNU5AsDzm9huO518V0pXre1f10bkoxISBiomOmAxel2BfBeMJkL2OhInwFSVEF6vjpVcYsiyKvKLqmpVBTJc"},
    {"kty":"EC","crv":"P-521","kid":"f8b8b670","x":"AeFeZ_ePkLIG4DEJCtmHXokJ9uYsv2wH6Ctw93KAx204GlKqn-2VXDdauTriPHf5TaYBT1cxGw1UPO-Qj4Hp6hkY","y":"ATfHchqpIOhkxDalD6zzwNQs4WerWwQmv9dxkJyegliEuSyGdo4xzTy5muGi3sVE0Xbk1z_GEGPfUOIp8ZzvAEKj"},
    {"kty":"RSA","e":"AQAB","kid":"6dff2a41","n":"kOchCLm5LXBvZ1pwfOzGHYks_t-lxccqDoRuW0AJq0b6iFfwjop2f5USJyUOgK2Wo4tOxrQEWSI-4Pyw1uQ5SpNEQ1eZdFzfQdenewVKFday_Uf7ZfzocJIEtR5Sl25YKzlNI2GD5WwNcAy5UvniPm54yFOnDGmzRc2yILB-5cMzsmkFLophaMIl9DX6m-RuCdN3SzM8r9ug1o0UmyG9JYbT1pZNC7vlfMkfQMW9ux-MKv4_CMJRTcrvx0p4sbnKGviDla8FZ4SEu9Q5sqrxqEEzlgz0QJBm4537Q-w5WPow5GQzHq9T9dl4Md1VAFGwiuTs46HxP8VeQ8Y4ln3Oqw"},
    {"kty":"EC","crv":"P-384","kid":"28de17d1","x":"NPWNZPt5IfMvoIe41GQ-M9PZlr6MFqZnW80l6wMibK-xX-CF3bEa0TSt57wO0erP","y":"zbU4GGfArQLD2xIIYr7N5mxAhY5U699ZPhubxslR-wgnsezf5xKIsu6jYuxLmDNT"}
  ]
}

If you would like to try a test JWKS, you can use this endpoint: https://jwks-service.appspot.com/

Thank you for your quick response. Sorry i didn't add secret key and verb POST in the above post(in service callout policy) to get JWKS. That's why you got Internal server error. Below is the response from https://accounts.eu1.gigya.com/accounts.getJWTPublicKey endpoint. 

 

{
"callId": "cc1d907f0ce747ea8b4fdf1fc8b8082e",
"errorCode": 0,
"apiVersion": 2,
"statusCode": 200,
"statusReason": "OK",
"time": "2021-08-07T07:17:06.032Z",
"kty": "RSA",
"n": "qoQah4MFGYedrbWwFc3UkC1hpZlnB2_E922yRJfHqpq2tTHL_NvjYmssVdJBgSKi36cptKqUJ0Phui9Z_kk8zMPrPfV16h0ZfBzKsvIy6_d7cWnn163BMz46kAHtZXqXhNuj19IZRCDfNoqVVxxCIYvbsgInbzZM82CB86iYPAS7piijYn1S6hueVHGAzQorOetZevKIAvbH3kJXZ4KdY6Ffz5SFDJBxC3bycN4q2JM1qnyD53vcc0MitxyIUF7a06iJb5_xXBiA-3xnTI0FU5hw_k6x-sdB5Rglx13_2aNzdWBSBAnxs1XXtZUt9_2RAUxP1XORkrBGlPg9D7cBtQ",
"e": "AQAB",
"alg": "RS256",
"use": "sig",
"kid": "REQ0MUQ5N0NCRTJEMzk3M0U1RkNDQ0U0Q0M1REFBRjhDMjdENUFBQg"
}

Hope this is JWKS .  

Header value i am passing in header of the request is below. 

Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IlJFUTBNVVE1TjBOQ1JUSkVNemszTTBVMVJrTkRRMFUwUTBNMVJFRkJSamhETWpkRU5VRkJRZyIsInR5cCI6IkpXVCIsImRjIjoiZXUxIn0.eyJzdWIiOiJvclgwaTBTcTJGTE5TbmxmZWlpaXQ1dnp0RGx0YWRfNy1UZ3lpd0VwcHE0IiwianRpIjoiYTZlODViMjQtY2VjMC00YWZjLTk4ODYtY2NjZjdlZjllM2E5IiwiZXhwIjoxNjI4MTUyOTg1LCJpYXQiOjE2MjgxNTI5MjUsImlzcyI6Imh0dHBzOi8vZmlkbS5naWd5YS5jb20vand0LzNfMHBhOVhCNXRfSGRxUVl5RVZnQnM5aXN6a2UtRG1Gdll4ZWZCdktkb3BpRGpZR0poaGFUWUJySk5UOU13eVNmdS8ifQ.jBwLvOK_e-HHkLa9Nl2rPaic30BEqZzuvr4tE9cNZBfpflWhokjXkzo5JCTgUKtI88gjGX3bdIC0p6MK_esUCl3zflIsykQmU8xdRl1Wl4E_yyq9w2O_FUSry-cpmByqY1_39FgY-yKNEuoKt1OW9jDfQxSoAVCG2iMHPJlsW2_98sdjxh7KeIHfXucoyOUuyfPTsCdyLxjUFLOmk6jk3cG2XsN25NuwUyjgGtjpl08RSlE56HlQkVRrWaDQ-GKyAqniTAsTyH9UbAu3tWYc8n5e-U-JLnGOmW1wlItuCisu08hxao1f0CPwbIggvonTmYGHy0smxw9I6HYn_w1IBg

 

Error from APIGEE trace

APIGEEErrorSnap.JPG

can you have a look and suggest please.

Hello @Vignesh0808 ,

Did you get a chance to go thru validate JWT policy documentation?Looks like JWKS format issue.

 

Some of the information how to troubleshoot such issues.

https://docs.apigee.com/api-platform/troubleshoot/policies/deployment/verify-JWT-deployment-errors

Let us know if it doesn't help and we can further diagnose the issue but make sure go thru step by step and it should resolve.

Yes, I'm sorry, there's a mismatch between what Apigee is expecting, and what Gigya is sending. 

Apigee is expecting a JWKS, a JWK Set.  A Set of JWK things.  Each JWK looks like: 

{ 
 "kty": "RSA",
 "n": "qoQah4...",
 "e": "AQAB",
 "alg": "RS256",
 "use": "sig",
 "kid": "REQ0M...",
  ...other fields possible here...
}

And a JWKS is just a set of those.  It looks like this: 

{
  "keys": [
    {"kty":"RSA","n":"...", "e" : "..." ... }, 
    {"kty":"RSA","n":"...", "e" : "..." ... }, 
    {"kty":"RSA","n":"...", "e" : "..." ... }, 
      ...
   ]
}

In other words, Apigee is expecting a JSON that contains a single member "keys", which has an array of JWK.  Your endpoint is sending a single JWK.  That's the reason for the problem .  The key that Gigya is sending is valid and good, but Apigee isn't smart enough to figure out that it's exactly one key.  Apigee is looking for a JWKS. 

How to fix this? 

The way I might do it: use ServiceCallout to the Gigya URL to obtain the JWK.  Then use a Javascript policy to build a JWKS from that.  It would look like this: 

// In the line below, replace serviceCalloutResponse with the name of the 
// response variable you used in your ServiceCallout policy.  
var r = JSON.parse(context.getVariable('serviceCalloutResponse.content'));
var jwks = { keys : [ r ] } ; 
context.setVariable('myjwks', JSON.stringify(jwks));

Then, specify in your VerifyJWT policy: 

<VerifyJWT name="Verify-JWT-1">
  <Algorithm>RS256</Algorithm>
  <PublicKey>
    <JWKS ref="myjwks"/>
  </PublicKey>
</VerifyJWT>

This will "work" , but if you implement it naively, then ServiceCallout will invoke the public key endpoint EVERY TIME you verify a JWT.  Which is unnecessary.  So to be smarter, you will need to insert the response into a cache.  Use PopulateCache and LookupCache for that purpose.

 

The other alternative is to somehow persuade Gigya to return a JWKS directly, instead of a single JWK.  I don't know Gigya and I don't know if that is possible.  The VerifyJWT policy will automatically cache the JWKS when you specify the JWKS URL directly. 

 

 

Thanks @dchiesa1 , @API-Evangelist . I got the JWKS from Gigya and tried, it worked. Thank you very much for explaining it detailly. 

Hello @dchiesa1 

when you specify the JWKS URL directly

Do you have any solution for the case when URL is different per Apigee environment? It appears VerifyJWT policy allows only single URL.

Kind regards,
Maxim

Yes. In Apigee X and hybrid you can use a uriRef attribute, which specifies a context variable that holds a URI .

 

<VerifyJWT name="vjwt-1">
 <PublicKey>
  <!-- option 1: static URI -->
  <JWKS uri="https://uri-to-jwks.com/.wellknown/jwks"/> <!-- static URI -->

  <!-- option 2: variable that contains a URI -->
  <JWKS uriRef="flow-var"/>

 ...

 

I do not know if this has been rolled out to Apigee Edge, just yet.

The update to the documentation, to include the description of this element, is pending.

So to use a different URI for different environments, use a properties file or some other environment-scoped variable to contain the URI.  

Hello @dchiesa1 ,

Thanks for your swift response. Attribute uriRef is exactly what we're looking for. But unfortunately seems this hasn't been rolled out to Apigee Edge yet. I'm getting the error when saving configuration

 

Error Saving Revision. Empty Element for Key Configuration : policy(Verify-JWT) element(PublicKey/JWKS).

 

What would you recommend in this case? Thank you in advance.

Kind regards,
Maxim

Yes, that's correct. That uriRef attribute is supported in Apigee X and hybrid only, at this time. 

 

There is a workaround: use ServiceCallout and specify the JWKS content directly. 

Within your proxy, use a ServiceCallout that points to a different URL for your different environments, and then specify the scResponse.content as the ref for the JWKS element.  Like this: 

  <PublicKey>
    <JWKS ref='scResponse.content'/>
  </PublicKey>

The scResponse must be the name of the response variable you use in the ServiceCallout policy.  You will  need to dynamically assign the URL for that ServiceCallout, to be different based on the environment. 

If you do this, you will want to cache the response for that ServiceCallout so that you don't retrieve the JWKS for every API request. 

A good, simple way to do the caching might be to wrap the JWKS retrieval into another Apigee API Proxy , and enable Response Caching there.  In that case, you would use a ServiceCallout from proxy1, pointing to proxy2, which is a JWKS proxy.  That JWKS proxy should auto-select it's target URL based on its environment. And that JWKS proxy should use Response caching. 

And you'd want to use localTarget connection between proxy1 and proxy2 to make this even more efficient.

 

Hello @dchiesa1 

Indeed everything works as expected. Managed to retrieve JWKS content via ServiceCallout and cache it, per environment. Thanks a lot for solving problem.

PS Using build-in Populate/Lookup Cache policies for caching seems a bit more straightforward option though. But maybe relevant only for my particular setup. 

Kind regards,
Maxim

Excellent

hi, we are getting same error, but sometime it worked and some time we are getting the same error, the JWKS we receive is :

{
"keys":[
{
"kty":"RSA",
"use":"sig",
"kid":"??????",
"e":"AQAB",
"n":"????????????????????",
"alg":"RS256"
},
{
"kty":"RSA",
"use":"sig",
"kid":"???????????????????????",
"x5t":"????????",
"e":"AQAB",
"n":"????????????????????????",
"x5c":[
"???????????????????????????????????"
],
"alg":"RS256"
}
]
}

 

and in the policy we use VerifyJWT :

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<VerifyJWT async="false" continueOnError="false" enabled="true" name="VerifyJWT">
<DisplayName>VerifyJWT</DisplayName>
<Algorithm>RS256</Algorithm>
<PublicKey>
<JWKS uri="https://IP/jwks"/>
</PublicKey>
<OutputVariable>jwt-variable</OutputVariable>
</VerifyJWT>

 

 

any idea??

ask a new question, and state clearly what error you are seeing.  Not "same error".  We've discussed several errors on this thread.

If you make a new thread it will be easier for people to keep your question separate from other questions , and easier for them to help you.