Get hands-on experience with 20+ free Google Cloud products and $300 in free credit for new customers.

GenerateJWT is escaping slashes in claim values before signing

Hi,

I have this JWT:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0LXN1YmplY3QiLCJhdWQiOlsiYXVkaWVuY2UxIiwiYXVkaWVuY2UyIl0sImlzcyI6InVybjpcL1wvYXBpZ2VlLWVkZ2UtSldULXBvbGljeS10ZXN0IiwiZXhwIjoxNTkwNTM3OTA4LCJhY3Rpb25zIjoiYVwvYlwvYVwvYlwvYVwvYyIsImlhdCI6MTU5MDUwOTEwOCwianRpIjoiM2M3NGEyM2QtZTU1ZS00N2I5LTkxZWUtMDUxOGQyY2MwYzFmIn0.nkD2RUr5NNGG3bqO0UMCO33MD_ld91CI0jgTdZhmy1M

This JWT have custom claim called actions:

"actions": "a/b/a/b/a/c"

as you can see, this string have the '/' element.

Once we are decoding this in Apigee (or other tools) - we can see in the final variable, that this claim getting escaped: (payload-json var)

{"sub":"subject-subject","aud":["audience1","audience2"],"iss":"urn:\/\/apigee-edge-JWT-policy-test","exp":1590540403,"actions":"a\/\/b\/\/a\/\/b\/\/a\/\/c","iat":1590511603,"jti":"675646c9-ec88-4fe0-9689-5aef7f75095e"}

and also urn...

My backend can't work with this escaping

Any ideas?

Solved Solved
1 11 3,299
1 ACCEPTED SOLUTION

Thanks for that. what I understand is:

  • you're using Apigee GenerateJWT to generate a JWT
  • one of the claims is created via an AdditionalClaims/Claim element, using a variable reference
  • you want the API proxy to send the output of that (the generated JWT) to the backend (upstream) system.
  • The people that run the upstream system are saying that the JWT they receive is "broken" - I guess because the forward-slashes are being backslash-escaped.

I just tested GenerateJWT with your configuration and a variable that holds "a/b/c/d" and I see that the base64-decoded payload looks like this:

{"aud":["audience1","audience2"],"iss":"urn:\/\/apigee-edge-JWT-policy-test","exp":1590643673,"actions":"a\/b\/c\/b","iat":1590614873,"jti":"a264a537-32cd-4fd1-9cda-507f96566297"}

...which is consistent with my understanding of your description.

It's unfortunate, but: Escapes of forward slash are valid though not required in JSON.

A correct processor of JSON must be able to handle backslash-escaped forward slashes. Since the JWT payload is simply JSON, While you might look at the behavior of Apigee GenerateJWT here and say "I don't want the escapes" , it is correct and valid. The way to avoid the problem is: the backend needs to handle the escaped JSON properly.

One *possible* workaround is to use the GenerateJWS policy. The GenerateJWS policy signs a payload, similar to the way the GenerateJWT signs a payload. But, the payload in a JWS can be "anything", any stream of bytes. The GenerateJWS policy doesn't assume you want a JSON payload and doesn't quietly insert backslash-escapes for forward-slashes if the payload happens to be JSON.

The policy configuration would look like this:

<GenerateJWS name='GenerateJWS-1'>
  <Algorithm>HS256</Algorithm>
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <SecretKey>
    <Value ref='private.secretkey'/>
    <Id>cb24a396-4414-4793-b4ce-90333b694bff</Id>
  </SecretKey>
  <Payload ref='jws-payload'/>
  <AdditionalHeaders>
    <Claim name='typ'>JWT</Claim>
  </AdditionalHeaders>
  <OutputVariable>output-jwt</OutputVariable>
</GenerateJWS>

And before that, you would need to set into the variable "jws-payload", the exact JSON you want to sign. You could use something like this:

<AssignMessage name='AM-Payload'>
  <AssignVariable>
    <Name>jws-payload</Name>
    <Value>{"aud":["audience1","audience2"],"iss":"urn://apigee-edge-JWT-policy-test","exp":1590643673,"actions":"a/b/c/b","iat":1590614873,"jti":"a264a537-32cd-4fd1-9cda-507f96566297"}</Value>
  </AssignVariable>
</AssignMessage>

Be aware: the iat and exp claims are fixed in this example, and that's probably not what you want. So a better method may be to use a JS step to create the payload and initialize the iat/exp claims as you prefer. Maybe like this:

<Javascript name='JS-SetPayload' >
  <Source>
var payload = {
  "aud":["audience1","audience2"],
  "iss":"urn://apigee-edge-JWT-policy-test",
  "actions":"a/b/c/b"
};
var lifetimeInSeconds = 600;
var now = new Date();
now = Math.round(now.valueOf()/1000);
payload.iat = now
payload.exp = now + lifetimeInSeconds;
context.setVariable('jws-payload',JSON.stringify(payload));
  </Source>
</Javascript>

View solution in original post

11 REPLIES 11