APP

Documentation

Developer Guide

Authentication System

AutoTechJobs implements a secure authentication system that manages user registration, login, and session management. The system supports multiple user roles with different permissions and access levels.

User Registration

New users can register through the signup form, which collects essential information and creates a user account:

// In routes/signup.tsx
import type { ActionFunctionArgs } from '@remix-run/cloudflare';
import { json, redirect } from '@remix-run/cloudflare';
import { Form, useActionData } from '@remix-run/react';

export const action = async ({ request, context }: ActionFunctionArgs) => {
  const formData = await request.formData();
  const email = formData.get('email')?.toString();
  const password = formData.get('password')?.toString();
  const roleId = formData.get('roleId')?.toString();
  
  // Validation
  const fieldErrors = {
    email: !email ? 'Email is required' : null,
    password: !password ? 'Password is required' : 
              password.length < 8 ? 'Password must be at least 8 characters' : null,
    roleId: !roleId ? 'Role is required' : null,
  };

  const hasErrors = Object.values(fieldErrors).some(error => error !== null);
  if (hasErrors) {
    return json({ success: false, fieldErrors }, { status: 400 });
  }
  
  try {
    // Create user account
    const authService = new AuthService(context.cloudflare.env.DB);
    await authService.registerUser({
      email,
      password,
      roleId: parseInt(roleId),
    });
    
    // Redirect to login
    return redirect('/login');
  } catch (error) {
    return json({ 
      success: false, 
      error: error instanceof Error ? error.message : 'An error occurred during registration'
    }, { status: 500 });
  }
};

Login Process

The login process authenticates users and creates a session:

// In routes/login.tsx
export const action = async ({ request, context }: ActionFunctionArgs) => {
  const formData = await request.formData();
  const email = formData.get('email')?.toString();
  const password = formData.get('password')?.toString();
  
  // Validation
  const fieldErrors = {
    email: !email ? 'Email is required' : null,
    password: !password ? 'Password is required' : null,
  };

  const hasErrors = Object.values(fieldErrors).some(error => error !== null);
  if (hasErrors) {
    return json({ success: false, fieldErrors }, { status: 400 });
  }
  
  try {
    // Authenticate user
    const authService = new AuthService(context.cloudflare.env.DB);
    const user = await authService.validateCredentials(email, password);
    
    // Create session
    const sessionStorage = createSessionStorage(context.cloudflare.env);
    const session = await sessionStorage.getSession(request.headers.get('Cookie'));
    session.set('user', { id: user.id.toString(), email: user.email, roleId: user.roleId });
    
    // Redirect based on role
    const redirectTo = user.roleId === 1 ? '/admin/dashboard' :
                      user.roleId === 2 ? '/employers/dashboard' :
                      '/candidates/dashboard';
    
    return redirect(redirectTo, {
      headers: {
        'Set-Cookie': await sessionStorage.commitSession(session),
      },
    });
  } catch (error) {
    return json({ 
      success: false, 
      error: 'Invalid email or password'
    }, { status: 401 });
  }
};

Session Management

Sessions are managed using Cloudflare KV for storage:

// In lib/session.ts
import { createCookieSessionStorage } from '@remix-run/cloudflare';

export function createSessionStorage(env: Env) {
  return createCookieSessionStorage({
    cookie: {
      name: 'autotechjobs_session',
      httpOnly: true,
      path: '/',
      sameSite: 'lax',
      secrets: [env.SESSION_SECRET],
      secure: process.env.NODE_ENV === 'production',
    },
  });
}

export async function getUserFromSession(request: Request, env: Env) {
  const sessionStorage = createSessionStorage(env);
  const session = await sessionStorage.getSession(request.headers.get('Cookie'));
  const userId = session.get('user')?.id;
  
  if (!userId) {
    return null;
  }
  
  // Get user from database
  const userRepository = new UserRepository(env.DB);
  return userRepository.findById(userId);
}

Password Security

Passwords are securely hashed using bcrypt before storage:

// In lib/utils/auth.ts
import * as bcrypt from 'bcryptjs';

export async function hashPassword(password: string): Promise<string> {
  const salt = await bcrypt.genSalt(10);
  return bcrypt.hash(password, salt);
}

export async function comparePasswords(password: string, hashedPassword: string): Promise<boolean> {
  return bcrypt.compare(password, hashedPassword);
}

Role-Based Access Control

The application implements role-based access control to restrict access to certain routes:

// In routes/employers.dashboard.tsx
export const loader = async ({ request, context }: LoaderFunctionArgs) => {
  const user = await getUserFromSession(request, context.cloudflare.env);
  
  // Check if user is authenticated
  if (!user) {
    return redirect('/login');
  }
  
  // Check if user has employer role
  if (user.roleId !== 2) {
    return redirect('/unauthorized');
  }
  
  // Load employer-specific data
  const employerRepository = new EmployerRepository(context.cloudflare.env.DB);
  const profile = await employerRepository.findByUserId(user.id);
  
  return json({ user, profile });
};

Authentication Service

The AuthService class encapsulates authentication logic:

// In lib/services/auth.service.ts
export class AuthService {
  constructor(
    private userRepository: UserRepository,
  ) {}
  
  async registerUser(userData: UserRegistrationData): Promise<User> {
    // Check if email already exists
    const existingUser = await this.userRepository.findByEmail(userData.email);
    if (existingUser) {
      throw new Error('Email already in use');
    }
    
    // Hash password
    const hashedPassword = await hashPassword(userData.password);
    
    // Create user
    return this.userRepository.create({
      email: userData.email,
      passwordHash: hashedPassword,
      roleId: userData.roleId,
    });
  }
  
  async validateCredentials(email: string, password: string): Promise<User> {
    const user = await this.userRepository.findByEmail(email);
    if (!user) {
      throw new Error('Invalid credentials');
    }
    
    const passwordHash = await this.userRepository.getPasswordHash(user.id);
    const isValid = await comparePasswords(password, passwordHash);
    
    if (!isValid) {
      throw new Error('Invalid credentials');
    }
    
    return user;
  }
}

Security Best Practices

  • Always use HTTPS for all communications
  • Implement proper password hashing with bcrypt
  • Set secure cookie options (httpOnly, secure, sameSite)
  • Validate and sanitize all user inputs
  • Implement rate limiting for login attempts
  • Use role-based access control for route protection
  • Regularly rotate session secrets
  • Implement CSRF protection for all forms