Skip to main content

Overview

The Auction Platform implements a robust authentication system with role-based access control (RBAC), supporting two user roles: ORGANIZER and TEAM_ADMIN. The authentication flow is built using Zustand for state management and includes automatic session initialization and redirect logic.

Authentication Flow

1

User Initialization

When the app loads, the useAuthStore automatically initializes by calling /auth/me to check if a valid session exists.
2

Login/Signup

Users can authenticate through the login or signup forms, which validate credentials and establish a session.
3

Session Management

Upon successful authentication, user data and authentication state are stored in the Zustand store.
4

Route Protection

Protected routes check authentication status and redirect unauthenticated users to login.

User Types and Roles

The system supports two distinct user roles:
// src/features/auth/types/auth.types.ts
export type User = {
  id: number;
  email: string;
  role: 'ORGANIZER' | 'TEAM_ADMIN';
  is_onboarded: boolean;
}

Role Descriptions

ORGANIZER

Primary users who can create and manage auctions. Organizers go through an onboarding process to set up their profile and company information.

TEAM_ADMIN

Administrative users with elevated permissions to manage teams and organizational settings.

Authentication State

The authentication state is managed globally using Zustand and includes:
src/features/auth/types/auth.types.ts:16
export type AuthState = {
  isAuthenticated: boolean;   // Whether user is logged in
  isOnboarded: boolean;        // Whether user completed onboarding
  user: User | null;           // Current user data
  initialized: boolean;        // Whether auth state has been initialized
}

Login Form

Schema Definition

The login form uses a schema-driven approach with built-in validation:
src/features/auth/Schema/auth.schema.ts:6
export const LoginFormSchema: (AuthFieldType & { 
  leftIcon?: LucideIcon, 
  rightIcon?: LucideIcon 
})[] = [
  {
    id: "email",
    label: "Email",
    type: "email",
    placeholder: "Enter your email",
    rightIcon: Mail,
    fieldValidators: [
      { type: "required" }
    ]
  },
  {
    id: "password",
    label: "Password",
    type: "password",
    placeholder: "Enter your password",
    leftIcon: Key,
    fieldValidators: [
      { type: "required" },
      { type: "minLength", constraints: { minLength: 8 } },
    ]
  }
]

Login Component

The login form uses the useFormEngine hook for form state management and validation:
src/features/auth/components/LoginForm.tsx:12
export default function LoginForm() {
  const { formData, onChange, errors } = useFormEngine<AuthFieldType>(LoginFormSchema)

  return (
    <section className={styles.authcontainer}>
      <Typography as="h1" weight="bold">Access Your Account</Typography>
      
      {LoginFormSchema.map((field) => (
        <TextField
          key={field.id}
          id={field.id}
          value={formData[field.id] ?? ""}
          type={field.type}
          label={field.label}
          placeholder={field.placeholder}
          onChange={(e) => onChange(e, field.id)}
          LeftIcon={field.leftIcon}
          RightIcon={field.rightIcon}
          error={errors[field.id] ? true : false}
          helperText={errors[field.id] ? errors[field.id] : ""}
        />
      ))}

      <Button variant="primary">Submit</Button>
      
      <Typography as="p" weight="light" size="text-md">
        Don't have an Account? <Link to="/signup">Sign up</Link>
      </Typography>
    </section>
  )
}

Signup Form

Schema Definition

The signup form includes password confirmation:
src/features/auth/Schema/auth.schema.ts:31
export const SignupFormSchema: (AuthFieldType & { leftIcon?: LucideIcon })[] = [
  {
    id: "email",
    label: "Email",
    type: "email",
    placeholder: "Enter your email",
    leftIcon: Mail,
    fieldValidators: [
      { type: "required" }
    ]
  },
  {
    id: "password",
    label: "Password",
    type: "password",
    placeholder: "Enter your password",
    leftIcon: Key,
    fieldValidators: [
      { type: "required" },
      { type: "minLength", constraints: { minLength: 8 } }
    ]
  },
  {
    id: "cnfpassword",
    label: "Confirm Password",
    type: "password",
    placeholder: "Confirm your password",
    leftIcon: Key,
    fieldValidators: [
      { type: "required" },
    ]
  }
]

Password Requirements

Passwords must be at least 8 characters long. Additional validation rules can be added to the schema’s fieldValidators array.

Authentication Service

The authentication service handles API communication and state updates:
src/features/auth/services/auth.service.ts:15
export async function login(email: string, password: string) {
  const data: LoginResponse = await apiPOST("/auth/login", {
    body: JSON.stringify({ email, password })
  });

  useAuthStore.getState().login(data.user);

  return data.user
}

Auth Store (Zustand)

The global authentication store manages user state throughout the application:
src/app/store/auth/auth.store.ts:11
export const useAuthStore = create<AuthStore>((set, get) => ({
  isAuthenticated: false,
  isOnboarded: false,
  user: null,
  initialized: false,

  init: async () => { /* ... */ },
  
  login: (user) =>
    set({
      user,
      isAuthenticated: true,
      isOnboarded: user.is_onboarded,
      initialized: true,
    }),

  logout: () =>
    set({
      user: null,
      isAuthenticated: false,
      isOnboarded: false,
      initialized: true,
    }),
}));

Route Protection

Routes are protected using TanStack Router’s beforeLoad hook:
src/routes/_without-navbar/login.tsx:5
export const Route = createFileRoute('/_without-navbar/login')({
  beforeLoad: async () => {
    const { isAuthenticated, isOnboarded } = useAuthStore.getState();

    // Allow access if not authenticated
    if (!isAuthenticated) {
      return;
    }
    
    // Redirect to onboarding if not completed
    if (!isOnboarded) {
      throw redirect({ to: "/onboarding" });
    }
    
    // Redirect to dashboard if already authenticated
    throw redirect({ to: "/dashboard" });
  },
  component: () => <AuthLayout type="login" />,
})

API Client Configuration

The API client automatically handles authentication:
src/app/api/client.ts:5
export async function request(endpoint: string, options: RequestInit = {}) {
  const response = await fetch(`${API_BASE_URL}${endpoint}`, {
    ...fetchOptions,
    credentials: "include",  // Include cookies for session
    headers,
    signal: controller.signal
  })

  // Auto-logout on 401
  if (response.status === 401) {
    useAuthStore.getState().logout();
    throw new Error("Unauthorized");
  }

  if (!response.ok) {
    let messsage = "API request failed"
    try {
      const error = await response.json().catch(() => ({}))
      messsage = error.error || messsage
    } catch { }
    throw new Error(messsage)
  }

  return response.status === 204 ? null : response.json();
}

Form Validation Engine

The platform uses a custom form engine that provides real-time validation:
src/app/hooks/useFormEngine.tsx:9
export default function useFormEngine<T extends BaseField>(formSchema: T[]) {
  const [formData, setFormData] = useState<Record<string, string>>({})
  const [errors, setErrors] = useState<Record<string, string>>({})

  // Compile schema with attached validators
  const compiledSchema = formSchema.map(attachValidators)

  const onChange = (e: React.ChangeEvent<HTMLInputElement>, fieldId: string) => {
    const val = e.target.value;
    setFormData((prev) => ({ ...prev, [fieldId]: val }));

    const fieldSchema = getFieldSchema(fieldId);
    if (!fieldSchema) return

    // Validate field on change
    const error = validateFields(val, fieldSchema);
    setErrors((prev) => ({ ...prev, [fieldId]: error }))
  }

  return { formData, onChange, errors }
}

Best Practices

  • Always call useAuthStore.getState().init() on app startup
  • Handle 401 responses by automatically logging out users
  • Use credentials: "include" for cookie-based sessions
  • Check both isAuthenticated and isOnboarded states
  • Redirect unauthenticated users to login
  • Redirect non-onboarded users to onboarding flow
  • Use schema-driven validation for consistency
  • Provide real-time feedback to users
  • Validate on field change for better UX

API Endpoints

/auth/login
POST
Authenticate user with email and passwordRequest Body:
{
  "email": "[email protected]",
  "password": "password123"
}
Response:
{
  "token": "jwt_token_here",
  "user": {
    "id": 1,
    "email": "[email protected]",
    "role": "ORGANIZER",
    "is_onboarded": false
  }
}
/auth/logout
POST
End user session and clear authentication state
/auth/me
GET
Get current authenticated user informationResponse:
{
  "id": 1,
  "email": "[email protected]",
  "role": "ORGANIZER",
  "is_onboarded": true
}

Build docs developers (and LLMs) love