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:

{
  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_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):

{
  "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:

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

  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:

{
  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:

  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

"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:

  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


Implementation Date: February 25, 2026 Version: 1.0 Status: Production Ready βœ