React JWT Authentication: Getting Started
A JWT (JSON Web Token) is a compact, digitally signed string that encodes user identity and permissions, letting your React app verify login state without hitting the server on every request. A JWT consists of three Base64-encoded parts separated by dots: a header (algorithm), a payload (user data and claims), and a signature (proof the token was issued by your server). Unlike traditional session cookies, tokens are stateless: the server does not store them in memory or a database.
What Is a JWT and Why Use One?
A JWT is fundamentally a claim assertion: "this user, issued at this time, with these permissions, is valid until this expiry." The signature ensures the token has not been tampered with by your server's secret key. In React apps, the flow is: user logs in, server returns a token, the React client stores it (securely) and includes it in the Authorization: Bearer <token> header on subsequent requests. The server decodes and verifies the signature, then processes the request if valid.
According to a 2025 Auth0 benchmark, 68% of modern web apps use token-based auth (JWT, OAuth) instead of session cookies, because tokens are stateless, scale across microservices, and enable single sign-on (SSO). Tokens reduce server memory pressure and scale horizontally: any server instance can verify a token without shared session storage.
JWT Structure and Components
The Three Parts: Header, Payload, Signature
A JWT is three Base64-encoded JSON objects joined by periods. Here is an example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Decoded, it contains:
-
Header: Specifies the algorithm and token type.
{ "alg": "HS256", "typ": "JWT" } -
Payload: Contains claims about the user.
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } -
Signature: HMAC hash of header + payload + server secret.
The signature proves the token came from your server. If an attacker modifies the payload, the signature becomes invalid, and your server rejects it.
Standard JWT Claims
JWTs use registered claim names, the most common being:
sub(subject): User ID (e.g.,"user:42")iat(issued at): Unix timestamp when the token was createdexp(expiration): Unix timestamp when the token expiresiss(issuer): Who issued the token (e.g., your API domain)aud(audience): For whom the token is intended (e.g., your React app's domain)nbf(not before): Token is invalid before this time
You can also add custom claims like role, permissions, or email.
JWT Authentication Flow in React
Step-by-Step Flow Diagram (Conceptual)
- User opens your React app and clicks "Log In"
- React sends username and password to your backend over HTTPS
- Backend validates credentials against a database
- If valid, backend generates a JWT with user claims and signs it with a secret
- Backend returns the JWT to React
- React stores the token securely (covered in later articles)
- React includes the token in the
Authorization: Bearer <token>header on every API call - Backend receives the request, extracts the token from the header, verifies the signature using its secret
- If the signature is valid and the token is not expired, backend processes the request
- If the token is expired or invalid, backend returns a 401 Unauthorized, and React prompts the user to log in again
Generating a JWT in Node.js/Express
Here is a minimal backend example using the jsonwebtoken library:
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET; // Never hardcode; use environment variables
app.post('/login', (req, res) => {
const { username, password } = req.body;
// Validate username/password against your database
// (This example assumes validation succeeded)
const user = { id: 42, username: 'alice', role: 'admin' };
const token = jwt.sign(
{
sub: user.id,
username: user.username,
role: user.role,
iat: Math.floor(Date.now() / 1000), // Issued now
exp: Math.floor(Date.now() / 1000) + 3600, // Expires in 1 hour
iss: 'https://api.example.com',
aud: 'https://app.example.com'
},
SECRET,
{ algorithm: 'HS256' }
);
res.json({ token, expiresIn: 3600 });
});
Verifying a JWT in React
In your React app, after you receive a token from login, you can decode it (without verification — only the server verifies the signature):
import { jwtDecode } from 'jwt-decode'; // npm install jwt-decode
const token = localStorage.getItem('authToken'); // Not recommended; see later articles
const decoded = jwtDecode(token);
console.log(decoded);
// { sub: 42, username: 'alice', role: 'admin', iat: ..., exp: ... }
// Check if expired
if (decoded.exp * 1000 < Date.now()) {
console.log('Token is expired');
}
React can decode a token to display the user's name or check their role locally, but it cannot verify the signature (the secret is on the server). Never trust client-side claims without server verification.
Why JWT Over Session Cookies?
| Aspect | Session Cookies | JWT Tokens |
|---|---|---|
| Storage | Server session store (memory/DB) | Stateless; stored in client |
| Scalability | Requires shared session cache (Redis) | Any server instance can verify |
| Microservices | Difficult; requires session sync | Ideal; each service verifies independently |
| Mobile/SPA | Possible but awkward | Native fit for React/Vue/Angular apps |
| CSRF Risk | Default; requires CSRF token | Lower; token in header, not cookie |
| Token Theft | Attacker gets session ID; still limited | Attacker gets full token; exposure window matters |
Key Takeaways
- A JWT is a signed JSON object that encodes user identity and permissions; the signature proves your server issued it
- JWTs are stateless: the server does not store them; any server can verify the signature using the secret key
- Standard claims (
sub,iat,exp,iss,aud) establish the token's identity, timing, and scope - In React, decode the token to display user info or check roles locally, but never trust claims without server verification
- JWT scale better than session cookies across microservices and stateless architectures
- The token's expiration (
exp) claim prevents indefinite access; short-lived tokens (1–15 minutes) are more secure - Never hardcode secrets; always use environment variables and rotate secrets periodically
Frequently Asked Questions
What happens if my JWT secret is leaked?
If your JWT secret is exposed, an attacker can forge any token and impersonate any user. Immediately revoke all tokens (by changing the secret), rotate the secret on all servers, and force all users to log in again. Use a key-rotation strategy to minimize the damage window.
Can I decode a JWT in React without the server secret?
Yes, you can Base64-decode the payload and read the claims locally. However, you cannot verify the signature without the secret. Always assume client-side decoding is for display only; the server must verify the signature on every request.
Is JWT safe if I store it in localStorage?
No. localStorage is vulnerable to XSS attacks: if an attacker injects malicious JavaScript into your page, they can read the token and steal it. Later articles cover httpOnly cookies, which JavaScript cannot access, making them more secure.
How long should a JWT last?
Short-lived access tokens (5–15 minutes) reduce exposure if stolen. Pair them with refresh tokens (valid for days or weeks) that the server can revoke. This is covered in the "Refresh Tokens" article.
Can I use JWT for stateful operations like "log out this user everywhere"?
No, because JWTs are stateless. To implement logout, you must maintain a blacklist or revocation list on the server (covered in "Logout and Token Revocation"). Alternatively, use a refresh-token approach where the server refuses to issue new access tokens.