| # Production-Ready Authentication System - Implementation Guide | |
| ## Overview | |
| This document describes the complete authentication system implementation for the Candidate Explorer frontend, including login, session management, route protection, and role-based access control. | |
| ## Architecture | |
| ### Security Features β | |
| - **HTTP-only Cookies**: JWT token stored in HTTP-only, secure cookies (prevents XSS attacks) | |
| - **Server-side Token Management**: Token never exposed to client-side JavaScript | |
| - **CSRF Protection**: Using same-site cookie policy ("lax") | |
| - **Middleware Route Protection**: All protected routes validated server-side before rendering | |
| - **Role-based Access Control**: User roles from backend `/admin/me` endpoint never trusted from UI | |
| - **Multi-tenant Support**: Tenant ID embedded in JWT by backend, always validated server-side | |
| ### Technology Stack | |
| - **Next.js 16.1.6** (App Router) | |
| - **TypeScript** (strict mode) | |
| - **React 19** with Hooks | |
| - **React Hook Form** (form handling) | |
| - **Zod** (schema validation) | |
| - **Radix UI** (accessible components) | |
| - **React Query** (data fetching) | |
| - **Next.js Middleware** (route protection) | |
| --- | |
| ## File Structure | |
| ``` | |
| src/ | |
| βββ app/ | |
| β βββ api/auth/ | |
| β β βββ login/route.ts # POST /api/auth/login - Exchange credentials for token | |
| β β βββ logout/route.ts # POST /api/auth/logout - Clear token cookie | |
| β β βββ me/route.ts # GET /api/auth/me - Fetch current user | |
| β βββ login/ | |
| β β βββ page.tsx # Public login page | |
| β βββ recruitment/ | |
| β β βββ layout.tsx # Protected dashboard layout | |
| β β βββ page.tsx # Recruitment dashboard | |
| β βββ layout.tsx # Root layout with Providers | |
| β βββ providers.tsx # AuthProvider + QueryClientProvider | |
| β βββ page.tsx # Root page (redirects via middleware) | |
| βββ components/ | |
| β βββ LoginForm.tsx # Login form with validation | |
| β βββ dashboard/ | |
| β βββ header.tsx # Header with logout button | |
| βββ lib/ | |
| β βββ auth.ts # Core auth utilities | |
| β βββ auth-context.tsx # AuthProvider + useAuth hook | |
| β βββ rbac.ts # Role-based access control | |
| β βββ api.ts # API wrapper (existing, unchanged) | |
| βββ types/ | |
| β βββ auth.ts # Auth TypeScript interfaces | |
| β βββ user.ts # User types (existing) | |
| βββ middleware.ts # Route protection + redirects | |
| βββ .env.local # Environment variables | |
| ``` | |
| --- | |
| ## Core Components | |
| ### 1. Authentication Context (`lib/auth-context.tsx`) | |
| Provides global auth state and methods via React Context. | |
| **Exports:** | |
| - `AuthProvider` - Wraps the entire app | |
| - `useAuth()` - Hook to access auth state and methods | |
| **State:** | |
| ```typescript | |
| { | |
| user: User | null, // Current user data | |
| isLoading: boolean, // Auth operation in progress | |
| isAuthenticated: boolean, // User is logged in | |
| login(username, password) // Login function | |
| logout() // Logout function | |
| refreshUser() // Refresh user data from /admin/me | |
| } | |
| ``` | |
| **Example Usage:** | |
| ```typescript | |
| const { user, login, logout, isAuthenticated } = useAuth(); | |
| if (isAuthenticated) { | |
| console.log(`Logged in as ${user.username}`); | |
| } | |
| ``` | |
| ### 2. Login Form Component (`components/LoginForm.tsx`) | |
| Radix UI-based login form with validation. | |
| **Features:** | |
| - Zod schema validation | |
| - React Hook Form integration | |
| - Real-time error display | |
| - Loading state with spinner | |
| - API error handling | |
| - Auto-submit to `/api/auth/login` | |
| **Validation Rules:** | |
| - Username: required, min 3 chars | |
| - Password: required, min 6 chars | |
| ### 3. API Routes | |
| #### POST `/api/auth/login` | |
| Exchanges credentials for JWT token. | |
| **Request:** | |
| ```json | |
| { | |
| "username": "admin", | |
| "password": "password123" | |
| } | |
| ``` | |
| **Response (200):** | |
| ```json | |
| { | |
| "user_id": "uuid", | |
| "username": "admin", | |
| "email": "admin@example.com", | |
| "full_name": "Admin User", | |
| "role": "admin", | |
| "is_active": true, | |
| "tenant_id": "tenant-uuid", | |
| "created_at": "2024-01-01T00:00:00Z" | |
| } | |
| ``` | |
| **Side Effects:** | |
| - Sets `auth_token` HTTP-only cookie (7 days) | |
| - Redirects to `/recruitment` on client-side | |
| **Error Cases:** | |
| - 400: Missing username/password | |
| - 401: Invalid credentials | |
| - 500: Backend error | |
| #### GET `/api/auth/me` | |
| Fetches current user data from backend `/admin/me`. | |
| **Headers:** | |
| - Uses `auth_token` cookie automatically | |
| **Response (200):** User object (same as login response) | |
| **Error Cases:** | |
| - 401: No token or invalid token | |
| - 500: Backend error | |
| #### POST `/api/auth/logout` | |
| Clears auth token cookie. | |
| **Response (200):** | |
| ```json | |
| { | |
| "message": "Logged out successfully" | |
| } | |
| ``` | |
| **Side Effects:** | |
| - Clears `auth_token` cookie | |
| - Invalidates React Query cache on client | |
| - Redirects to `/login` | |
| ### 4. Middleware Route Protection (`middleware.ts`) | |
| Server-side route validation and redirects. | |
| **Protected Routes:** | |
| - `/recruitment/*` - Requires valid token | |
| - `/dashboard/*` - Requires valid token | |
| - `/` - Redirects based on auth status | |
| **Protected API Routes:** | |
| - `/api/cv-profile/*` - Requires Bearer token in headers | |
| - `/api/cv-profile/options/*` | |
| **Behavior:** | |
| | Route | Authenticated | Unauthenticated | | |
| |-------|---------------|-----------------| | |
| | `/` | β `/recruitment` | β `/login` | | |
| | `/login` | β `/recruitment` | Show login form | | |
| | `/recruitment` | Show dashboard | β `/login` | | |
| | `/api/protected` | Allow | 401 Unauthorized | | |
| ### 5. Role-Based Access Control (`lib/rbac.ts`) | |
| Helper functions for checking user roles. | |
| **Functions:** | |
| ```typescript | |
| hasRole(user, "admin") // true/false | |
| hasAnyRole(user, ["admin", "recruiter"]) // true/false | |
| hasAllRoles(user, ["admin"]) // true/false | |
| requireRole(user, "admin") // throws if no role | |
| requireAnyRole(user, ["admin"]) // throws if wrong role | |
| ``` | |
| **Usage Example:** | |
| ```typescript | |
| const { user } = useAuth(); | |
| if (hasRole(user, "admin")) { | |
| // Show admin-only UI | |
| } | |
| ``` | |
| --- | |
| ## Authentication Flow | |
| ### Login Flow | |
| 1. User visits `/login` (not authenticated) | |
| - Middleware allows access | |
| - LoginForm rendered | |
| 2. User enters credentials and clicks "Sign In" | |
| - React Hook Form validates inputs (client-side) | |
| - Data sent to `POST /api/auth/login` | |
| 3. Backend login endpoint (`/api/auth/login`) | |
| - Calls backend `/admin/login` with credentials | |
| - Receives `access_token` | |
| - Calls backend `/admin/me` with token to fetch user data | |
| - Sets `auth_token` HTTP-only cookie | |
| - Returns user object | |
| 4. Client-side `useAuth.login()` redirects | |
| - `window.location.href = "/recruitment"` | |
| 5. Middleware validates on redirect | |
| - Cookie present β Allow `/recruitment` | |
| 6. `/recruitment` loads data | |
| - `AuthProvider` fetches user from `/api/auth/me` | |
| - `useAuth()` provides user data to components | |
| ### Daily Access Flow | |
| 1. User visits app | |
| - Middleware checks for `auth_token` cookie | |
| - If present β Allow protected routes | |
| - If absent β Redirect to `/login` | |
| 2. Protected pages automatically fetch user | |
| - `AuthProvider.useEffect()` calls `getUser()` | |
| - Populates context for entire app | |
| ### Logout Flow | |
| 1. User clicks "Logout" in header | |
| - Calls `useAuth.logout()` | |
| 2. `logout()` function | |
| - Calls `POST /api/auth/logout` | |
| - Clears `auth_token` cookie on server | |
| - Invalidates React Query cache | |
| - Redirects to `/login` | |
| 3. Middleware redirects | |
| - No cookie β Allow `/login` | |
| --- | |
| ## Cookie Configuration | |
| **Name:** `auth_token` | |
| **Settings:** | |
| ```typescript | |
| { | |
| httpOnly: true, // Prevents JavaScript access (XSS protection) | |
| secure: true, // HTTPS only (production) | |
| sameSite: "lax", // CSRF protection | |
| path: "/", // Available site-wide | |
| maxAge: 7 * 24 * 60 * 60, // 7 days expiration | |
| } | |
| ``` | |
| **Important:** Token is NEVER stored in localStorage or accessible via `document.cookie`. This prevents XSS attacks from stealing authentication tokens. | |
| --- | |
| ## Environment Setup | |
| ### `.env.local` Configuration | |
| ```env | |
| NEXT_PUBLIC_API_URL=https://byteriot-candidateexplorer.hf.space/CandidateExplorer | |
| ``` | |
| For local development: | |
| ```env | |
| NEXT_PUBLIC_API_URL=http://localhost:8000 | |
| ``` | |
| ### Backend Requirements | |
| Your FastAPI backend must provide: | |
| 1. **POST `/admin/login`** | |
| - Accepts `application/x-www-form-urlencoded` | |
| - Body: `username` + `password` | |
| - Returns: `{ "access_token": "...", "token_type": "bearer" }` | |
| 2. **GET `/admin/me`** | |
| - Requires `Authorization: Bearer <token>` header | |
| - Returns: User object with `user_id`, `username`, `role`, `tenant_id`, etc. | |
| --- | |
| ## Usage Examples | |
| ### Using Auth Context | |
| ```typescript | |
| "use client"; | |
| import { useAuth } from "@/lib/auth-context"; | |
| export function MyComponent() { | |
| const { user, logout, isAuthenticated } = useAuth(); | |
| if (!isAuthenticated) { | |
| return <div>Not authenticated</div>; | |
| } | |
| return ( | |
| <div> | |
| <p>Welcome, {user?.username}</p> | |
| <button onClick={logout}>Logout</button> | |
| </div> | |
| ); | |
| } | |
| ``` | |
| ### Role-Based UI | |
| ```typescript | |
| "use client"; | |
| import { useAuth } from "@/lib/auth-context"; | |
| import { hasRole } from "@/lib/rbac"; | |
| export function AdminPanel() { | |
| const { user } = useAuth(); | |
| if (!hasRole(user, "admin")) { | |
| return <div>Access Denied</div>; | |
| } | |
| return <div>Admin Settings</div>; | |
| } | |
| ``` | |
| ### Server-Side Role Checking | |
| ```typescript | |
| // API Route or Server Component | |
| import { requireRole } from "@/lib/rbac"; | |
| export async function GET(request: NextRequest) { | |
| const user = await getUser(); // from auth context or session | |
| requireRole(user, "admin"); // Throws if not admin | |
| // Admin-only code | |
| } | |
| ``` | |
| --- | |
| ## Security Checklist | |
| β **Implemented:** | |
| - HTTP-only cookie storage (no localStorage) | |
| - Secure cookie flag for HTTPS | |
| - SameSite cookie policy | |
| - Server-side route protection | |
| - Server-side token validation | |
| - CSRF protection via cookies | |
| - Multi-tenant isolation (backend-enforced) | |
| - Role-based access control | |
| - Error logging without exposing sensitive data | |
| - Session invalidation on logout | |
| β οΈ **Additional Recommendations for Production:** | |
| 1. **HTTPS Enforcement** | |
| - Set `secure: true` in cookies (already done for production) | |
| - Use HSTS headers | |
| 2. **Rate Limiting** | |
| - Implement on `/api/auth/login` endpoint | |
| - Prevent brute-force attacks | |
| 3. **CSRF Tokens** | |
| - For state-changing operations (already protected by SameSite) | |
| 4. **Password Policy** | |
| - Enforce strong passwords on backend | |
| - Minimum length, complexity requirements | |
| 5. **Session Timeout** | |
| - Refresh tokens for long-lived sessions | |
| - Auto-logout after inactivity | |
| 6. **Audit Logging** | |
| - Log all auth events (login, logout, failed attempts) | |
| - Store in secure database | |
| 7. **API Rate Limiting** | |
| - Limit requests per IP | |
| - Prevent abuse | |
| --- | |
| ## Troubleshooting | |
| ### User stays on login page after entering credentials | |
| **Symptoms:** Form submits but doesn't redirect | |
| **Causes:** | |
| - Backend `/admin/login` endpoint not responding | |
| - `NEXT_PUBLIC_API_URL` points to wrong backend | |
| - CORS issues (if frontend and backend on different domains) | |
| **Solution:** | |
| 1. Check browser DevTools β Network tab | |
| 2. Verify login API request to `/api/auth/login` | |
| 3. Check console for error messages | |
| 4. Verify `NEXT_PUBLIC_API_URL` in `.env.local` | |
| ### "Unauthorized" error when accessing protected routes | |
| **Symptoms:** Redirected to `/login` even when signed in | |
| **Causes:** | |
| - Cookie not being set properly | |
| - Backend `/admin/me` rejecting token | |
| - Token expired | |
| **Solution:** | |
| 1. Check browser DevTools β Application β Cookies | |
| 2. Verify `auth_token` cookie exists | |
| 3. Check backend logs for token validation errors | |
| 4. Ensure token hasn't expired (7 days) | |
| ### Role-based features not working | |
| **Symptoms:** Buttons/forms showing when they shouldn't based on role | |
| **Causes:** | |
| - User role not correctly returned from `/admin/me` | |
| - Case sensitivity in role comparison | |
| - Frontend not respecting role from backend | |
| **Solution:** | |
| 1. Log user data: `console.log(user)` | |
| 2. Verify role value and case | |
| 3. Check `useAuth()` hook in browser devtools | |
| 4. Verify backend is returning correct role | |
| ### Logout not working | |
| **Symptoms:** Clicking logout does nothing or causes error | |
| **Causes:** | |
| - `/api/auth/logout` endpoint not working | |
| - Network error during logout | |
| - Redirect not happening | |
| **Solution:** | |
| 1. Check Network tab in DevTools | |
| 2. Verify `/api/auth/logout` returns 200 | |
| 3. Check console for errors | |
| 4. Verify redirect happens manually: `window.location.href = "/login"` | |
| --- | |
| ## Testing the Auth System | |
| ### Manual Testing Checklist | |
| - [ ] Visit `/login` without auth β Shows login form | |
| - [ ] Enter wrong credentials β Shows error message | |
| - [ ] Enter correct credentials β Redirects to `/recruitment` | |
| - [ ] Reload page β Stays on `/recruitment` (session persists) | |
| - [ ] Check DevTools β Cookies tab β `auth_token` exists and is httpOnly | |
| - [ ] Click logout β Redirects to `/login`, cookie cleared | |
| - [ ] Visit `/recruitment` without auth β Redirects to `/login` | |
| - [ ] User info displays correctly with name and role | |
| - [ ] Verify token is NOT in localStorage or accessible via JS | |
| ### Test Credentials | |
| Use credentials from your backend: | |
| - Username: `admin` (or your test user) | |
| - Password: Your test password | |
| ### Test URLs | |
| ``` | |
| Development: http://localhost:3000 | |
| Login: http://localhost:3000/login | |
| Dashboard: http://localhost:3000/recruitment | |
| API: http://localhost:3000/api/auth/login | |
| ``` | |
| --- | |
| ## Next Steps | |
| ### Recommended Enhancements | |
| 1. **Refresh Token Flow** - Add refresh token for extended sessions | |
| 2. **Multi-device Logout** - Logout from all devices at once | |
| 3. **2FA/MFA** - Two-factor authentication | |
| 4. **Password Reset** - Self-service password reset flow | |
| 5. **Social Login** - Google, GitHub, etc. | |
| 6. **Session Activity Tracking** - Track and verify user activity | |
| 7. **Device Trust** - Trust device for future logins | |
| ### Monitoring & Analytics | |
| 1. Set up error logging (Sentry, LogRocket) | |
| 2. Monitor auth endpoint failures | |
| 3. Track login/logout events | |
| 4. Alert on suspicious auth patterns | |
| --- | |
| ## References | |
| - [Next.js Middleware Documentation](https://nextjs.org/docs/app/building-your-application/routing/middleware) | |
| - [NextResponse - Setting Cookies](https://nextjs.org/docs/app/building-your-application/routing/middleware#setting-cookies) | |
| - [HTTP-Only Cookies for Security](https://owasp.org/www-community/attacks/xss/#stored-xss-attacks) | |
| - [SameSite Cookie Attribute](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) | |
| - [React Hook Form Documentation](https://react-hook-form.com/) | |
| - [Zod Documentation](https://zod.dev/) | |
| - [Radix UI Components](https://www.radix-ui.com/) | |
| --- | |
| **Implementation Date:** February 25, 2026 | |
| **Version:** 1.0 | |
| **Status:** Production Ready β | |