跪拜 Guibai
← All articles
Interview

A Token's Lifecycle: The Complete JWT Login Loop in React

By 黄敬峰 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

The four-step loop—sign, store, carry, verify—is the skeleton of nearly every frontend auth system. Understanding how the axios instance, interceptors, state store, and route guard connect eliminates the confusion that comes from reading about each piece in isolation.

Summary

A full-stack JWT login flow built with React 19, zustand, axios, and react-router-dom 7, using vite-plugin-mock to simulate the backend. The token's journey starts at a login form, passes through a mock server that signs it with jsonwebtoken, lands in localStorage and a zustand store, and then rides along on every request via an axios request interceptor. A route guard component checks the store and redirects unauthenticated users.

The architecture centers on a single axios instance configured with a base URL and two interceptors: one that reads the token from localStorage and injects it into the Authorization header, and another that strips the response data wrapper. Business API modules import this instance, not the raw axios library, which is a common pitfall that causes `instance is not defined` errors.

Logout clears both localStorage and the zustand store, instantly updating the UI and reactivating the route guard. The mock server mirrors a real backend by signing tokens at `/api/login` and verifying them at `/api/repo`, creating a self-contained development loop that requires no external API.

Takeaways
JWT replaces server-side session state by encoding user identity directly into a signed token that any server with the secret key can verify.
An axios request interceptor reads the token from localStorage and attaches it as a `Bearer` token to every outgoing request, so no API call needs to handle auth manually.
Importing the customized axios instance from `./config` instead of the raw `axios` package is essential; using the wrong import causes `instance is not defined` errors.
zustand centralizes login state so components like the navbar and route guard can subscribe to the token without prop drilling.
A route guard component wraps protected routes, reads the token from the store, and renders `<Navigate to="/login" replace />` when the token is missing.
Logout clears the token from both localStorage and the zustand store, which instantly updates all subscribed components and reactivates the route guard.
vite-plugin-mock intercepts requests matching configured URLs and returns fake responses, allowing a full JWT sign-and-verify loop without a real backend.
Storing tokens in localStorage is convenient but vulnerable to XSS; httpOnly cookies are more secure but require CSRF protection.
Conclusions

The `instance is not defined` bug is a symptom of a deeper problem: developers often treat axios interceptors as magic rather than understanding that the configured instance is a distinct object that must be explicitly imported.

Mocking the backend inside the frontend toolchain turns JWT from a theoretical concept into a concrete, testable loop. The symmetry between `jwt.sign` at `/api/login` and `jwt.verify` at `/api/repo` makes the stateless nature of JWT tangible.

pnpm 10+ ignoring build scripts by default is a sharp edge that will trip up developers who expect `pnpm install` to behave like npm. The esbuild native binary requirement means a silent failure that manifests as a cryptic Vite 500 error.

Concepts & terms
JWT (JSON Web Token)
A stateless authentication token that encodes a JSON identity object into a three-part string (Header.Payload.Signature) using a secret key. The server signs it on login and verifies it on subsequent requests without storing session state.
jwt.sign / jwt.verify
The two core operations of the jsonwebtoken library. `sign` takes a payload and secret key and produces a token string. `verify` takes a token and secret key and decodes it back to the original payload, throwing an error if the token is expired or tampered with.
Axios interceptor
Middleware functions that run on every request or response made by an axios instance. A request interceptor can inject headers like Authorization; a response interceptor can unwrap data or handle global errors like 401.
Route guard
A wrapper component in React Router that conditionally renders its children or redirects to a login page based on authentication state, typically read from a global store like zustand.
vite-plugin-mock
A Vite plugin that intercepts HTTP requests in the development server and returns mock data based on configured URL patterns, eliminating the need for a running backend during frontend development.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗