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.

Fullstack Engineerfullstackjwtauth

The flow

  1. User logs in -> backend returns a JWT.
  2. Frontend stores the token and sends it on every protected request (Authorization: Bearer <token>).
  3. The backend verifies the token's signature before returning protected data.
  4. 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

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

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

Fix a React useState Stale-State BugFullstack projectFix a Blank React Dashboard (Failed Fetch)Fullstack projectFix the React Search That Won't Filter (useEffect Deps)Fullstack projectFullstack roadmapStep by step to hiredFullstack interview questionsSTAR answersAll Fullstack projectsProjects hub

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 →