Understanding JSON Web Tokens (JWT) for Beginners
Alex Developer
Lead Security Engineer
If you have started building modern web applications using frameworks like React, Next.js, or Vue, you have undoubtedly encountered the term "JWT." Authentication can be one of the most confusing and intimidating aspects of full-stack development. In this comprehensive masterclass, we will completely demystify JSON Web Tokens, explain exactly why they replaced traditional session cookies, and teach you how to securely implement them in your next project.
The Problem: The Flaw of Traditional Session Cookies
To understand why JSON Web Tokens were invented, we must first look at how authentication worked on the web for the past twenty years. The HTTP protocol is inherently stateless. This means that every time your browser makes a request to a server (e.g., clicking a link to view your profile), the server treats you as a completely new, anonymous user. The server has no memory of the fact that you just logged in five seconds ago.
To solve this, developers used Session Cookies.
When you logged into a traditional PHP or Ruby on Rails application, the server would generate a random "Session ID" (e.g., a8d9f7g1) and store that ID in its own internal database alongside your User ID. The server would then send that Session ID back to your browser in the form of an HTTP Cookie. On every subsequent request, your browser would automatically attach that cookie. The server would read the cookie, query its database to see who the Session ID belonged to, and authenticate you.
Why Statefulness Fails at Scale
This architecture is incredibly secure, but it has a massive scalability flaw: It is Stateful.
The server is forced to store the session data in its own memory or database. What happens when your startup goes viral and you suddenly have 100,000 active users? Your server's memory is overloaded. What happens when you need to deploy a second load-balanced server? Server B has no idea who the user is because the Session ID was saved on Server A's hard drive. You now have to set up complicated distributed caching systems like Redis just to keep users logged in across multiple servers.
Enter JSON Web Tokens (Stateless Authentication)
A JSON Web Token (JWT) completely flips this architecture upside down. Instead of the server storing a Session ID and looking you up on every request, the server cryptographically signs a small JSON package containing your identity (e.g., "User ID: 42, Role: Admin") and hands it directly to your browser.
Your browser stores this token (usually in LocalStorage or an HttpOnly cookie). On every subsequent request, your browser sends the entire token to the server. The server uses a secret key to verify that the cryptographic signature is valid. If it is valid, the server instantly knows who you are without ever querying a database!
This is called Stateless Authentication. The server stores zero memory of the session. You could have one server or one thousand load-balanced servers across the globe; as long as they all share the same Secret Signature Key, they can instantly verify any user's JWT.
Crucial Misconception
JSON Web Tokens are Encoded, not Encrypted. Anyone who intercepts a JWT can easily decode it and read the information inside (like your email or user ID). The cryptography in a JWT is solely used for the Signature to prove that the token was not tampered with. Never put sensitive data like passwords or credit card numbers inside a JWT payload!
The Anatomy of a JWT
If you intercept a JWT in your browser's Network tab, it looks like a random string of gibberish separated by two periods:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cThis string is always comprised of three distinct parts: the Header (Red), the Payload (Blue), and the Signature (Green). Let's decode exactly what each part does.
Part 1: The Header
The first part of the token (before the first period) is the Header. It consists of two parts: the type of the token (which is always JWT), and the signing algorithm being used, such as HMAC SHA256 (HS256) or RSA.
{ "alg": "HS256", "typ": "JWT" }This JSON object is then Base64Url encoded to form the first part of the JWT.
Part 2: The Payload (Claims)
The second part of the token is the Payload, which 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.
- Registered Claims: These are a set of predefined claims that are not mandatory but highly recommended to provide a set of useful, interoperable claims. Some common ones are:
iss(Issuer): The entity that issued the token.exp(Expiration Time): The exact Unix timestamp when the token expires and should be rejected.sub(Subject): The principal that is the subject of the JWT (usually the User ID).iat(Issued At): The time at which the JWT was issued.
- Public Claims: These can be defined at will by those using JWTs. But to avoid collisions they should be defined in the IANA JSON Web Token Registry.
- Private Claims: These are custom claims created to share information between parties that agree on using them (e.g.,
"role": "admin").
{ "sub": "1234567890", "name": "John Doe", "role": "admin", "iat": 1516239022, "exp": 1516242622 }Just like the Header, this JSON payload is Base64Url encoded to form the second part of the JWT.
Part 3: The Signature
This is the most critical part of the JWT. To create the signature part, you have to take the encoded header, the encoded payload, a secret key, and the algorithm specified in the header, and sign them.
For example, if you want to use the HMAC SHA256 algorithm, the signature will be created mathematically like this:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), YOUR_SECRET_KEY )The signature ensures that the token hasn't been altered in transit. If a malicious user intercepts their JWT, Base64 decodes it, changes "role": "user" to "role": "admin", and re-encodes it, the Signature will instantly become invalid. When they send the altered token to the server, the server will mathematically hash the new payload using its Secret Key, see that the resulting hash does not match the Signature attached to the token, and throw a 401 Unauthorized error.
This is the magic of JWTs: You can trust data stored on the client's insecure browser, because you can cryptographically verify that it was not tampered with.
How to Inspect and Debug a JWT
Because JWTs are just Base64Url encoded strings, you do not need the server's Secret Key to read the Payload! If you are developing a frontend application and want to see what information the backend engineer stuffed into your token (like checking the exact expiration timestamp), you can decode it locally.
You can use our entirely free, 100% local JWT Decoder utility tool to instantly parse any JWT. Because our tool runs natively in your browser using WebAssembly, you can safely paste your production tokens into it. The token will be decoded on your local screen without ever being transmitted to a cloud server, ensuring zero risk of exposing your authentication credentials.
Security Implications: Where to Store Your JWT?
This is the most debated topic in web security. Once your frontend (React/Next.js) receives the JWT from the backend API, where should you store it? You essentially have two options, and both come with severe security trade-offs.
Option 1: LocalStorage or SessionStorage
The easiest and most common way to store a JWT is in the browser's localStorage.
// Storing the token localStorage.setItem('auth_token', jwt_string); // Retrieving the token for an API call const token = localStorage.getItem('auth_token'); fetch('https://api.example.com/data', { headers: { 'Authorization': `Bearer ${token}` } });The Risk: Cross-Site Scripting (XSS)
Any JavaScript running on your website has access to localStorage. If an attacker manages to inject malicious JavaScript into your site (perhaps through a compromised third-party NPM package or an unescaped comment section), that malicious script can simply run localStorage.getItem('auth_token') and send your users' tokens to an offshore server. The attacker now has full access to the users' accounts.
Option 2: HttpOnly Secure Cookies
The most secure way to store a JWT is to have the backend server set it as an HttpOnly cookie.
When a cookie is flagged as HttpOnly, the browser physically prevents JavaScript from reading it. An XSS attacker could inject malicious JavaScript, but when they try to read document.cookie, the JWT will be invisible. The browser will automatically attach this cookie to any HTTP requests made to your backend server.
The Risk: Cross-Site Request Forgery (CSRF)
While HttpOnly cookies solve XSS, they open you up to CSRF. Since the browser automatically attaches the cookie to requests, a malicious website could trick a user into clicking a link that triggers a hidden POST request to your bank's API (e.g., <form action="https://bank.com/transfer">). The browser will obediently attach the JWT cookie, and the bank will authorize the transfer.
The Verdict: The industry consensus is that HttpOnly cookies are strictly better. While you must implement CSRF protection (like SameSite cookie attributes or anti-CSRF tokens), XSS is generally considered a much more devastating and harder-to-prevent vulnerability in modern JavaScript-heavy applications.
The Refresh Token Strategy
One of the biggest problems with stateless JWTs is that they cannot be easily revoked. If a hacker steals a JWT, they have access to the system until that JWT mathematically expires. You cannot simply "delete the session from the database" because there is no database!
To mitigate this, the standard industry practice is the Access Token / Refresh Token strategy.
- When the user logs in, the server gives them two tokens: a short-lived Access JWT (expires in 15 minutes), and a long-lived Refresh JWT (expires in 30 days).
- The Access Token is used for all daily API requests.
- When the Access Token expires, the frontend quietly sends the Refresh Token to a special
/refreshendpoint. - The server checks the Refresh Token against a database (yes, refresh tokens are stateful!). If the user hasn't been banned or logged out, the server issues a brand new 15-minute Access Token.
This perfectly balances the performance of stateless authentication (no database hits on 99% of API calls) with the security of stateful authentication (the ability to instantly revoke a user's access within 15 minutes by blacklisting their Refresh Token).
Debug Your JWTs Securely
Stop pasting your production authentication tokens into random websites that log them to offshore servers. Use our 100% offline, WebAssembly-powered JWT Decoder to inspect your tokens locally.
Launch the Secure JWT Decoder