TT Lab
Get started
Learn Learning paths Courses

Keycloak and Enterprise Identity

Parsing a JWT and Verifying Signature and Claims

Continue in TT Lab

Goal

By taking a JWT apart by hand, verifying its signature, and checking its claims, you understand exactly what the library was doing for you.

Why it matters

JWT verification is one line of library code, so it is easy to pass by without knowing the inside. But if you give that one line the wrong option, it is silently breached. A typical case is that if you do not turn on the aud check, a token for another API issued by the same authentication server passes at our API. If you do not verify alg, there was an actual incident where a token without a signature passed using the none algorithm. And the number you measure at the end of this lab matters — introspection is a network round trip on every request and local verification is a CPU computation. If ten microservices each ask, the authentication server becomes the bottleneck, so in practice you keep access tokens short at 5–15 minutes and use local verification. It is a trade that limits the revocation delay to the token lifetime.

Steps

  1. With /root/kc/wait.sh, wait up to 180 seconds until http://127.0.0.1:8080/realms/master returns 200. In /root/kc/ready.txt, write ready_seconds=<정수> (an integer).
  2. Obtain a token using the values in /opt/fixtures/kc/realm-info.env and save only the access token on one line to /root/kc/token.txt. It must be a string with 2 dots.
  3. With /root/kc/decode.py, save the header to /root/kc/header.json and the payload to /root/kc/claims.json. Both must be valid JSON.
  4. In /root/kc/claimcheck.txt, write iss=<값> aud=<값> sub=<값> exp_in=<남은초> (the values, and the seconds remaining). exp_in must be greater than 0.
  5. From http://127.0.0.1:8080/realms/labhub/protocol/openid-connect/certs, get the JWKS and save the key matching the header's kid to /root/kc/jwk.json. kty must be RSA.
  6. With /root/kc/verify.py, verify the signature and in /root/kc/verify.out write signature=valid alg=<값> (the value).
  7. Run the same verification with a token whose payload has been tampered with, and in /root/kc/tamper.out write signature=invalid.

Notes

Wait until Keycloak is ready

With /root/kc/wait.sh, wait up to 180 seconds until http://127.0.0.1:8080/realms/master returns 200. In /root/kc/ready.txt, write ready_seconds=<정수> (an integer).

It is a JVM, so startup is slow. Use a loop that polls for readiness and allow a generous maximum wait time.

Obtain a token

Obtain a token using the values in /opt/fixtures/kc/realm-info.env and save only the access token on one line to /root/kc/token.txt. It must be a string with 2 dots.

The information for the pre-imported realm is in /opt/fixtures/kc/realm-info.env. Send form data to the token endpoint.

Decode the header and payload

With /root/kc/decode.py, save the header to /root/kc/header.json and the payload to /root/kc/claims.json. Both must be valid JSON.

They are the first two of the three pieces separated by dots. Base64URL may lack padding, so you need to correct it.

Check the required claims

In /root/kc/claimcheck.txt, write iss=<값> aud=<값> sub=<값> exp_in=<남은초> (the values, and the seconds remaining). exp_in must be greater than 0.

Check the issuer, audience, expiry, and subject each. If you do not look at even one of them, it becomes a hole.

Find the signing key in the JWKS

From http://127.0.0.1:8080/realms/labhub/protocol/openid-connect/certs, get the JWKS and save the key matching the header's kid to /root/kc/jwk.json. kty must be RSA.

Pick from the list by the key identifier in the header. During a rotation, several keys can be there together.

Verify the signature

With /root/kc/verify.py, verify the signature and in /root/kc/verify.out write signature=valid alg=<값> (the value).

What is signed is the string of the header and payload joined with a dot. It is not the decoded JSON.

Confirm that a tampered token is rejected

Run the same verification with a token whose payload has been tampered with, and in /root/kc/tamper.out write signature=invalid.

It is normal for the signature to break if you change even one character of the payload.