# 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 ` 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
Not authenticated
; } return (

Welcome, {user?.username}

); } ``` ### 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
Access Denied
; } return
Admin Settings
; } ``` ### 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 ✅