What is a JWT?

A JSON Web Token (JWT, pronounced "jot") is a compact, URL-safe token format defined by RFC 7519. It allows two parties to exchange claims — small pieces of information — in a way that can be verified and trusted. JWTs are the backbone of modern authentication systems across the web.

When you log into a web application that uses JWT-based authentication, the server creates a token containing information about you (like your user ID and role), signs it with a secret key, and sends it back to your browser. On every subsequent request, your browser sends that token in an HTTP header, and the server verifies the signature to confirm the token is authentic and unmodified.

The key advantage of JWTs over traditional session cookies is that the server does not need to store session data. All the information the server needs is encoded right inside the token itself. This makes JWTs particularly well-suited for stateless APIs, microservice architectures, and single sign-on systems where sharing session state between servers would be complex.

The Three Parts: Header, Payload, Signature

Every JWT consists of exactly three parts, separated by dots (.). Here is an example token:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Each of those three sections — separated by the dots — has a specific purpose:

Header: The first part is a Base64url-encoded JSON object that describes the token type and the signing algorithm being used. Decoding the header from the example above gives:

{
  "alg": "HS256",
  "typ": "JWT"
}

The alg field specifies the algorithm (HS256 means HMAC with SHA-256), and typ confirms this is a JWT.

Payload: The second part is also a Base64url-encoded JSON object. It contains the claims — the actual data you want to transmit. Decoding the payload gives:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

Claims can be anything you need: a user ID, an email address, permission roles, or an expiration timestamp. The JWT specification defines a set of standard claims (covered below), but you can also add any custom claims your application requires.

Signature: The third part is the cryptographic signature that ensures the token has not been tampered with. For HS256, the signature is computed as:

HMAC-SHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

Only someone who knows the secret key can produce a valid signature. If an attacker modifies even a single character in the header or payload, the signature will no longer match, and the server will reject the token.

Standard Claims

The JWT specification defines several registered claims. These are not mandatory, but they are widely used and recognized by most JWT libraries:

How JWT Signing Works

JWT supports multiple signing algorithms, but the two most common are HS256 (symmetric) and RS256 (asymmetric).

HS256 (HMAC + SHA-256) uses a single shared secret key for both signing and verification. The server that creates the token and the server that verifies it must both know the same secret. This is simple to implement and works well when the same application handles both token creation and verification.

// Signing with HS256 (conceptual)
signature = HMAC_SHA256(header_b64 + "." + payload_b64, shared_secret)

RS256 (RSA + SHA-256) uses a public/private key pair. The token is signed with the private key, and anyone can verify it using the corresponding public key. This is ideal for distributed systems where multiple services need to verify tokens but only one service (the auth server) should be able to create them.

// Signing with RS256 (conceptual)
signature = RSA_SHA256(header_b64 + "." + payload_b64, private_key)

// Verification uses the public key
valid = RSA_SHA256_VERIFY(header_b64 + "." + payload_b64, signature, public_key)

The choice between HS256 and RS256 depends on your architecture. For a single monolithic application, HS256 is simpler. For microservices or scenarios where third parties need to verify tokens, RS256 is the better choice.

How to Verify a JWT

When your server receives a JWT, it must verify the token before trusting its contents. Proper verification involves several steps:

  1. Split the token into its three dot-separated parts: header, payload, and signature.
  2. Decode the header to determine the signing algorithm.
  3. Recompute the signature using the header, payload, and the secret or public key.
  4. Compare signatures: If the computed signature matches the signature in the token, the token has not been tampered with.
  5. Check the exp claim: Reject the token if it has expired.
  6. Check the iss and aud claims: Ensure the token was issued by a trusted issuer and is intended for your application.

In practice, you should always use a well-tested JWT library for your language rather than implementing verification yourself. Libraries like jsonwebtoken for Node.js, PyJWT for Python, or java-jwt for Java handle all of these steps and protect against known attack vectors.

Common JWT Security Mistakes

JWTs are secure by design, but incorrect usage introduces vulnerabilities. Here are the most common mistakes developers make:

JWT vs Session Tokens

JWTs and traditional server-side sessions are both valid approaches to authentication, and each has tradeoffs.

Server-side sessions store a session ID in a cookie. The actual session data (user info, permissions) lives on the server, typically in a database or Redis cache. The session ID is just an opaque reference. This means the server can revoke a session instantly by deleting its record, and no sensitive data is exposed to the client.

JWTs are self-contained. The token carries all the data the server needs, so there is no server-side state to manage. This simplifies horizontal scaling because any server can verify the token independently. However, revoking a JWT before its expiration is difficult — since there is no server-side record to delete, you would need to maintain a token blocklist, which partially defeats the purpose of going stateless.

Use JWTs when you need stateless authentication across multiple services, APIs, or microservices. Use server-side sessions when you need fine-grained control over session lifetime and the ability to revoke sessions instantly, such as in applications where security is paramount (banking, healthcare).

Many modern applications use a hybrid approach: short-lived JWTs for API access tokens combined with server-side refresh token storage for session management. This gives you the scalability benefits of JWTs while maintaining the ability to revoke access when needed.