frontend-candidate-explorer / AUTH_IMPLEMENTATION.md
ishaq101's picture
[NOTICKET] Feat: Login Page
d635176
# 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 βœ