JWT Auth in React: Login and a Protected Route
JWT auth in React is: log in to get a token, store it, attach it to API calls, and gate the protected view. The crucial part - the backend must verify the token on every protected request. Here's the whole flow.
The flow
- User logs in -> backend returns a JWT.
- Frontend stores the token and sends it on every protected request
(
Authorization: Bearer <token>). - The backend verifies the token's signature before returning protected data.
- The frontend gates the protected route/UI on having a (valid) token.
Frontend: store the token + attach it
async function login(username, password) {
const r = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});
const { token } = await r.json();
localStorage.setItem("token", token); // or in memory / httpOnly cookie
}
// attach on protected calls
fetch("/api/me", { headers: { Authorization: `Bearer ${localStorage.getItem("token")}` } });
Frontend: gate the route
function ProtectedRoute({ children }) {
const token = localStorage.getItem("token");
return token ? children : <Navigate to="/login" />;
}
The part that's actually security: verify on the backend
Hiding a route in React is UX, not security - anyone can call the API directly. The protected endpoint must verify the JWT signature and reject bad/expired tokens:
import jwt
from flask import request, jsonify
@app.get("/api/me")
def me():
auth = request.headers.get("Authorization", "")
if not auth.startswith("Bearer "):
return jsonify({"error": "missing token"}), 401
try:
claims = jwt.decode(auth[7:], SECRET, algorithms=["HS256"])
except jwt.PyJWTError:
return jsonify({"error": "invalid token"}), 401
return jsonify({"user": claims["sub"]})
Without that server-side check, GET /api/me returns the profile to anyone - the React
guard does nothing for a direct request.
Decisions worth knowing
- Where to store the token:
localStorageis simple but exposed to XSS; an httpOnly cookie isn't readable by JS (safer) but needs CSRF protection. Pick per threat model. - Expiry + refresh: short-lived access token + a refresh token is the standard; at minimum, handle a 401 by sending the user back to login.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Storing a JWT and attaching it as a Bearer header
- Gating a React route on the token
- Verifying the JWT server-side on the protected endpoint
FAQ
How do I add JWT authentication to a React app?
Log in to get a JWT, store it (localStorage or an httpOnly cookie), send it as Authorization: Bearer <token> on protected requests, and gate the protected route on having a token. Critically, the backend must verify the token on every protected endpoint.
Is hiding a route in React enough to secure it?
No - that's only UX. Anyone can call the API directly, so the protected endpoint must verify the JWT signature server-side and return 401 for missing/invalid/expired tokens.
Where should I store a JWT in the browser?
localStorage is simple but readable by any XSS; an httpOnly cookie can't be read by JavaScript (safer against XSS) but requires CSRF protection. Choose based on your threat model.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →