Documentation
Templates
Components
Authentication
Auth Provider

Auth Provider

Complete authentication state management with Zustand, JWT handling, and React Router integration.

Overview

This template provides:

  • Global auth state with Zustand
  • JWT token management (access + refresh)
  • Automatic token refresh
  • Protected route component
  • Login/logout functionality
  • Persistent auth across page reloads

Dependencies

npm install zustand jwt-decode
npm install react-router-dom  # if using React Router

Code

import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { tokenService } from './tokenService';
 
export interface User {
  id: string;
  email: string;
  name: string;
  avatar?: string;
  role: 'admin' | 'user';
}
 
interface AuthState {
  user: User | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  error: string | null;
 
  // Actions
  setUser: (user: User | null) => void;
  setLoading: (loading: boolean) => void;
  setError: (error: string | null) => void;
  logout: () => void;
  initialize: () => Promise<void>;
}
 
export const useAuthStore = create<AuthState>()(
  persist(
    (set, get) => ({
      user: null,
      isAuthenticated: false,
      isLoading: true,
      error: null,
 
      setUser: (user) =>
        set({
          user,
          isAuthenticated: !!user,
          error: null,
        }),
 
      setLoading: (isLoading) => set({ isLoading }),
 
      setError: (error) => set({ error, isLoading: false }),
 
      logout: () => {
        tokenService.clearTokens();
        set({
          user: null,
          isAuthenticated: false,
          error: null,
        });
      },
 
      initialize: async () => {
        const token = tokenService.getAccessToken();
 
        if (!token) {
          set({ isLoading: false });
          return;
        }
 
        if (tokenService.isTokenExpired(token)) {
          try {
            await tokenService.refreshAccessToken();
          } catch {
            get().logout();
            return;
          }
        }
 
        try {
          const response = await fetch('/api/auth/me', {
            headers: {
              Authorization: `Bearer ${tokenService.getAccessToken()}`,
            },
          });
 
          if (response.ok) {
            const user = await response.json();
            set({ user, isAuthenticated: true, isLoading: false });
          } else {
            get().logout();
          }
        } catch {
          get().logout();
        }
      },
    }),
    {
      name: 'auth-storage',
      partialize: (state) => ({
        user: state.user,
        isAuthenticated: state.isAuthenticated,
      }),
    }
  )
);

Setup

Initialize Auth on App Load

// App.tsx or main.tsx
import { useEffect } from 'react';
import { useAuthStore } from '@/features/auth/stores/authStore';
 
function App() {
  const initialize = useAuthStore((state) => state.initialize);
 
  useEffect(() => {
    initialize();
  }, [initialize]);
 
  return <AppRouter />;
}

Configure API Client with Auto-Refresh

// lib/apiClient.ts
import { tokenService } from '@/features/auth/services/tokenService';
 
const apiClient = {
  async fetch(url: string, options: RequestInit = {}): Promise<Response> {
    let token = tokenService.getAccessToken();
 
    if (token && tokenService.isTokenExpired(token)) {
      try {
        token = await tokenService.refreshAccessToken();
      } catch {
        window.location.href = '/login';
        throw new Error('Session expired');
      }
    }
 
    const headers = new Headers(options.headers);
    if (token) {
      headers.set('Authorization', `Bearer ${token}`);
    }
 
    return fetch(url, { ...options, headers });
  },
};
 
export default apiClient;

Set Up Routes

// app/router/AppRouter.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { ProtectedRoute } from './ProtectedRoute';
import { LoginPage } from '@/pages/LoginPage';
import { DashboardPage } from '@/pages/DashboardPage';
 
export function AppRouter() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/login" element={<LoginPage />} />
        <Route
          path="/dashboard"
          element={
            <ProtectedRoute>
              <DashboardPage />
            </ProtectedRoute>
          }
        />
      </Routes>
    </BrowserRouter>
  );
}

File Structure

features/auth/
├── components/
│   ├── LoginForm.tsx
│   ├── SignupForm.tsx
│   └── index.ts
├── hooks/
│   ├── useAuth.ts
│   └── index.ts
├── services/
│   ├── authService.ts
│   ├── tokenService.ts
│   └── index.ts
├── stores/
│   └── authStore.ts
├── types/
│   └── auth.types.ts
└── index.ts

Security Considerations

⚠️
Never store sensitive data in localStorage in production. Consider using httpOnly cookies for refresh tokens.
  • Use HTTPS in production
  • Implement CSRF protection
  • Set appropriate token expiration times
  • Validate tokens on the server side
  • Consider using httpOnly cookies for refresh tokens

Related