# Authentication Flow

> Secure login, signup, and token refresh patterns. Auth state lives in Zustand — never in React Query — with JWT and protected route handling.

**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.

**Tip:** Store only the minimum necessary in auth state. Token expiry, user ID, and roles. Fetch full user profile via React Query when needed.

**Rules:**
- **Never Store Sensitive Data in State** — Tokens live in httpOnly cookies or secure storage — never in React state or localStorage.
- **Centralize Interceptors** — One Axios/fetch interceptor handles 401 responses and token refresh for all API calls.
- **Separate Auth from UI** — AuthProvider wraps the app. UI components only call useAuth() — never touch tokens directly.
- **Route Protection at Layout** — Protect routes at the layout level, not inside individual pages.

**Pattern** — `stores/useAuthStore.ts`

```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' },
  ),
);
```

**Implementation — Next.js**

Use Next.js Middleware to protect routes server-side before the page renders. Check the auth cookie on every request.

File: `middleware.ts`

```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();
}
```

**Implementation — Vite**

Wrap protected routes in a PrivateRoute component that checks Zustand auth state and redirects unauthenticated users.

File: `components/PrivateRoute.tsx`

```tsx
import { Navigate, Outlet } from 'react-router-dom';
import { useAuthStore } from '@/stores/useAuthStore';

export function PrivateRoute() {
  const isAuthenticated = useAuthStore((s) => s.isAuthenticated);

  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }

  return <Outlet />;
}
```

Read more: https://reactprinciples.dev/cookbook/authentication-flow

---

Source: https://reactprinciples.dev/nextjs/cookbook/authentication-flow
All recipes (compact): https://reactprinciples.dev/llms.txt
All recipes (full): https://reactprinciples.dev/llms-full.txt
