Anatomy of a JWT (JSON Web Token)
A deep dive into the structure of a JWT, including the header, payload, and signature.
JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.
Anatomy of a JWT
A JWT consists of three parts separated by dots (.):
- 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 or RSA.
- Payload: The payload contains the claims. Claims are statements about an entity (typically, the user) and additional data. There are three types of claims: registered, public, and private claims.
- Signature: To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.
The output is three Base64-URL strings separated by dots.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT Header Claims
The JWT header contains metadata about the token, such as the type of token and the signing algorithm being used. The header is a JSON object that is Base64Url encoded to form the first part of the JWT. Here are some of the common header claims:
-
typ(Type): This parameter is used by JWT applications to declare the media type of this complete JWT. This is intended for use by the JWT application when values are nested. This parameter is OPTIONAL. Thetypvalue is a case-sensitive string.- Use Case: It is recommended to use the value
JWTfor this claim.
- Use Case: It is recommended to use the value
-
alg(Algorithm): This parameter identifies the cryptographic algorithm used to secure the JWS. The JWS signature value is not valid if thealgvalue does not represent a supported algorithm or if there is not a key for use with that algorithm.algvalues should either be registered in the IANA "JSON Web Signature and Encryption Algorithms" registry established by [JWA] or be a value that contains a Collision-Resistant Name. Thealgvalue is a case-sensitive ASCII string. This parameter is REQUIRED.- Use Case: Common values include
HS256,RS256, andES256.
- Use Case: Common values include
-
cty(Content Type): This parameter is used by JWS applications to declare the media type of the secured content (the payload). This parameter is OPTIONAL. Thectyvalue is a case-sensitive string.- Use Case: In a nested JWT, the
ctyof the outer JWT would beJWT.
- Use Case: In a nested JWT, the
-
jku(JWK Set URL): This parameter is a URI that refers to a resource for a set of JSON-encoded public keys, one of which corresponds to the key used to digitally sign the JWS. The keys must be encoded as a JWK Set. This parameter is OPTIONAL.- Use Case: This allows the recipient of the JWT to retrieve the public key required to verify the signature.
-
kid(Key ID): This parameter is a hint indicating which key was used to secure the JWS. This parameter allows originators to explicitly signal a change of key to recipients. This parameter is OPTIONAL. Thekidvalue is a case-sensitive string.- Use Case: If you have multiple keys for signing tokens, the
kidcan be used to identify which key to use for verification.
- Use Case: If you have multiple keys for signing tokens, the
-
x5u(X.509 URL): This parameter is a URI that refers to a resource for the X.509 public key certificate or certificate chain corresponding to the key used to digitally sign the JWS. This parameter is OPTIONAL.- Use Case: This provides another way to retrieve the public key for verification.
-
x5c(X.509 Certificate Chain): This parameter contains the X.509 public key certificate or certificate chain corresponding to the key used to digitally sign the JWS. This parameter is OPTIONAL.- Use Case: This allows the certificate to be embedded directly in the JWT.
JWT Payload Claims
The JWT payload contains claims, which are statements about an entity (typically, the user) and additional data. There are several standard claims, also known as registered claims, which are recommended but not mandatory. Some of the most common registered claims are:
-
iss(Issuer): This claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. Theissvalue is a case-sensitive string containing a StringOrURI value.- Use Case: In a multi-tenant application, you might use the
issclaim to specify which tenant issued the token. This allows the application to apply different rules based on the tenant.
- Use Case: In a multi-tenant application, you might use the
-
sub(Subject): This 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. Thesubvalue is a case-sensitive string containing a StringOrURI value.- Use Case: This is typically the user ID. For example, in a social media application, the
subclaim would be the unique identifier of the user who is authenticated.
- Use Case: This is typically the user ID. For example, in a social media application, the
-
aud(Audience): This 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 theaudclaim when this claim is present, then the JWT must be rejected. Theaudvalue is a case-sensitive string containing a StringOrURI value.- Use Case: If you have multiple backend services, you can use the
audclaim to specify which service the token is intended for. This prevents a token intended for one service from being used to access another.
- Use Case: If you have multiple backend services, you can use the
-
exp(Expiration Time): This claim identifies the expiration time on or after which the JWT must not be accepted for processing. The processing of theexpclaim requires that the current date/time must be before the expiration date/time listed in theexpclaim. Theexpvalue must be a number containing a NumericDate value.- Use Case: This is a crucial security feature. By setting an expiration time, you limit the amount of time an attacker can use a stolen token. For example, a session token might have an expiration time of 15 minutes.
-
nbf(Not Before): This claim identifies the time before which the JWT must not be accepted for processing. The processing of thenbfclaim requires that the current date/time must be on or after the not-before date/time listed in thenbfclaim. Thenbfvalue must be a number containing a NumericDate value.- Use Case: This can be used to issue a token that is not yet valid. For example, you might issue a token for a future event, and the token should not be valid until the event starts.
-
iat(Issued At): This claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Theiatvalue must be a number containing a NumericDate value.- Use Case: This can be used in combination with the
expclaim to calculate the exact time the token expires. It can also be used for logging and auditing purposes.
- Use Case: This can be used in combination with the
-
jti(JWT ID): This 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. Thejticlaim can be used to prevent the JWT from being replayed. Thejtivalue is a case-sensitive string.- Use Case: This is used to prevent replay attacks. By keeping a record of the
jtiof all processed tokens, you can ensure that a token is not used more than once.
- Use Case: This is used to prevent replay attacks. By keeping a record of the
Signing Algorithms
JWTs can be signed using various algorithms. The alg header parameter specifies the algorithm used to sign the token. Some of the common algorithms include:
- HS256 (HMAC with SHA-256): This is a symmetric algorithm that uses a single secret key to both sign and verify the token.
- RS256 (RSA with SHA-256): This is an asymmetric algorithm that uses a private key to sign the token and a public key to verify it.
- ES256 (ECDSA with SHA-256): This is an elliptic curve digital signature algorithm that is more efficient than RSA.
Creating a JWT in Node.js with jose
The jose library is a popular choice for working with JWTs in Node.js. Here's a simple example of how to create a JWT using jose:
import { SignJWT } from "jose";
async function createJwt() {
const secret = new TextEncoder().encode("your-super-secret-key");
const alg = "HS256";
const jwt = await new SignJWT({ "urn:example:claim": true })
.setProtectedHeader({ alg })
.setIssuedAt()
.setIssuer("urn:example:issuer")
.setAudience("urn:example:audience")
.setSubject("urn:example:subject")
.setExpirationTime("2h")
.sign(secret);
console.log(jwt);
}
createJwt();
In this example, we are creating a JWT with a 2-hour expiration time and signing it with the HS256 algorithm.
JWT in Authentication and OAuth2
JWTs are a cornerstone of modern authentication and authorization systems, particularly in stateless architectures and federated identity setups like OAuth2.
Direct JWT Authentication (Stateless Sessions)
In a simple client-server architecture, JWTs are used to create stateless authentication. Unlike traditional session-based authentication where the server must store session data, a JWT-based approach embeds the session information securely within the token itself.
The flow is as follows:
- Login: A user submits their credentials (e.g., username and password) to the server.
- Verification: The server validates the credentials.
- Token Generation: Upon successful validation, the server generates a JWT. The payload of this token typically includes the user's ID (
sub), the token's expiration time (exp), and any other non-sensitive data required by the client-side application. - Token Issuance: The server sends this JWT back to the client.
- Client Storage: The client stores the JWT securely (e.g., in an
HttpOnlycookie or local storage). - Authenticated Requests: For every subsequent request to a protected resource, the client includes the JWT in the
Authorizationheader using theBearerscheme (e.g.,Authorization: Bearer <token>). - Server Verification: The server's protected routes have middleware that intercepts the request, extracts the token, and verifies its signature and expiration. If the token is valid, the server processes the request; otherwise, it rejects it with a
401 Unauthorizedstatus.
This method is stateless because the server does not need to keep a record of active sessions. All the necessary information is self-contained within the token, allowing for great scalability.
JWT in OAuth2 and OpenID Connect (OIDC)
In the context of OAuth2, authentication is often handled by a dedicated, trusted third party called an Authorization Server or Identity Provider (IdP), such as Google, Auth0, or Microsoft Entra ID. Here, JWTs play two primary roles: Access Tokens and ID Tokens.
-
Access Token: An Access Token is a credential used by a client application to access protected resources on a Resource Server (your application's backend API). While OAuth2 doesn't mandate the format of Access Tokens, JWT is a very common choice. When a JWT is used, it contains information about the user and the permissions (scopes) granted to the client. The Resource Server can validate this token on its own without calling the Authorization Server, making the process highly efficient.
-
ID Token: An ID Token is specific to OpenID Connect (OIDC), a thin layer on top of OAuth2. It is always a JWT. While an Access Token is meant for the Resource Server, an ID Token is meant for the Client Application. It proves that the user has been authenticated by the Authorization Server and contains identity information about them, such as their name, email, and profile picture. The client can use this information to personalize the user experience.
Conclusion
JWT is a powerful tool for securely transmitting information between parties. Its simple, compact, and self-contained nature makes it a popular choice for authentication and authorization in modern web applications. By understanding the anatomy of a JWT and the different claims and signing algorithms, you can effectively use JWTs to build secure and scalable applications.
You can find more details about these and other information in RFC 7519 .