
Understanding JWT: Structure, Signing, and Validation
An examination of JWT structure, signing mechanisms, and validation processes for secure authentication.
To truly grasp JSON Web Tokens (JWT), you must shift your mental model from viewing them as opaque "tokens" to recognizing them as signed data structures. A JWT is not a database record; it is a self-contained packet of information where the integrity of the content is guaranteed by a cryptographic signature. The security model relies entirely on the mathematical impossibility of generating a valid signature for modified data without the secret key. If an attacker can modify the payload, they must also be able to forge the signature, which is the core challenge this architecture addresses.
This article is Part 2 of the OpenID Connect Deep Dive Series.
The Deterministic Structure of JWS
The structure of a JWT is defined by the JWS (JSON Web Signature) specification. It is a string consisting of three parts separated by dots: Header.Payload.Signature. These parts are not arbitrary; they are Base64URL-encoded representations of JSON objects. The dot characters (.) act as structural delimiters, not mere separators, defining the boundaries of the serialization format.
Consider a token generated for a user named "alice". The structure looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cThe first segment, eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9, decodes to the Header. This is a JSON object describing the algorithm and token type.
{"alg":"HS256","typ":"JWT"}The second segment, eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNTE2MjM5MDIyfQ, decodes to the Payload. This contains the claims—data about the user and metadata about the token's validity.
{"sub":"1234567890","name":"Alice","iat":1516239022}The third segment is the signature itself, a string of random-looking characters.
The Cryptographic Signing Mechanism
The mechanism here is critical. The signature is not calculated over the JSON text or the Base64URL string. It is calculated over the raw bytes of the first two segments, concatenated with a period: Base64URL(Header) + "." + Base64URL(Payload). The signature algorithm then processes this byte string. For an HMAC algorithm like HS256, the secret key is applied to this string using the SHA-256 hash function. The result is then Base64URL-encoded to form the third segment.
If you alter even a single bit in the Payload—changing "Alice" to "Bob"—the byte string input to the hash function changes completely. Because of the avalanche effect in cryptographic hashing, the resulting signature will be totally different. If you send the modified token to a server, the server will re-calculate the signature using the same secret key and compare it to the provided signature. They will not match, and the server rejects the request. This is the mechanism of integrity.
However, the mechanism fails if the algorithm is misconfigured. The most famous vulnerability in JWT history stems from the ability to specify the algorithm in the Header. If a server accepts a token with "alg": "none", it may skip signature verification entirely, allowing an attacker to forge any payload. This is why strict validation requires the server to whitelist specific algorithms (like RS256 or HS256) and reject anything else.
Asymmetric Keys and the Kid Claim
When moving to asymmetric cryptography, such as RSA (RS256), the signing mechanism changes slightly but the structure remains. The server generates a private key pair. The private key signs the Header.Payload string. The public key is distributed to the validating servers. When a token arrives, the validator uses the public key to verify the signature.
A crucial detail in this flow is the kid (Key ID) claim in the Header. This field tells the validator which specific public key to use if the server manages a key set (a JWK Set). Without the kid, the validator might try the wrong key or default to a weak one. The validator fetches the JWK (JSON Web Key) corresponding to the kid and performs the verification.
The Strict Order of Validation Operations
The validation process is a strict sequence of operations. You cannot check expiration before checking the signature. If you check exp first, an attacker could modify the token to set a future expiration date, and if your code checks that before verifying the signature, you have already accepted a forged token as "valid" for a long time.
The correct mechanism is:
- Parse: Split the string by
.. Validate that there are exactly three parts. - Decode Header Only: Decode the Header from Base64URL to determine the algorithm (
alg) and key ID (kid). Do not decode the Payload yet. Decoding the payload before signature verification exposes the system to Denial of Service (DoS) attacks via large payloads and logic attacks where malformed data could bypass intended checks. - Verify Signature: Reconstruct the
Header.Payloadbyte string using the raw, undecoded segments. Apply the algorithm specified in the Header (e.g., RS256) using the correct public key (identified bykid). Compare the result with the Signature segment. If this fails, abort immediately. - Decode Payload and Validate Claims: Only after the signature is confirmed, decode the Payload from Base64URL. Then, verify the claims. Check
iss(issuer) to ensure the token came from your trusted identity provider. Checkaud(audience) to ensure the token is intended for your application. Checkexp(expiration) andnbf(not before) timestamps.
This order prevents logic attacks where an attacker manipulates timestamps to extend token life.
JWS vs JWE: Integrity vs Confidentiality
It is also necessary to distinguish between JWS and JWE. The previous sections describe JWS, which provides integrity and authenticity but no confidentiality. Anyone holding the token can decode the Header and Payload and read the data. If you need to encrypt the payload so that only the intended recipient can read it, you use JWE (JSON Web Encryption). In JWE, the Payload is encrypted using a key derived from a shared secret or the recipient's public key. The structure expands to five parts in standard compact serialization: Header.EncryptedKey.IV.Ciphertext.AuthenticationTag, as defined in RFC 7516.
For most authentication flows, JWS is sufficient because the payload usually contains non-sensitive claims (user ID, roles) or the transport layer (HTTPS) handles confidentiality. JWE adds significant complexity and performance overhead. Using JWE when JWS is sufficient often introduces implementation bugs that weaken security more than it strengthens it.
Algorithm Selection and Trust Boundaries
The choice of algorithm matters. HS256 (HMAC with SHA-256) is fast and simple but requires sharing a secret key between all parties. If that key is leaked, an attacker can sign any token. RS256 (RSA with SHA-256) uses a private/public key pair. The private key never leaves the issuer. The public key can be published openly. This is the preferred mechanism for distributed systems where the issuer and the API server are different entities.
Finally, consider the storage and transmission. A JWT is a stateless token. The server does not store the token in a session database. It relies on the signature to prove validity. This means if a token is stolen, it remains valid until it expires. Short expiration times and refresh token mechanisms are required to mitigate this risk. The mechanism of JWT is robust, but it shifts the burden of security entirely to the implementation of the signing and validation logic. If the validation logic skips a step, the entire system collapses.
The diagram of trust is simple: The Issuer signs with Private Key. The Validator verifies with Public Key. The User holds the token. The chain of trust is broken if the Public Key is compromised or if the algorithm is downgraded. Every line of code handling a JWT must enforce the strict order of operations: Sign -> Encode -> Transmit -> Verify -> Decode -> Check Claims.
Common Pitfalls
Implementing JWT security correctly requires avoiding several common traps that frequently lead to vulnerabilities:
- Accepting
alg: none: Failing to strictly reject tokens where thealgheader is set tononeallows attackers to bypass signature verification entirely, effectively turning the token into an unsigned, modifiable packet. - Decoding Payload Before Verification: As noted in the validation order, decoding the Payload before verifying the signature is dangerous. It wastes resources on untrusted data and can expose the application to DoS attacks or logic errors if the payload is malformed.
- Using Weak Algorithms in Distributed Systems: Relying on symmetric algorithms like HS256 across distributed systems without a robust key management strategy is risky. If the secret is leaked or rotated improperly, the security of the entire system is compromised. Asymmetric algorithms (RS256, ES256) are generally preferred for separating signing and verification responsibilities.
Practical Takeaways
To maintain security, adopt these mental models and rules of thumb when working with JWTs:
- Verify Before You Trust: Never process claims or rely on data in the Payload until the cryptographic signature has been mathematically verified against the expected key.
- Decode Header Only for Alg: Parse the Header to identify the algorithm and key ID, but keep the Payload in its encoded string form until verification is complete.
- Never Share Private Keys: In asymmetric setups, the private key must remain strictly within the issuer's boundaries. If a private key is ever exposed, it must be considered compromised immediately.
FAQ
Can JWTs be revoked? Technically, JWTs are stateless and cannot be revoked by the issuer once issued, as the server does not maintain a session state for them. To handle revocation, you must implement a blocklist (allowing short-lived tokens) or use short expiration times paired with a refresh token mechanism that can be invalidated.
What is the difference between JWS and JWE? JWS (JSON Web Signature) ensures data integrity and authenticity but leaves the payload readable. JWE (JSON Web Encryption) provides confidentiality by encrypting the payload, ensuring only the intended recipient with the decryption key can read the contents.
Why is decoding order important? The order is critical to prevent Denial of Service (DoS) attacks and logic bypasses. If you decode the Payload first, an attacker can send massive payloads or malformed data that your application attempts to process before verifying the signature, wasting server resources or triggering errors. Verifying the signature first ensures you are only processing data that is cryptographically guaranteed to be intact.
Conclusion
Understanding JWT requires a thorough understanding of the deterministic relationship between the header, payload, and signature. By adhering to the strict order of operations and respecting the cryptographic constraints of the chosen algorithms, developers can prevent validation bypasses and maintain the integrity of their authentication systems. The difference between a secure implementation and a vulnerable one often lies in the precise handling of these low-level mechanisms.
Related posts
JWT Expiration, Rotation, and Revocation: A Lifecycle Guide
A guide to JWT expiration, rotation, and revocation strategies for secure token lifecycle management.
The Complete Guide to JSON Web Token (JWT) Claims
An examination of JWT claims including registered and custom types, covering best practices and token validation for advanced developers.
OAuth 2.0 vs JWT: Understanding the Relationship
An examination of the relationship between OAuth 2.0 and JSON Web Tokens, covering opaque tokens, token format selection, and JWT best practices.