Skip to main content

Overview

CicloVital uses a token-based authentication system with localStorage for session persistence. The authentication layer is built on three key components:
  • authService - API communication layer (src/services/authService.js)
  • useAuth hook - Authentication logic and state management (src/hooks/useAuth.js)
  • UserContext - Global user state across the application (src/contexts/UserContext.js)

Authentication Service

The authService handles all HTTP requests to the authentication API using Axios.

API Configuration

src/services/authService.js
import axios from "axios";

const isProd = import.meta.env.VITE_PROD === 'true';

const API_URL = isProd 
  ? `${import.meta.env.VITE_URL_API_USER}` 
  : `${import.meta.env.VITE_URL_API_LOCAL_USER}`;
The service automatically switches between development and production API endpoints based on the VITE_PROD environment variable.

User Registration

Creates a new user account and returns the created user data.
src/services/authService.js
export const createUser = async (userData) => {
  try {
    const response = await axios.post(API_URL, userData);
    return { ok: true, data: response.data };
  } catch(error) {
    const messageError = error.response?.data || 'Error al conectar con el servidor.';
    return { ok: false, messageError }
  }
}
Request Payload:
{
  "nombre": "John Doe",
  "edad": 28,
  "correo": "[email protected]",
  "password": "securepass123"
}
Response:
{
  "ok": true,
  "data": {
    "id": 1,
    "nombre": "John Doe",
    "edad": 28,
    "correo": "[email protected]"
  }
}

User Login

Authenticates user credentials and returns user session data.
src/services/authService.js
export const loginUser = async (userData) => {
  try {
    const response = await axios.post(API_URL + '/login', userData);
    return { ok: true, data: response.data }
  } catch(error) {
    const messageError = error.response?.data || 'Error al conectar con el servidor.';
    return { ok: false, messageError }
  }
}
Request Payload:
{
  "correo": "[email protected]",
  "password": "securepass123"
}
Both createUser and loginUser return a consistent response format with ok status and either data or messageError.

useAuth Hook

The useAuth hook provides authentication methods and state management throughout the application.

Import and Usage

import { useAuth } from './hooks/useAuth';

const LoginComponent = () => {
  const { login, logout, showAlert, alertMessage, alertHeader } = useAuth();
  
  // Use authentication methods
};

Registration Flow

The registerUser method handles the complete registration process:
1

Password Validation

src/hooks/useAuth.js:33-36
if (data.password !== data.confirmPassword) {
  handleAlert(true, "Las contraseñas no coinciden.", "Advertencia");
  return;
}
2

Create User Account

src/hooks/useAuth.js:41
const createdUser = await createUser(data);
3

Auto-Login After Registration

src/hooks/useAuth.js:45-48
const loginUserData = { 
  correo: data.correo, 
  password: data.password 
};
const registedUserData = await loginUser(loginUserData);
4

Store Session & Redirect

src/hooks/useAuth.js:52-56
setLocalStorageUser(registedUserData.data);
setUser(registedUserData.data);
handleAlert(true, `Bienvenido ${registedUserData.data.nombre}`, "Usuario creado");
history.push("/chat");

Login Flow

The login method authenticates users and manages session state:
src/hooks/useAuth.js:71-87
const login = useCallback(async (logindata, resetFormCallback) => {
  try {
    const registedUserData = await loginUser(logindata);

    if (registedUserData.ok) {
      setUser(registedUserData.data);
      setLocalStorageUser(registedUserData.data);
      handleAlert(true, `Bienvenido ${registedUserData.data.nombre}`, "Sesión iniciada");
      resetFormCallback?.();
      history.push("/chat");
    } else {
      handleAlert(true, registedUserData.messageError, "Advertencia");
    }
  } catch (error) {
    console.error(`Mensaje de error: ${error}`);
  }
}, [history, setUser, setLocalStorageUser]);

Logout Flow

The logout method clears the user session:
src/hooks/useAuth.js:90-94
const logout = () => {
  history.push('/home');
  setUser(null);
  setLocalStorageUser(null);
}
Logout clears both the global UserContext state and localStorage, ensuring complete session termination.

Hook Return Values

src/hooks/useAuth.js:97-105
return {
  registerUser,   // Registration method
  login,          // Login method
  logout,         // Logout method
  showAlert,      // Alert visibility state
  alertMessage,   // Alert message content
  alertHeader,    // Alert header text
  handleAlert     // Alert control function
}

User Context

The UserContext provides global user state management across the application.

Context Definition

src/contexts/UserContext.js
import { createContext } from "react";

const UserContext = createContext({
  user: null,
  setUser: () => {},
});

export default UserContext;

User Provider Implementation

src/contexts/UserProvider.jsx
import { useState, useEffect } from "react";
import UserContext from "./UserContext";

const safeGet = (k, fallback) => {
  try { 
    return JSON.parse(localStorage.getItem(k)) ?? fallback; 
  } catch { 
    return fallback; 
  }
};

const UserProvider = ({ children }) => {
  const [user, setUser] = useState(() => safeGet("user", null));

  // Persist user when it changes (login/logout)
  useEffect(() => {
    try {
      if (user === null) localStorage.removeItem("user");
      else localStorage.setItem("user", JSON.stringify(user));
    } catch {
      // Handle storage errors silently
    }
  }, [user]);

  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
};

Using the Context

import { useContext } from 'react';
import UserContext from './contexts/UserContext';

const MyComponent = () => {
  const { user, setUser } = useContext(UserContext);
  
  if (!user) {
    return <div>Please log in</div>;
  }
  
  return <div>Welcome, {user.nombre}!</div>;
};

Session Persistence

CicloVital uses the useLocalStorage hook for persistent session management.

Implementation

src/hooks/useLocalStorage.js
import { useEffect, useState } from 'react';

export function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    if (typeof window === 'undefined') return initialValue;
    try {
      const raw = window.localStorage.getItem(key);
      return raw ? JSON.parse(raw) : initialValue;
    } catch {
      return initialValue;
    }
  });

  useEffect(() => {
    try {
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch { /* Storage quota full / private mode */ }
  }, [key, value]);

  return [value, setValue];
}

Key Features

  • SSR Safe - Checks for window object before accessing localStorage
  • JSON Serialization - Automatically serializes/deserializes data
  • Error Handling - Gracefully handles storage quota and private mode issues
  • Auto-Sync - Automatically saves to localStorage when value changes

Protected Routes

The Chat component implements route protection:
src/pages/Chat/Chat.jsx:27-32
if (user === null) {
  window.location.reload();
  return <Home />;
}
For more robust route protection, consider implementing a PrivateRoute component wrapper that checks authentication before rendering.

Form Validation

Login Form Rules

src/pages/Login/LoginForm/LoginForm.jsx
{
  correo: {
    required: "El correo es obligatorio",
    pattern: {
      value: /^[^@]+@[^@]+\.[^@]+$/,
      message: "Formato de email inválido"
    },
    maxLength: 254
  },
  password: {
    required: "La contraseña es obligatoria",
    minLength: { value: 8, message: "Mínimo 8 caracteres" },
    maxLength: { value: 12, message: "Máximo 12 caracteres" }
  }
}

Signup Form Rules

src/pages/SignUp/SingUpForm/SignUpForm.jsx
{
  nombre: { 
    required: "El nombre es obligatorio", 
    minLength: 1, 
    maxLength: 30 
  },
  edad: {
    required: "La edad es obligatoria",
    min: 0,
    max: 120,
    valueAsNumber: true
  },
  correo: {
    required: "El correo es obligatorio",
    pattern: {
      value: /^[^@]+@[^@]+\.[^@]+$/,
      message: "Formato de email inválido"
    },
    maxLength: 254
  },
  password: {
    required: "La contraseña es obligatoria",
    minLength: { value: 8, message: "Mínimo 8 caracteres" },
    maxLength: { value: 12, message: "Máximo 12 caracteres" }
  },
  confirmPassword: {
    required: "Debe confirmar la contraseña",
    minLength: { value: 8, message: "Mínimo 8 caracteres" },
    maxLength: { value: 12, message: "Máximo 12 caracteres" }
  }
}

Error Handling

All authentication operations include comprehensive error handling:
try {
  const response = await axios.post(API_URL, userData);
  return { ok: true, data: response.data };
} catch(error) {
  const messageError = error.response?.data || 'Error al conectar con el servidor.';
  return { ok: false, messageError }
}
Common Error Scenarios:
  • Network Errors - Returns default “Error al conectar con el servidor” message
  • Invalid Credentials - Returns server error message from error.response.data
  • Validation Errors - Handled by React Hook Form before API call
  • Storage Errors - Silently caught in useLocalStorage

Best Practices

if (data.password !== data.confirmPassword) {
  handleAlert(true, "Las contraseñas no coinciden.", "Advertencia");
  return;
}
// Remove confirmPassword before sending to API
delete data.confirmPassword;
const createdUser = await createUser(data);
// All service methods return { ok, data } or { ok, messageError }
return { ok: true, data: response.data };
return { ok: false, messageError };
const [isLoading, setIsLoading] = useState(false);

const handleLogin = async (data) => {
  setIsLoading(true);
  await login(data);
  setIsLoading(false);
};

Next Steps

Configuration

Learn about environment variables and API configuration

User Context API

Explore the full UserContext API reference

useAuth Hook

Deep dive into the useAuth hook documentation

Auth Service

View complete authService API documentation

Build docs developers (and LLMs) love