A JSON Web Token is three sections separated by dots, and only one of them is a control. The first two are encoded, not encrypted, which means they are readable by anyone holding the token and writable by anyone who can construct a string. The third is the signature, and it is the only reason any of the other two mean anything.
That single structural fact explains every mistake the room walks through, and it is the same fact behind today's field note on an agent memory layer that accepted any well-formed token because it decoded the claims and never verified them.
01 · Scope and authorisation
This companion documents the public TryHackMe JWT Security room, an introductory room on token-based authentication in APIs. I worked through it in my own session, everything below stays inside that environment, and I have published no flags, no quiz answers, no credentials, no target addresses and no working forgery procedure. Where the room asks you to construct or modify a token, I describe the class of mistake and the reasoning that identifies it, so you work the specifics out yourself rather than replaying mine.
The room covers what JWTs are and where they are used, why token-based authentication became the norm for APIs, what a correctly implemented signature check looks like, the common ways validation is implemented wrongly, and the disclosure problem that arises from the payload being readable by design. It maps to CWE-347, improper verification of a cryptographic signature, and CWE-200, exposure of sensitive information to an unauthorised actor, and it is the best small laboratory I know for learning the difference between reading a credential and validating one.
02 · Learning objectives
- Describe a JSON Web Token as three sections and state which one carries the security.
- Explain why encoding is not encryption and what that means for every claim in the payload.
- Distinguish a decode from a verification, and describe what each one proves.
- Explain why the algorithm must be chosen by the verifier rather than read from the token.
- Recognise sensitive information disclosure in a token even when the signature check is perfect.
- Write up a token-handling defect at the stage it actually failed, rather than as generic broken authentication.
03 · Prerequisites and the loop I use
The room assumes basic web familiarity, comfort reading HTTP requests, and enough patience to look at a token's contents rather than treating it as an opaque string. The loop I applied is the one I use on any credential mechanism:
- Observe the credential's structure and where it travels, at the level of the individual sections rather than the whole string.
- Hypothesise which property the system is relying on and where that property is established.
- Choose the least invasive modification that would show whether the property is actually checked.
- Interpret the response as a statement about the verifier, not about the specific token you built.
- Update the model and mark which stage failed: receive, parse, verify or authorise.
The fourth step is the one worth holding on to. When a modified token is accepted, the finding is never "this token worked". It is that the verifier is not checking the thing you assumed it was checking, and that conclusion generalises to every token the service will ever see.
04 · The model: three sections, one control
Before touching the lab, hold the structure apart, because everything else follows from it.
A token has a header, which names the algorithm. A payload, which carries the claims the issuer wants to assert: a subject, roles, an expiry, whatever the application needs. And a signature, which binds the first two sections to a key so that a verifier can tell whether they were produced by someone holding that key.
The critical property is that the first two sections are base64url encoded, not encrypted. That is deliberate: the client needs to read the claims, so the format is designed to be legible. The consequence is that the header and payload are public to anyone who holds the token, and constructible by anyone at all. Reading them requires no key, no permission and no tooling.
So the security of the entire mechanism rests on one operation, and the room states it in a way I would keep: all of the security is in the signature. Everything the token asserts is exactly as trustworthy as the strength and correctness of that check, and if the check is wrong for any reason, an attacker can put any claim they like into a token and the server will believe it.
That gives the model three ways to fail, and the room covers all three: trust the claims without verifying, verify but let the token choose how, and verify correctly while still leaking what should not be public.
05 · Stage one: read the token as three sections
The room's first practical step is worth slowing down, because most testers read a token as one opaque string and move on. The productive reading is to separate it into its sections and ask a question about each.
Of the header: what does it claim, and does the verifier have any opinion about that claim. Of the payload: what is being asserted, and would I be comfortable if a stranger read all of it. Of the signature: does anything here depend on it, and where is that dependency implemented.
Two habits follow from doing this consistently. The first is that the payload stops being a secret. Once you have read claims out of a token you hold, the question of what else is in there becomes obvious, and the answer is usually more than the application needed. The second is that the signature stops being abstract. It is either checked somewhere specific in the code, or it is not checked, and the difference is visible rather than philosophical.
The observation to record is a column per token: what the payload asserts, and which of those assertions anything actually verifies. Where an assertion is read by the application and never verified, you have found the shape of the failure before constructing anything.
06 · Stage two: the algorithm claim is not the algorithm
This is the part of the room that most repays attention, because the mistake it describes is a design error rather than a coding slip.
The header of a token names the algorithm used to produce the signature. That is a statement by the token about itself, and until the signature has been verified, the header is attacker-controlled text. A verifier that reads the algorithm out of the header and then performs the corresponding check is therefore allowing the attacker to choose how the check is performed. Among the choices available in the format is the case where no signature is expected at all.
The general principle is worth stating without the specifics, because it generalises well beyond tokens: a credential must not be permitted to select the mechanism that validates it. The verifier holds the algorithm as configuration, refuses anything that disagrees, and treats a mismatch as a failure rather than as an instruction.
There is a second, subtler version of the same error in the room's material, and it concerns the two families of algorithm. One family uses a shared secret, so the same key both signs and verifies. The other uses a key pair, so the verifying key is public by design. A verifier that accepts either family without pinning which one it expects can be induced to treat a public key as a shared secret, at which point the attacker can produce a valid signature because the key needed to do so is published. Both versions reduce to the same rule: the verifier decides, and the token is checked against that decision.
07 · Stage three: what a correct check actually requires
Having established that verification is the whole of the security, the room makes you build one properly, which is where the difference between a check and a sufficient check becomes concrete.
A sufficient verification does four things. It fixes the accepted algorithms rather than accepting what the token declares. It confirms the signature against the correct key for that algorithm. It validates the claims that carry security meaning, of which expiry is the one most often skipped and the one with the clearest consequence, since a token that never expires cannot be revoked by waiting. And it confirms the token was intended for this service, which is the audience question and the one that survives a perfect signature check.
That last point is worth dwelling on, because it is the failure mode that looks correct from every angle. A token can be authentically signed by the expected issuer, unexpired, and carrying legitimate claims, and still be the wrong credential for the service receiving it, because it was issued for a different destination or with a broader authority than the request implied. Nothing about the signature check catches this, which is why verification and authorisation are separate stages rather than one.
08 · Stage four: the payload is public
The room's disclosure section is the one that changes how people design tokens rather than how they validate them, and it follows directly from the encoding property established at the start.
If the payload is readable by anyone holding the token, then everything placed in it should be treated as published to that audience. That includes the person using the application, anyone who can observe the token in transit or in a log, and anyone the token is ever shared with by accident, which happens routinely through support tickets, screenshots, referrer headers and analytics. A correct signature does not change any of this. It proves the claims were issued by the right party and says nothing about whether they should have been issued at all.
The practical test is a sentence, and it is uncomfortable on purpose: read the payload as though it were going to be posted publicly, and if any claim fails that test, it does not belong in the token. Identifiers that are meaningful only internally, internal role or group names, and anything describing the state of an account are the usual offenders. The payload is a place to put a reference, not a place to put the thing itself.
09 · Defensive controls
| Weakness | Durable control | What to log and review |
|---|---|---|
| Claims read but not verified (CWE-347) | Verify on every path that accepts a token, and treat decoding as a display concern with no security role. | Rejected tokens and the reason for rejection, since a verifier that never rejects anything is not verifying. |
| Algorithm chosen by the token | Pin the accepted algorithms on the verifier, reject mismatches, and refuse any no-signature case unconditionally. | Any request whose token declares an algorithm the service does not expect. |
| Claims not validated after a correct signature | Validate expiry, issuer and audience as part of verification, not as a separate optional step. | Tokens accepted with an audience or issuer outside the expected set, and tokens with no expiry. |
| Sensitive information in the payload (CWE-200) | Treat the payload as public: carry a reference to state rather than the state itself, and never put a secret in a claim. | Payload contents across the token population, reviewed as though they were being published, because they effectively are. |
| A token treated as proof of permission | Keep verification and authorisation separate: a verified identity is not a permission, and the access decision belongs where the resources are. | Endpoints that authorise on the basis of a claim without a separate permission check. |
The first row is the one to close first, because everything else in the table assumes it. A verifier that does not verify makes the remaining four rows irrelevant, and a verifier that does verify is the precondition for the other four being meaningful.
10 · Misconceptions and troubleshooting
- "The token is encrypted, so the claims are private." The payload is encoded, not encrypted, and readable by anyone holding the token. Confidentiality was never part of the design.
- "We verify the signature, so we are covered." Verification is necessary and not sufficient. It says nothing about scope, audience or expiry, and it says nothing about whether the claim set should have been issued.
- "The library handles it." Libraries implement what they are configured to do, and a configuration that disables verification is a configuration the library will honour. Today's field note is a live example of exactly that.
- "Reading the claims is part of checking them." Reading is parsing. A parser has no opinion about authenticity, and the fact that the claims appear is what makes the mistake so easy to make.
- "Short-lived tokens remove the need for the other checks." Expiry limits the window and does not address audience, scope or disclosure, all of which operate independently of how long the token lives.
Troubleshooting comes down to the stage. When a token behaves unexpectedly, ask which of the four operations the service actually performed on it, because a defect described as broken authentication is usually a specific stage that was skipped or mis-implemented, and the fix differs completely between them.
11 · Memory aids and practice checklist
Memory aid: three sections, one control. Two of them are readable by anyone and writable by anyone, and the third is the only reason either means anything. Every mistake in this room is a way of forgetting that sentence.
- Split a token into its sections and read the payload before doing anything else with it.
- Ask what each claim asserts and which of those assertions anything verifies.
- Confirm the algorithm is fixed on the verifier and not taken from the token.
- Check expiry, issuer and audience as part of verification rather than afterwards.
- Read the payload as though it were about to be published, because it effectively is.
- Keep the access decision separate from the identity decision.
- State which stage failed when writing up, rather than calling it broken authentication.
12 · What the room teaches
The lesson, once: a token is a claim that someone signed, and the entire security of the mechanism is the verifier's willingness to check. The format is honest about this. It encodes rather than encrypts, it names its algorithm openly, and it places the whole burden on one operation, which is exactly why every failure mode in the room is a failure to perform that operation properly.
Read alongside the rest of this series it pairs with today's field note on an agent memory layer that accepted any well-formed token, which is the same failure in production: the claims were decoded and the signature was never checked. The previous companion covers the adjacent question of a restriction that describes names rather than effects, and the IDOR companion covers the authorisation stage this one deliberately stops short of.
Frequently asked questions
What are the three parts of a JSON Web Token?
A header naming the algorithm, a payload carrying the claims, and a signature binding the first two to a key. The first two are base64url encoded rather than encrypted, so they are readable by anyone holding the token and constructible by anyone at all. Only the signature is a control, because it is the only part that cannot be produced without the key.
Why is decoding a JWT not the same as verifying it?
Decoding is a parsing step. It reveals the claims and says nothing about who created them, whether they were altered, or whether the issuer is one you trust. Verification is the separate operation that checks a signature against a key the verifier holds, and it is the only operation that makes any claim in the token worth acting on.
Why must the algorithm come from the verifier rather than the token?
Because the header is attacker-writable until the signature has been checked. A verifier that reads the algorithm from the token is letting the attacker select how validation is performed, and the format includes a case where no signature is expected. The verifier should hold the accepted algorithms as configuration and treat any disagreement as a failure.
Is the payload of a JWT private?
No. It is encoded, not encrypted, so anyone holding the token can read every claim in it, and tokens leak routinely through logs, screenshots, support tickets and referrer headers. A correct signature check does not change that, which means anything placed in the payload should be treated as disclosed to that audience.
Primary references and further practice
- TryHackMe: JWT Security, official room
- RFC 8725: JSON Web Token Best Current Practices
- RFC 7515: JSON Web Signature
- CWE-347: Improper Verification of Cryptographic Signature
- CWE-200: Exposure of Sensitive Information to an Unauthorized Actor
- OWASP Web Security Testing Guide, session management and token testing