JWT for Beginners: Understanding JSON Web Tokens
JSON Web Tokens (JWTs) have become one of the most widely used mechanisms for authentication and information exchange in modern web applications. Defined in RFC 7519, a JWT is a compact, URL-safe token format that allows claims to be transferred between two parties in a verifiable way. Whether you are building a single-page application, a mobile API, or a microservices architecture, understanding JWTs is an essential skill for any developer working with authentication.
The Three Parts of a JWT
Every JWT consists of three Base64url-encoded segments separated by dots: header.payload.signature. Together they form a single string that looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIiwiaWF0IjoxNjE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cHeader
The header is a JSON object that typically contains two fields: alg (the signing algorithm, such as HS256 or RS256) and typ (the token type, almost always "JWT"). The header tells the receiving party how to verify the signature.
{
"alg": "HS256",
"typ": "JWT"
}Payload
The payload contains the claims — statements about the user or any other data you want to transmit. Claims are simply key-value pairs in a JSON object. The payload is Base64url-encoded but not encrypted, so anyone with access to the token can read its contents. Never put sensitive information like passwords or credit card numbers in a JWT payload.
Signature
The signature is created by taking the encoded header, the encoded payload, a secret or private key, and the algorithm specified in the header. The server uses this signature to verify that the token has not been tampered with. If a single character in the header or payload is changed, the signature will no longer match.
HMACSHA256(
base64urlEncode(header) + "." + base64urlEncode(payload),
secret
)Base64url Encoding
JWTs use Base64url encoding, which is a URL-safe variant of standard Base64. The key differences are that + is replaced with -, / is replaced with _, and trailing padding characters (=) are stripped. This makes the token safe to include in URLs, HTTP headers, and HTML attributes without additional encoding.
Standard Claims (Registered Claims)
RFC 7519 defines a set of registered claim names. None are mandatory, but they provide interoperability when used consistently:
iss(Issuer) — Identifies the principal that issued the token, such as your authentication server's domain.sub(Subject) — Identifies the subject of the token, typically a user ID.aud(Audience) — Identifies the recipients the token is intended for, such as a specific API.exp(Expiration Time) — A Unix timestamp after which the token must be rejected.iat(Issued At) — A Unix timestamp indicating when the token was created.nbf(Not Before) — A Unix timestamp before which the token must not be accepted.jti(JWT ID) — A unique identifier for the token, useful for preventing replay attacks.
{
"iss": "https://auth.example.com",
"sub": "user_8421",
"aud": "https://api.example.com",
"exp": 1700000000,
"iat": 1699996400,
"nbf": 1699996400,
"jti": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}Custom Claims
Beyond the registered claims, you can add any custom claims your application needs. Common examples include user roles, permissions, email addresses, or tenant IDs. Use namespaced names (like https://example.com/roles) for custom claims to avoid collisions with registered or public claim names.
{
"sub": "user_8421",
"name": "Jane Doe",
"role": "admin",
"permissions": ["read", "write", "delete"]
}How JWT Authentication Flows Work
The typical JWT authentication flow follows four steps:
- Login: The user submits credentials (username and password) to the authentication server.
- Token issuance: The server validates the credentials and, if correct, generates a JWT containing claims about the user. This token is returned to the client.
- Authenticated requests: The client includes the JWT in subsequent requests, typically in the
Authorizationheader using the Bearer scheme:Authorization: Bearer <token>. - Verification: The server receiving the request verifies the token's signature and checks that the claims (expiration, audience, etc.) are valid before granting access to the requested resource.
Client Auth Server API Server
| | |
|-- POST /login {credentials} ---->| |
|<---- 200 OK { token: "eyJ..." } -| |
| | |
|-- GET /api/data ---------------------------------->|
| Authorization: Bearer eyJ... |
|<------------ 200 OK { data } ---------------------|Where to Store JWTs
Token storage is one of the most debated topics in JWT-based authentication. There are two primary options, each with tradeoffs:
localStorage / sessionStorage
Storing tokens in the browser's Web Storage API is simple and makes it easy to attach the token to API requests via JavaScript. However, Web Storage is accessible to any JavaScript running on the page, making it vulnerable to Cross-Site Scripting (XSS) attacks. If an attacker injects malicious script into your page, they can read the token and impersonate the user.
httpOnly Cookies
Storing the JWT in an httpOnly cookie prevents JavaScript from accessing it, which eliminates the XSS vector. However, cookies are automatically sent with every request to the domain, which introduces Cross-Site Request Forgery (CSRF) risks. You must implement CSRF protection (such as the SameSite cookie attribute or anti-CSRF tokens) to mitigate this. For most applications, httpOnly cookies with proper CSRF defenses are the more secure choice.
Token Lifecycle
A well-designed JWT system considers the entire lifecycle of a token:
- Creation: The token is generated at login with an appropriate expiration time (often 15 minutes to 1 hour for access tokens).
- Usage: The client sends the token with each request during its valid period.
- Refresh: Before the access token expires, the client can use a longer-lived refresh token to obtain a new access token without requiring the user to log in again.
- Expiration: Once the
expclaim time passes, the token is rejected by any compliant server. - Revocation: Since JWTs are stateless, revoking them before expiration requires additional mechanisms like server-side blocklists or short-lived tokens paired with refresh tokens.
Understanding these fundamentals is the first step toward building secure, scalable authentication systems with JWTs. The token format is deceptively simple, but the security considerations around signing, storage, and lifecycle management require careful attention.