JWT Token Structure, Signatures, and Common Vulnerabilities
JSON Web Tokens (JWT) have become the de-facto standard for stateless authentication in modern microservices architectures. However, fundamental misunderstandings regarding their core structure frequently lead to devastating security implementations.
The Anatomy of a JWT
A JWT is not an encrypted blob; it is an encoded string comprising three distinct layers, separated by periods (`.`): `Header.Payload.Signature`.
- Header: Typically consists of two parts: the type of the token (JWT), and the signing algorithm being used (e.g., HMAC SHA256 or RSA). This JSON object is then `Base64Url` encoded.
- Payload: Contains the actual claims (statements about the user and additional data). Like the header, it is merely `Base64Url` encoded. Anyone who intercepts the token can instantly retrieve this data. Do not store passwords or financial data here.
- Signature: The crucial security layer. It is created by taking the encoded header, the encoded payload, a secret key, and the algorithm specified in the header to hash the result. This ensures the payload has not been tampered with.
The "none" Algorithm Vulnerability
Perhaps the most infamous vulnerability in early JWT implementations stems directly from the fact that the header specifies the signing algorithm. If an attacker intercepts a token, modifies his role to `admin` in the payload, and then changes the algorithm in the header to `"alg": "none"`, poorly configured backends will bypass the signature verification entirely. They trust the attacker's instruction that the token requires no signature.
Local Decoding vs. Remote Validation
To safely inspect JWTs during development, developers must decode the headers and payloads without transmitting the tokens to remote servers. At the FindDevTools lab, our JWT Decoder tool executes strictly client-side. It parses the base64url substrings, decodes them to unicode, and renders the JSON tree without making a single network call.
This is a 1000+ word deep dive... Names. The corresponding values are referred to as Claim Values. The contents of the JOSE Header describe the cryptographic operations applied to the JWT Claims Set. If the JOSE Header is for a JWS, the JWT is represented as a JWS and the claims are digitally signed or MACed, with the JWT Claims Set being the JWS Payload. If the JOSE Header is for a JWE, the JWT is represented as a JWE and the claims are encrypted, with the JWT Claims Set being the plaintext encrypted by the JWE. A JWT may be enclosed in another JWE or JWS structure to create a Nested JWT, enabling nested signing and encryption to be performed. A JWT is represented as a sequence of URL-safe parts separated by period ('.') characters. Each part contains a base64url-encoded value. The number of parts in the JWT is dependent upon the representation of the resulting JWS using the JWS Compact Serialization or JWE using the JWE Compact Serialization. Jones, et al. Standards Track [Page 6] RFC 7519 JSON Web Token (JWT) May 2015 3.1. Example JWT The following example JOSE Header declares that the encoded object is a JWT, and the JWT is a JWS that is MACed using the HMAC SHA-256 algorithm: {"typ":"JWT", "alg":"HS256"} To remove potential ambiguities in the representation of the JSON object above, the octet sequence for the actual UTF-8 representation used in this example for the JOSE Header above is also included below. (Note that ambiguities can arise due to differing platform representations of line breaks (CRLF versus LF), differing spacing at the beginning and ends of lines, whether the last line has a terminating line break or not, and other causes. In the representation used in this example, the first line has no leading or trailing spaces, a CRLF line break (13, 10) occurs between the first and second lines, the second line has one leading space (32) and no trailing spaces, and the last line does not have a terminating line break.) The octets representing the UTF-8 representation of the JOSE Header in this example (using JSON array notation) are: [123, 34, 116, 121, 112, 34, 58, 34, 74, 87, 84, 34, 44, 13, 10, 32, 34, 97, 108, 103, 34, 58, 34, 72, 83, 50, 53, 54, 34, 125] Base64url encoding the octets of the UTF-8 representation of the JOSE Header yields this encoded JOSE Header value: eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9 The following is an example of a JWT Claims Set: {"iss":"joe", "exp":1300819380, "http://example.com/is_root":true} The following octet sequence, which is the UTF-8 representation used in this example for the JWT Claims Set above, is the JWS Payload: [123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10, 32, 34, 101, 120, 112, 34, 58, 49, 51, 48, 48, 56, 49, 57, 51, 56, 48, 44, 13, 10, 32, 34, 104, 116, 116, 112, 58, 47, 47, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 105, 115, 95, 114, 111, 111, 116, 34, 58, 116, 114, 117, 101, 125] Jones, et al. Standards Track [Page 7] RFC 7519 JSON Web Token (JWT) May 2015 Base64url encoding the JWS Payload yields this encoded JWS Payload (with line breaks for display purposes only): eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly 9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ Computing the MAC of the encoded JOSE Header and encoded JWS Payload with the HMAC SHA-256 algorithm and base64url encoding the HMAC value in the manner specified in [JWS] yields this encoded JWS Signature: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk Concatenating these encoded parts in this order with period ('.') characters between the parts yields this complete JWT (with line breaks for display purposes only): eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9 . eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt cGxlLmNvbS9pc19yb290Ijp0cnVlfQ . dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk This computation is illustrated in more detail in Appendix A.1 of [JWS]. See Appendix A.1 for an example of an encrypted JWT. 4. JWT Claims The JWT Claims Set represents a JSON object whose members are the claims conveyed by the JWT. The Claim Names within a JWT Claims Set MUST be unique; JWT parsers MUST either reject JWTs with duplicate Claim Names or use a JSON parser that returns only the lexically last duplicate member name, as specified in Section 15.12 ("The JSON Object") of ECMAScript 5.1 [ECMAScript]. The set of claims that a JWT must contain to be considered valid is context dependent and is outside the scope of this specification. Specific applications of JWTs will require implementations to understand and process some claims in particular ways. However, in the absence of such requirements, all claims that are not understood by implementations MUST be ignored. There are three classes of JWT Claim Names: Registered Claim Names, Public Claim Names, and Private Claim Names. Jones, et al. Standards Track [Page 8] RFC 7519 JSON Web Token (JWT) May 2015 4.1. Registered Claim Names The following Claim Names are registered in the IANA "JSON Web Token Claims" registry established by Section 10.1. None of the claims defined below are intended to be mandatory to use or implement in all cases, but rather they provide a starting point for a set of useful, interoperable claims. Applications using JWTs should define which specific claims they use and when they are required or optional. All the names are short because a core goal of JWTs is for the representation to be compact. 4.1.1. "iss" (Issuer) Claim The "iss" (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The "iss" value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL. 4.1.2. "sub" (Subject) Claim The "sub" (subject) claim identifies the principal that is the subject of the JWT. The claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The "sub" value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL. 4.1.3. "aud" (Audience) Claim The "aud" (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the "aud" claim when this claim is present, then the JWT MUST be rejected. In the general case, the "aud" value is an array of case- sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the "aud" value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL. 4.1.4. "exp" (Expiration Time) Claim The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the "exp" claim requires that the current date/time MUST be before the expiration date/time listed in the "exp" claim. Jones, et al. Standards Track [Page 9] RFC 7519 JSON Web Token (JWT) May 2015 Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL. 4.1.5. "nbf" (Not Before) Claim The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the "nbf" claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the "nbf" claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL. 4.1.6. "iat" (Issued At) Claim The "iat" (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL. 4.1.7. "jti" (JWT ID) Claim The "jti" (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The "jti" claim can be used to prevent the JWT from being replayed. The "jti" value is a case- sensitive string. Use of this claim is OPTIONAL. 4.2. Public Claim Names Claim Names can be defined at will by those using JWTs. However, in order to prevent collisions, any new Claim Name should either be registered in the IANA "JSON Web Token Claims" registry established by Section 10.1 or be a Public Name: a value that contains a Collision-Resistant Name. In each case, the definer of the name or value needs to take reasonable precautions to make sure they are in control of the part of the namespace they use to define the Claim Name. 4.3. Private Claim Names A producer and consumer of a JWT MAY agree to use Claim Names that are Private Names: names that are not Registered Claim Names (Section 4.1) or Public Claim Names (Section 4.2). Unlike Public Jones, et al. Standards Track [Page 10] RFC 7519 JSON Web Token (JWT) May 2015 Claim Names, Private Claim Names are subject to collision and should be used with caution. 5. JOSE Header For a JWT object, the members of the JSON object represented by the JOSE Header describe the cryptographic operations applied to the JWT and optionally, additional properties of the JWT. Depending upon whether the JWT is a JWS or JWE, the corresponding rules for the JOSE Header values apply. This specification further specifies the use of the following Header Parameters in both the cases where the JWT is a JWS and where it is a JWE. 5.1. "typ" (Type) Header Parameter The "typ" (type) Header Parameter defined by [JWS] and [JWE] is used by JWT applications to declare the media type [IANA.MediaTypes] of this complete JWT. This is intended for use by the JWT application when values that are not JWTs could also be present in an application data structure that can contain a JWT object; the application can use this value to disambiguate among the different kinds of objects that might be present. It will typically not be used by applications when it is already known that the object is a JWT. This parameter is ignored by JWT implementations; any processing of this parameter is performed by the JWT application. If present, it is RECOMMENDED that its value be "JWT" to indicate that this object is a JWT. While media type names are not case sensitive, it is RECOMMENDED that "JWT" always be spelled using uppercase characters for compatibility with legacy implementations. Use of this Header Parameter is OPTIONAL. 5.2. "cty" (Content Type) Header Parameter The "cty" (content type) Header Parameter defined by [JWS] and [JWE] is used by this specification to convey structural information about the JWT. In the normal case in which nested signing or encryption operations are not employed, the use of this Header Parameter is NOT RECOMMENDED. In the case that nested signing or encryption is employed, this Header Parameter MUST be present; in this case, the value MUST be "JWT", to indicate that a Nested JWT is carried in this JWT. While media type names are not case sensitive, it is RECOMMENDED that "JWT" always be spelled using uppercase characters for compatibility with legacy implementations. See Appendix A.2 for an example of a Nested JWT. Jones, et al. Standards Track [Page 11] RFC 7519 JSON Web Token (JWT) May 2015 5.3. Replicating Claims as Header Parameters In some applications using encrypted JWTs, it is useful to have an unencrypted representation of some claims. This might be used, for instance, in application processing rules.Technical Deep Dive & Specification Reference