Authentication Flow
Secure login, signup, and token refresh patterns. Auth state lives in Zustand — never in React Query — with JWT and protected route handling.
01
Principle
Authentication state is client state, not server state. It belongs in Zustand or Context, not React Query. The auth store manages tokens, user profile, and session status. API interceptors handle token refresh transparently.
lightbulb
Store only the minimum necessary in auth state. Token expiry, user ID, and roles. Fetch full user profile via React Query when needed.
02
Rules
- check_circleNever Store Sensitive Data in StateTokens live in httpOnly cookies or secure storage — never in React state or localStorage.
- check_circleCentralize InterceptorsOne Axios/fetch interceptor handles 401 responses and token refresh for all API calls.
- check_circleSeparate Auth from UIAuthProvider wraps the app. UI components only call useAuth() — never touch tokens directly.
- check_circleRoute Protection at LayoutProtect routes at the layout level, not inside individual pages.
03
Pattern
stores/useAuthStore.ts
import { create } from 'zustand'; import { persist } from 'zustand/middleware'; export const useAuthStore = create<AuthState>()( persist( (set) => ({ user: null, isAuthenticated: false, login: (user) => set({ user, isAuthenticated: true }), logout: () => set({ user: null, isAuthenticated: false }), }), { name: 'auth-storage' }, ), );
04
Implementation
info
Version Compatibility
Requires React 19+ and the latest stable versions of all dependencies shown.
Use Next.js Middleware to protect routes server-side before the page renders. Check the auth cookie on every request.
middleware.ts
import { NextRequest, NextResponse } from 'next/server'; export function middleware(req: NextRequest) { const token = req.cookies.get('auth-token'); const isProtected = req.nextUrl.pathname.startsWith('/dashboard'); if (isProtected && !token) { return NextResponse.redirect(new URL('/login', req.url)); } return NextResponse.next(); }