Skip to main content

Overview

MotorDesk uses a Redux-based authentication system with JWT tokens for session management. The system supports both login for existing users and registration for new business owners.
Authentication state is persisted using Redux Persist, allowing users to remain logged in even after closing the browser.

Authentication Architecture

The authentication system consists of three main components:
  1. useAuth Hook (src/hooks/useAuth.ts): Custom React hook that provides authentication methods
  2. Auth Slice (src/store/slices/authSlice.ts): Redux store slice managing authentication state
  3. App Router (src/routes/AppRouter.tsx): Route protection and navigation logic

State Structure

The authentication state in Redux includes:
src/store/slices/authSlice.ts
interface AuthState {
  user: User | null;              // Current authenticated user
  token: string | null;           // JWT authentication token
  isAuthenticated: boolean;       // Authentication status
  isLoading: boolean;             // Loading state for async operations
  error: string | null;           // Error messages
  needsOnboarding: boolean;       // Flag for new users requiring setup
}

User Model

Each user in the system has the following structure:
src/store/slices/authSlice.ts
export interface User {
  id: string;              // Unique user identifier
  branchIds: string[];     // Branch locations user has access to
  nombre: string;          // User's full name
  email: string;           // Email (used for login)
  rol: UserRole;           // User role (OWNER, ADMIN, SELLER, CASHIER)
}

Login Flow

Using the Login Hook

The useAuth hook provides a complete authentication interface:
Example: Using useAuth
import { useAuth } from '@hooks/useAuth';

export const LoginComponent = () => {
  const {
    email,
    setEmail,
    password,
    setPassword,
    handleLogin,
    isLoading,
    error,
    isAuthenticated
  } = useAuth();

  const onSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    const success = await handleLogin(e);
    if (success) {
      // Redirect to dashboard
      navigate('/dashboard');
    }
  };

  return (
    <form onSubmit={onSubmit}>
      <input 
        type="email" 
        value={email} 
        onChange={(e) => setEmail(e.target.value)} 
      />
      <input 
        type="password" 
        value={password} 
        onChange={(e) => setPassword(e.target.value)} 
      />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Logging in...' : 'Login'}
      </button>
      {error && <p>{error}</p>}
    </form>
  );
};

Login Process

The login function performs the following steps:
1

Dispatch login start

Sets isLoading to true and clears any previous errors:
src/hooks/useAuth.ts
dispatch(loginStart()); // Activates loading state
2

Validate credentials

Checks credentials against the user database:
src/hooks/useAuth.ts
// Simulates server latency (1500ms)
await new Promise(resolve => setTimeout(resolve, 1500));

// Find user by email
const userFound = usersData.users.find(
  u => u.email === email.toLowerCase()
);

// Validate password (currently accepts '123456' for demo)
if (userFound && password === '123456') {
  // Success path
} else {
  dispatch(loginFailure('Credenciales incorrectas'));
  return false;
}
3

Dispatch login success

On successful authentication, updates the Redux store:
src/hooks/useAuth.ts
dispatch(loginSuccess({
  user: userFound as User,
  isNew: false,
  token: `mock-jwt-token-para-${userFound.id}`
}));
This sets:
  • isAuthenticated to true
  • Stores user data and token
  • Clears loading and error states
4

Handle errors

If authentication fails, error state is updated:
src/hooks/useAuth.ts
dispatch(loginFailure(
  'Credenciales incorrectas. Verifica tu correo o contraseña.'
));
For development/demo purposes, any registered email with password 123456 will successfully authenticate.

Registration Flow

New business owners can register through the registration form.

Registration Process

Example: Registration
const { register, isLoading, error } = useAuth();

const handleRegister = async () => {
  const success = await register(
    'Juan Pérez',           // nombre
    '[email protected]',   // email
    '123456'                // password (optional)
  );
  
  if (success) {
    // User is automatically logged in
    // needsOnboarding will be true
    navigate('/onboarding');
  }
};
1

Check for existing user

Validates that the email isn’t already registered:
src/hooks/useAuth.ts
const userExists = usersData.users.find(
  (u) => u.email === emailNuevo.toLowerCase()
);

if (userExists) {
  dispatch(loginFailure(
    'Este correo ya está registrado en el sistema.'
  ));
  return false;
}
2

Create new user

Creates a new user with OWNER role:
src/hooks/useAuth.ts
const newUser: User = {
  id: `user-new-${Date.now()}`,
  branchIds: [],                    // Empty initially
  nombre: nombreNuevo,
  email: emailNuevo.toLowerCase(),
  rol: UserRole.OWNER,              // Auto-assigned OWNER role
};
3

Auto-login with onboarding flag

Automatically logs in the new user with onboarding enabled:
src/hooks/useAuth.ts
dispatch(loginSuccess({
  user: newUser,
  isNew: true,                      // Triggers onboarding
  token: `mock-jwt-token-new-${newUser.id}`
}));
New registrations always receive the OWNER role, which grants full system access. Additional users with different roles must be created by the owner through the Settings panel.

Protected Routes

MotorDesk uses route guards to protect authenticated pages:
src/routes/AppRouter.tsx
export const AppRouter = () => {
  const isAuthenticated = useSelector(
    (state: RootState) => state.auth.isAuthenticated
  );

  return (
    <BrowserRouter>
      <Routes>
        {/* Public route - redirects if logged in */}
        <Route
          path="/login"
          element={
            !isAuthenticated ? <Login /> : <Navigate to="/dashboard" replace />
          }
        />

        {/* Protected routes - requires authentication */}
        <Route
          element={
            isAuthenticated ? <MainLayout /> : <Navigate to="/login" replace />
          }
        >
          <Route path="/dashboard" element={<Home />} />
          <Route path="/vehicles" element={<Vehicles />} />
          <Route path="/sales" element={<Sales />} />
          {/* ... more protected routes */}
        </Route>
      </Routes>
    </BrowserRouter>
  );
};

Route Protection Logic

Unauthenticated Users

  • Can only access /login
  • Redirected to /login when accessing protected routes
  • Cannot access dashboard or app features

Authenticated Users

  • Cannot access /login (redirected to /dashboard)
  • Full access to all protected routes
  • Unknown routes redirect to /dashboard

Session Management

Logout Functionality

Users can logout, which clears all authentication state:
Example: Logout
const { logout } = useAuth();

const handleLogout = () => {
  logout();
  // User is redirected to /login automatically
};
The logout action resets the auth state:
src/store/slices/authSlice.ts
logout: (state) => {
  state.user = null;
  state.token = null;
  state.isAuthenticated = false;
  state.needsOnboarding = false;
}

Onboarding Completion

For new users who need to complete onboarding:
Example: Complete Onboarding
const { needsOnboarding, finishOnboarding } = useAuth();

if (needsOnboarding) {
  // Show onboarding wizard
  // After completion:
  finishOnboarding();
}
src/store/slices/authSlice.ts
completeOnboarding: (state) => {
  state.needsOnboarding = false;
}

State Persistence

Authentication state is persisted using Redux Persist with LocalForage:
src/store/index.ts
import { persistStore, persistReducer } from 'redux-persist';
import localforage from 'localforage';

const persistConfig = {
  key: 'root',
  storage: localforage,  // Uses IndexedDB for larger storage
  whitelist: ['auth']    // Only persist auth state
};
Persisted data allows users to remain logged in across browser sessions and enables full offline functionality.

Testing Credentials

For development and testing, use these mock credentials:
EmailPasswordRoleBranch Access
[email protected]123456ADMINISTRADORbranch-001, branch-002
[email protected]123456VENDEDORbranch-001
[email protected]123456VENDEDORbranch-002
[email protected]123456CAJERObranch-003
All test users accept password 123456 for demo purposes. In production, implement proper password validation and hashing.

Security Considerations

The current implementation uses mock authentication for development. For production deployment, you must:
  • Implement secure password hashing (bcrypt, argon2)
  • Use real JWT tokens with proper signing
  • Add token refresh mechanism
  • Implement rate limiting on login attempts
  • Add multi-factor authentication (MFA)
  • Use HTTPS for all API calls
  • Implement CSRF protection

Next Steps

User Roles

Learn about the different user roles and their permissions

State Management

Understand how Redux manages application state

useAuth Hook

Full API reference for the useAuth hook

Settings

Configure user accounts and team management

Build docs developers (and LLMs) love