Apigee X VerifyJWT policy - it seems to ignore the expiry of the JWT

 I have proxy that implements a " verify-jwt" policy and it is working fine.

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<VerifyJWT continueOnError="false" enabled="true" name="Verify-JWT">
  <DisplayName>Verify JWT</DisplayName>
  <Algorithm>HS256</Algorithm>
  <Source>request.header.auth-token</Source>
  <SecretKey>
    <Value ref="private.key"/>
  </SecretKey>
</VerifyJWT>

 

But I have noticed one problem. The token will not expired. The " jwt.Verify-JWT.is_expired" is always "false". It continues to work beyond the time specified on "jwt.Verify-JWT.claim.expired"(considering time offset).

I am looking your help.

Solved Solved
0 3 428
1 ACCEPTED SOLUTION

THANKYOU for that, that's super helpful. Here's what I see when I decode that JWT.

 

{
  "email": "First.Last@example.com",
  "id": "9830398309",
  "firstName": "First",
  "lastName": "Last",
  "divisionId": "01",
  "storeNumber": "001",
  "access_token": "anQPEsWSnJ7jYclbiYghusDFywhr",
  "expired": "2022-12-20T16:23:50.322Z",
  "iat": 1671549831
}

 

And I can see NO exp claim. There is a claim which is named "expired". But "expired" is not a registered claim name. The JWT specification, IETF RFC 7519, describes a number of "well known" registered claims and their meanings. Among them are:

name meaning
iss issuer; the party or system or actor that created the JWT
iat issued-at time, a number of seconds since UTC Jan 1, 1970
exp expiry time, number of seconds
aud audience - party that the JWT is intended for
sub subject, the party that the JWT refers to

And there are other claims as well. The use of any of these claims is optional. In other words, a JWT need not have any of the registered claims, in order to be a valid JWT. And, other claims, outside of those on the registered list, can also be included in a JWT payload. It is up to the issuer and validator of a JWT to handle those other claims, to confer meaning on those claims, and to act on those meanings. It's a very flexible system.

Your JWT does not include an exp claim. exp is the registered name of a claim that expresses the expiry. So, according to the definition of the registered claims for JWT, your JWT truly, actually does not expire. That's why VerifyJWT never rejects it. The Apigee policy is completely ignoring the "expired" claim, because a claim by that name is not defined to have any standard meaning. The presence of that claim doesn't mean that the JWT is invalid. But it doesn't provide any meaning to the VerifyJWT policy regarding validity.

That is why you see the behavior you reported. The VerifyJWT policy looks only for the exp claim when evaluating the expiry of the JWT. VerifyJWT won't consider any other claims when evaluating the expiry of the JWT. There's no way to tell VerifyJWT to "look for the expiry in a different claim."

If you want a JWT that expires, you need to connect with the JWT issuer party (whatever system that is) and ask or direct that system to provide a proper exp claim.

If that is not possible, then , I suppose you could work around this by building some logic that runs in the Apigee API gateway that evaluates the special custom "expired" claim, and compares it to the time "right now" and throws a fault if the expired claim refers to a time in the past. If I were doing this, I'd use Java Script. You'd need to parse the expired string, and then compare the Date value against "now".

But to my thinking, that's a security risk. Just because you CAN do it doesn't mean you SHOULD do it. As a security architect, I would reject a design that proposed to do things that way. The JWT standard specifies a standard exp claim for a reason. If you want expiring JWT, you should get the issuing system to use the standard claim.

View solution in original post

3 REPLIES 3

Can you share with me the entire JWT?  

If you are unwilling to share an example JWT that exhibits this behavior, I'm not sure how we can diagnose what you're seeing.  

In my tests, the expiry (exp claim) is validated. As an example, using this policy configuration: 

<VerifyJWT name='VerifyJWT-Signed-HS256'>
  <Algorithm>HS256</Algorithm>
  <Source>request.header.jwt</Source>
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <SecretKey>
    <Value ref='private.secret-key'/>
  </SecretKey>
</VerifyJWT>

Using a JWT with a 10-second lifetime, I see the expected results:

$ JWT=eyJhbGciOiJIUzI1NiIsImtpZCI6IkEzRjUzOTU2LUY0MDAtNEYzQy1CRDYyLTZGQTU4NEU3NDNFQiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJwZXJzb25AZXhhbXBsZS5jb20iLCJhdWQiOiJhYzE1OTVkNy00ZGNlLTQyODYtYTFiZi0wMWIzYzZjMzIyZGEiLCJyb2xlcyI6WyJSZWFkIiwiV3JpdGUiLCJNb2RpZnkiXSwicXVhbHMiOiJyZWNlaXZlciB0cmFuc2NvZGVyIHNlbmRlciIsImlhdCI6MTY3MTY1NzMwNywiZXhwIjoxNjcxNjU3MzE3fQ.hwScnB64BKJauGWO2V98rMfxzKf_7EM66bIw4vuZURM

$ curl -i -X GET  $endpoint/jlum-jwt/t2 -H jwt:$JWT
HTTP/2 200 
via: 1.1 google, 1.1 google
traceparent: 00-c30f6e551fd3f0a03ee8514db75ea18f-40217173e9339c9c-00
grpc-trace-bin: AADDD25VH9PwoD7oUU23XqGPAUAhcXPpM5ycAgA
humanized-expiry: 2022-12-21T21:15:17Z
content-type: application/json
apiproxy: jlum-jwt r1
x-request-id: dffac5c6-aeb0-4744-9645-9a744e6a677c
content-length: 24
date: Wed, 21 Dec 2022 21:15:13 GMT
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000

{
    "status" : "ok"
}


$ curl -i -X GET  $endpoint/jlum-jwt/t2 -H jwt:$JWT
HTTP/2 401 
content-type: application/json
apiproxy: jlum-jwt r1
x-request-id: a8f1222a-8bbf-4629-a9c6-8b7f6a9b17c8
content-length: 129
date: Wed, 21 Dec 2022 21:15:17 GMT
via: 1.1 google
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000

{"fault":{"faultstring":"The Token has expired: policy(VerifyJWT-Signed-HS256)","detail":{"errorcode":"steps.jwt.TokenExpired"}}}
                                                                        

Note the "humanized-expiry" header in the first response . This is derived from the exp claim in the JWT.  And I submitted the second request just a few moments after the first.  You can see that the proxy rejects the JWT.  This is as expected, because by the time I sent the 2nd request, the JWT  has expired. 

You can view the decoded example JWT I used

Note: The JWT is externally generated and the sample I am using looks like

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6IkZpcnN0Lkxhc3RAZXhhbXBsZS5jb20iLCJpZCI6Ijk4MzAzOTgzMDkiLCJmaXJzdE5hbWUiOiJGaXJzdCIsImxhc3ROYW1lIjoiTGFzdCIsImRpdmlzaW9uSWQiOiIwMSIsInN0b3JlTnVtYmVyIjoiMDAxIiwiYWNjZXNzX3Rva2VuIjoiYW5RUEVzV1NuSjdqWWNsYmlZZ2h1c0RGeXdociIsImV4cGlyZWQiOiIyMDIyLTEyLTIwVDE2OjIzOjUwLjMyMloiLCJpYXQiOjE2NzE1NDk4MzF9.xkabQ00uuPWKckj_WtNCRzHf44SvMhkd4Gb3LEhn3S4

THANKYOU for that, that's super helpful. Here's what I see when I decode that JWT.

 

{
  "email": "First.Last@example.com",
  "id": "9830398309",
  "firstName": "First",
  "lastName": "Last",
  "divisionId": "01",
  "storeNumber": "001",
  "access_token": "anQPEsWSnJ7jYclbiYghusDFywhr",
  "expired": "2022-12-20T16:23:50.322Z",
  "iat": 1671549831
}

 

And I can see NO exp claim. There is a claim which is named "expired". But "expired" is not a registered claim name. The JWT specification, IETF RFC 7519, describes a number of "well known" registered claims and their meanings. Among them are:

name meaning
iss issuer; the party or system or actor that created the JWT
iat issued-at time, a number of seconds since UTC Jan 1, 1970
exp expiry time, number of seconds
aud audience - party that the JWT is intended for
sub subject, the party that the JWT refers to

And there are other claims as well. The use of any of these claims is optional. In other words, a JWT need not have any of the registered claims, in order to be a valid JWT. And, other claims, outside of those on the registered list, can also be included in a JWT payload. It is up to the issuer and validator of a JWT to handle those other claims, to confer meaning on those claims, and to act on those meanings. It's a very flexible system.

Your JWT does not include an exp claim. exp is the registered name of a claim that expresses the expiry. So, according to the definition of the registered claims for JWT, your JWT truly, actually does not expire. That's why VerifyJWT never rejects it. The Apigee policy is completely ignoring the "expired" claim, because a claim by that name is not defined to have any standard meaning. The presence of that claim doesn't mean that the JWT is invalid. But it doesn't provide any meaning to the VerifyJWT policy regarding validity.

That is why you see the behavior you reported. The VerifyJWT policy looks only for the exp claim when evaluating the expiry of the JWT. VerifyJWT won't consider any other claims when evaluating the expiry of the JWT. There's no way to tell VerifyJWT to "look for the expiry in a different claim."

If you want a JWT that expires, you need to connect with the JWT issuer party (whatever system that is) and ask or direct that system to provide a proper exp claim.

If that is not possible, then , I suppose you could work around this by building some logic that runs in the Apigee API gateway that evaluates the special custom "expired" claim, and compares it to the time "right now" and throws a fault if the expired claim refers to a time in the past. If I were doing this, I'd use Java Script. You'd need to parse the expired string, and then compare the Date value against "now".

But to my thinking, that's a security risk. Just because you CAN do it doesn't mean you SHOULD do it. As a security architect, I would reject a design that proposed to do things that way. The JWT standard specifies a standard exp claim for a reason. If you want expiring JWT, you should get the issuing system to use the standard claim.