Introduction to JSON Web Tokens
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
In modern web architectures, JWTs are commonly used for stateless authorization. Once a user logs in, the authentication server generates a token containing claims about the user and signs it. For subsequent requests, the client transmits this token, allowing the resource server to authenticate the user without querying a central database or session store.
The Structure of a JWT
In its compact form, a JSON Web Token consists of three parts separated by dots (.):
Header.Payload.Signature
1. The Header
The header typically consists of two parts: the type of the token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 (HS256) or RSA SHA256 (RS256).
{
"alg": "HS256",
"typ": "JWT"
}
This JSON is then Base64Url encoded to form the first part of the JWT.
2. The Payload
The payload contains the "claims." Claims are statements about an entity (typically, the user) and additional metadata. There are three types of claims:
- Registered Claims: These are a set of predefined claims which are not mandatory but recommended, to provide a set of useful, interoperable claims. Examples include:
iss(issuer),exp(expiration time),sub(subject), andaud(audience). - Public Claims: These can be defined at will by those using JWTs. To avoid collisions, they should be defined in the IANA JSON Web Token Registry or defined as a URI that contains a collision-resistant namespace.
- Private Claims: These are custom claims created to share information between parties that agree on using them (e.g.,
userIdoruserRoles).
An example payload might look like this:
{
"sub": "1234567890",
"name": "Jane Doe",
"admin": true,
"iat": 1516239022,
"exp": 1516242622
}
This payload is Base64Url encoded to form the second part of the JWT.
3. The Signature
To create the signature part, you must take the encoded header, the encoded payload, a secret key, the algorithm specified in the header, and sign them. For instance, if you want to use the HMAC SHA256 algorithm, the signature will be created in the following way:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secretKey
)
The signature is used to verify that the sender of the JWT is who it claims to be and to ensure that the message wasn't changed along the way.
Cryptographic Signature Validation
When a server receives a JWT, it must validate it before performing any operations. The process involves:
- Splitting the token into its three constituents.
- Decoding the Header and Payload to verify basic structure, alg, and claims like expiration time (
exp). - Re-computing the signature using the received Header, Payload, and the server's private secret key.
- Comparing the re-computed signature with the signature component sent in the token. If they match exactly, the token is authentic and has not been tampered with.
Security Considerations and Best Practices
While JWTs are highly efficient, improper configuration can introduce critical vulnerabilities:
- The "alg": "none" Vulnerability: Early library implementations allowed tokens signed with the algorithm set to "none". Attackers could modify the payload, set the algorithm to "none", and bypass validation. Modern libraries explicitly disable this behavior.
- Secret Key Entropy: If using symmetric signing (HS256), the secret key must be long and complex. Weak keys are vulnerable to offline brute-force attacks.
- Symmetric vs. Asymmetric Signing: In microservice architectures, asymmetric algorithms (like RS256 or ES256) are safer. The identity provider signs tokens with a private key, and downstream services verify them using a public key, meaning the private signing key never leaves the identity provider.
- Token Storage Security: Storing JWTs in browser storage requires careful trade-offs. LocalStorage is vulnerable to Cross-Site Scripting (XSS) attacks. Storing tokens in a secure, HTTP-only, SameSite cookie protects them from XSS extraction but requires protection against Cross-Site Request Forgery (CSRF).