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/meendpoint 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 appuseAuth()- Hook to access auth state and methods
State:
{
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:
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:
{
"username": "admin",
"password": "password123"
}
Response (200):
{
"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_tokenHTTP-only cookie (7 days) - Redirects to
/recruitmenton 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_tokencookie 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):
{
"message": "Logged out successfully"
}
Side Effects:
- Clears
auth_tokencookie - 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:
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:
const { user } = useAuth();
if (hasRole(user, "admin")) {
// Show admin-only UI
}
Authentication Flow
Login Flow
User visits
/login(not authenticated)- Middleware allows access
- LoginForm rendered
User enters credentials and clicks "Sign In"
- React Hook Form validates inputs (client-side)
- Data sent to
POST /api/auth/login
Backend login endpoint (
/api/auth/login)- Calls backend
/admin/loginwith credentials - Receives
access_token - Calls backend
/admin/mewith token to fetch user data - Sets
auth_tokenHTTP-only cookie - Returns user object
- Calls backend
Client-side
useAuth.login()redirectswindow.location.href = "/recruitment"
Middleware validates on redirect
- Cookie present β Allow
/recruitment
- Cookie present β Allow
/recruitmentloads dataAuthProviderfetches user from/api/auth/meuseAuth()provides user data to components
Daily Access Flow
User visits app
- Middleware checks for
auth_tokencookie - If present β Allow protected routes
- If absent β Redirect to
/login
- Middleware checks for
Protected pages automatically fetch user
AuthProvider.useEffect()callsgetUser()- Populates context for entire app
Logout Flow
User clicks "Logout" in header
- Calls
useAuth.logout()
- Calls
logout()function- Calls
POST /api/auth/logout - Clears
auth_tokencookie on server - Invalidates React Query cache
- Redirects to
/login
- Calls
Middleware redirects
- No cookie β Allow
/login
- No cookie β Allow
Cookie Configuration
Name: auth_token
Settings:
{
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
NEXT_PUBLIC_API_URL=https://byteriot-candidateexplorer.hf.space/CandidateExplorer
For local development:
NEXT_PUBLIC_API_URL=http://localhost:8000
Backend Requirements
Your FastAPI backend must provide:
POST
/admin/login- Accepts
application/x-www-form-urlencoded - Body:
username+password - Returns:
{ "access_token": "...", "token_type": "bearer" }
- Accepts
GET
/admin/me- Requires
Authorization: Bearer <token>header - Returns: User object with
user_id,username,role,tenant_id, etc.
- Requires
Usage Examples
Using Auth Context
"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
"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
// 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:
HTTPS Enforcement
- Set
secure: truein cookies (already done for production) - Use HSTS headers
- Set
Rate Limiting
- Implement on
/api/auth/loginendpoint - Prevent brute-force attacks
- Implement on
CSRF Tokens
- For state-changing operations (already protected by SameSite)
Password Policy
- Enforce strong passwords on backend
- Minimum length, complexity requirements
Session Timeout
- Refresh tokens for long-lived sessions
- Auto-logout after inactivity
Audit Logging
- Log all auth events (login, logout, failed attempts)
- Store in secure database
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/loginendpoint not responding NEXT_PUBLIC_API_URLpoints to wrong backend- CORS issues (if frontend and backend on different domains)
Solution:
- Check browser DevTools β Network tab
- Verify login API request to
/api/auth/login - Check console for error messages
- Verify
NEXT_PUBLIC_API_URLin.env.local
"Unauthorized" error when accessing protected routes
Symptoms: Redirected to /login even when signed in
Causes:
- Cookie not being set properly
- Backend
/admin/merejecting token - Token expired
Solution:
- Check browser DevTools β Application β Cookies
- Verify
auth_tokencookie exists - Check backend logs for token validation errors
- 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:
- Log user data:
console.log(user) - Verify role value and case
- Check
useAuth()hook in browser devtools - Verify backend is returning correct role
Logout not working
Symptoms: Clicking logout does nothing or causes error
Causes:
/api/auth/logoutendpoint not working- Network error during logout
- Redirect not happening
Solution:
- Check Network tab in DevTools
- Verify
/api/auth/logoutreturns 200 - Check console for errors
- Verify redirect happens manually:
window.location.href = "/login"
Testing the Auth System
Manual Testing Checklist
- Visit
/loginwithout 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_tokenexists and is httpOnly - Click logout β Redirects to
/login, cookie cleared - Visit
/recruitmentwithout 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
- Refresh Token Flow - Add refresh token for extended sessions
- Multi-device Logout - Logout from all devices at once
- 2FA/MFA - Two-factor authentication
- Password Reset - Self-service password reset flow
- Social Login - Google, GitHub, etc.
- Session Activity Tracking - Track and verify user activity
- Device Trust - Trust device for future logins
Monitoring & Analytics
- Set up error logging (Sentry, LogRocket)
- Monitor auth endpoint failures
- Track login/logout events
- Alert on suspicious auth patterns
References
- Next.js Middleware Documentation
- NextResponse - Setting Cookies
- HTTP-Only Cookies for Security
- SameSite Cookie Attribute
- React Hook Form Documentation
- Zod Documentation
- Radix UI Components
Implementation Date: February 25, 2026 Version: 1.0 Status: Production Ready β