How to verify UAA Tokens, part 2 — “Offline Validation”

Joshua Casey
4 min readApr 18, 2020

In part 1 [1] I discussed a strategy that we call “online validation” of tokens, by calling the UAA’s /introspect [2] endpoint and passing in a token for validation. Here I’d like to discuss an alternative strategy known as “offline validation” of tokens. This only works with JWT tokens, since JWT tokens contain the permissions (“scopes”) and are signed by the UAA, so they can be validated and read by any other application.

A JWT token looks like this:

eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vbG9jYWxob3N0OjgwODAvdWFhL3Rva2VuX2tleXMiLCJraWQiOiJzYW1wbGVfcHJpdmF0ZV9rZXlfRE9fTk9UX1VTRSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5ZDM2MmEwMTlhYzA0MzNmYTdiOThjZmIyMjYwZDk5MSIsInN1YiI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiY2xpZW50cy5yZWFkIiwicGFzc3dvcmQud3JpdGUiLCJjbGllbnRzLnNlY3JldCIsImNsaWVudHMud3JpdGUiLCJ1YWEuYWRtaW4iLCJzY2ltLndyaXRlIiwic2NpbS5yZWFkIl0sInNjb3BlIjpbImNsaWVudHMucmVhZCIsInBhc3N3b3JkLndyaXRlIiwiY2xpZW50cy5zZWNyZXQiLCJjbGllbnRzLndyaXRlIiwidWFhLmFkbWluIiwic2NpbS53cml0ZSIsInNjaW0ucmVhZCJdLCJjbGllbnRfaWQiOiJhZG1pbiIsImNpZCI6ImFkbWluIiwiYXpwIjoiYWRtaW4iLCJncmFudF90eXBlIjoiY2xpZW50X2NyZWRlbnRpYWxzIiwicmV2X3NpZyI6IjFlZTRkNGUyIiwiaWF0IjoxNTg3MjIyMTEyLCJleHAiOjE1ODcyNjUzMTIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC91YWEvb2F1dGgvdG9rZW4iLCJ6aWQiOiJ1YWEiLCJhdWQiOlsic2NpbSIsInBhc3N3b3JkIiwiY2xpZW50cyIsInVhYSIsImFkbWluIl19.I4-C1VY1GmQH4bfAWLXCsxO44C6eUCmBugR30mS6RZmfSazLK5aaZfqVy3B_taOYzf6VcqYGjpRvh7gkQ_gDlbAfRjNizQnP_ZeB3dJwuv_ZGW2bcekcYIyLMp0EFbqExlv6VdCWGes9OmSnmsW0HngXKR_IldJ4rZDu-AyiiNg

When decoded from base64 and displayed as JSON, the token will read as shown below. To learn more about JWT tokens, visit jwt.io [3].

HEADER:
{
"alg": "RS256",
"jku": "https://localhost:8080/uaa/token_keys",
"kid": "sample_private_key_DO_NOT_USE",
"typ": "JWT"
}
PAYLOAD:
{
"jti": "9d362a019ac0433fa7b98cfb2260d991",
"sub": "admin",
"authorities": [
"clients.read",
"password.write",
"clients.secret",
"clients.write",
"uaa.admin",
"scim.write",
"scim.read"
],
"scope": [
"clients.read",
"password.write",
"clients.secret",
"clients.write",
"uaa.admin",
"scim.write",
"scim.read"
],
"client_id": "admin",
"cid": "admin",
"azp": "admin",
"grant_type": "client_credentials",
"rev_sig": "1ee4d4e2",
"iat": 1587222112,
"exp": 1587265312,
"iss": "http://localhost:8080/uaa/oauth/token",
"zid": "uaa",
"aud": [
"scim",
"password",
"clients",
"uaa",
"admin"
]
}
SIGNATURE:
The signature is not human-readable information.

What do you need to perform offline validation on this token?

  1. The base URL of the authorization server you trust (in this case https://localhost:8080/uaa). This should be provided to your application at runtime. If an attacker can set or override this URL, they can take over your application!
  2. The token that you are trying to validate (comes in as the value of an Authorization: bearer header)

To perform offline validation, make sure to validate that thejku header parameter [4] and the iss claim [5] both begin with the URL of the authorization server that you trust, in this case https://localhost:8080/uaa.

Next, you will need the signing token used by the authorization server. This is available at the URL in the jku claim. In this case (and for nearly all tokens issued by the UAA) it’s the UAA’s /token_keys endpoint [6]. Please check the docs for how to access this endpoint. The token’s kid header parameter [7] will identify which key was used to sign this particular token.

Below is a sample /token_keys response from the UAA used to generate the token above. This response can be cached in your application so that your application does not need to reach back out to the UAA for later validation requests. This is why it is called “offline validation” and using this mechanism can reduce network traffic, load on the UAA, and the time required to validate a token.

[
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "sample_private_key_DO_NOT_USE",
"alg": "RS256",
"value": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDfTLadf6QgJeS2XXImEHMsa+1O\n7MmIt44xaL77N2K+J/JGpfV3AnkyB06wFZ02sBLB7hko42LIsVEOyTuUBird/3vl\nyHFKytG7UEt60Fl88SbAEfsUJN1i1aSUlunPS/NCz+BKwwKFP9Ss3rNImE9Uc2LM\nvGy153LHFVW2zrjhTwIDAQAB\n-----END PUBLIC KEY-----",
"n": "AN9Mtp1_pCAl5LZdciYQcyxr7U7syYi3jjFovvs3Yr4n8kal9XcCeTIHTrAVnTawEsHuGSjjYsixUQ7JO5QGKt3_e-XIcUrK0btQS3rQWXzxJsAR-xQk3WLVpJSW6c9L80LP4ErDAoU_1Kzes0iYT1RzYsy8bLXncscVVbbOuOFP"
}
]

At this point we have all of the information needed to validate the token. I won’t go into details of this, since it’s likely very language- and library-specific. For a list of libraries that support token validation, see https://jwt.io/. Make sure the library supports the particular signing algorithm that the UAA uses — typically HS256, although the UAA can be configured to use RS256. See the token’s alg header parameter [8] for this value.

There are some tradeoffs with “offline validation”.

It means that your application is responsible for validating more than just the key signature — typically other fields such as iat and exp and aud may be used to validate the tokens. See [9] for further reading on this.

Note that the UAA may add additional signing keys — and if you’re using an older cached response from /token_keys your application may reject valid tokens. One approach to mitigate this is that when token validation fails, your application can refresh its cached /token_keys response and try to revalidate the token.

On the flip side — your application may accept tokens that were issued by the UAA but the UAA no longer uses that key. One approach to mitigate this is to automatically refresh the cached /token_keys response on a recurring basis to keep up to date with the UAA.

Also note that tokens may be invalidated by the UAA for a number of reasons. The client that requested the token may be removed, the user might be deleted, or the token might be revoked. Offline validation may still accept tokens even if the UAA itself will not!

If you have any questions, please reach out to us on CloudFoundry Slack channel #uaa.

--

--