Tambo360 uses React Context to manage authentication state globally across the application. The AuthContext provides user information, token management, and authentication status to all components.
Overview
The authentication system implements:
Session Management Automatic session validation on app load
Token Storage HTTP-only cookies for secure token storage
Global State User data accessible throughout the app
Protected Routes Automatic redirects for unauthenticated users
File Location
apps/frontend/src/context/AuthContext.tsx
Type Definitions
User Type
export interface User {
id : string
correo : string
nombre : string
apellido : string
establecimientos : Establecimiento []
rol : string
verificado : boolean
createdAt : string
updatedAt : string
}
interface Establecimiento {
idEstablecimiento : string
nombre : string
direccion : string
localidad : string
provincia : string
}
AuthState Interface
interface AuthState {
user : User | null
token : string | null
isAuthenticated : boolean
loading : boolean
error : string | null
}
AuthContext Type
interface AuthContextType extends AuthState {
setToken : ( token : string | null ) => void
login : ({ user , token } : { user : User ; token : string }) => void
setUser : ( user : User | null ) => void
logout : () => void
setLoading : ( loading : boolean ) => void
setError : ( error : string | null ) => void
}
Implementation
AuthContext Provider
import React , { createContext , useContext , useEffect , useState } from 'react'
import { AuthState , User } from '../types'
import Cookies from 'js-cookie'
import { api } from '@/src/services/api'
import { useLogout } from '@/src/hooks/auth/useLogout'
interface AuthContextType extends AuthState {
setToken : ( token : string | null ) => void
login : ({ user , token } : { user : User ; token : string }) => void
setUser : ( user : User | null ) => void
logout : () => void
setLoading : ( loading : boolean ) => void
setError : ( error : string | null ) => void
}
const AuthContext = createContext < AuthContextType | undefined >( undefined )
export const AuthProvider : React . FC <{ children : React . ReactNode }> = ({
children ,
}) => {
const [ user , setUser ] = useState < User | null >( null )
const [ token , setToken ] = useState < string | null >( null )
const [ loading , setLoading ] = useState ( true )
const [ error , setError ] = useState < string | null >( null )
const { mutateAsync } = useLogout ()
const isAuthenticated = !! user
// Fetch current user session on mount
const fetchSession = async () => {
setLoading ( true )
try {
const res = await api . get ( '/auth/me' )
if ( res ?. data . data ) {
setUser ( res . data . data )
} else {
setUser ( null )
}
} catch {
setUser ( null )
} finally {
setLoading ( false )
}
}
useEffect (() => {
fetchSession ()
}, [])
const login = async ({ user , token } : { user : User ; token : string }) => {
setUser ( user )
setToken ( token )
setError ( null )
}
const logout = async () => {
try {
setUser ( null )
setToken ( null )
setError ( null )
await mutateAsync ()
Cookies . remove ( 'token' )
} catch ( error ) {
console . error ( error )
}
}
return (
< AuthContext.Provider
value = { {
user ,
setUser ,
token ,
setToken ,
isAuthenticated ,
loading ,
error ,
login ,
logout ,
setLoading ,
setError ,
} }
>
{ children }
</ AuthContext.Provider >
)
}
export const useAuth = () : AuthContextType => {
const context = useContext ( AuthContext )
if ( ! context ) {
throw new Error ( 'useAuth must be used within an AuthProvider' )
}
return context
}
Usage
Wrapping the App
The AuthProvider wraps the entire application in App.tsx:
import { AuthProvider } from './src/context/AuthContext'
import { QueryClientProvider } from '@tanstack/react-query'
export const App : React . FC = () => {
return (
< QueryClientProvider client = { queryClient } >
< AuthProvider >
< Router >
< AppRoutes />
</ Router >
</ AuthProvider >
</ QueryClientProvider >
)
}
Location: apps/frontend/App.tsx:27
Using the useAuth Hook
Access authentication state and methods in any component:
import { useAuth } from '@/src/context/AuthContext'
const MyComponent = () => {
const { user , isAuthenticated , loading , login , logout } = useAuth ()
if ( loading ) {
return < LoadingSpinner />
}
if ( ! isAuthenticated ) {
return < Navigate to = "/login" />
}
return (
< div >
< p > Welcome, { user ?. nombre } ! </ p >
< button onClick = { logout } > Logout </ button >
</ div >
)
}
Example: Login Flow
import { useAuth } from '@/src/context/AuthContext'
import { useLogin } from '@/src/hooks/auth/useLogin'
import { useNavigate } from 'react-router-dom'
const Login : React . FC = () => {
const { mutateAsync } = useLogin ()
const { login } = useAuth ()
const navigate = useNavigate ()
const onSubmit = handleSubmit ( async ( data ) => {
try {
const response = await mutateAsync ( data )
// Update auth context
login ({
token: response . data . token ,
user: response . data . user
})
// Redirect to dashboard
navigate ( '/dashboard' )
} catch ( err ) {
console . error ( 'Login failed:' , err )
}
})
return < form onSubmit = { onSubmit } > ... </ form >
}
Location: apps/frontend/src/pages/Login.tsx:69
Example: Accessing User Data
import { useAuth } from '@/src/context/AuthContext'
const Dashboard = () => {
const { user } = useAuth ()
return (
< div >
< p className = "text-muted-foreground text-xs sm:text-sm" >
Dashboard / { user . establecimientos [ 0 ]. nombre }
</ p >
< h1 className = "text-2xl sm:text-3xl font-bold" >
Bienvenido, { user . nombre } { user . apellido }
</ h1 >
</ div >
)
}
Location: apps/frontend/src/pages/Dashboard.tsx:25
Example: Logout
import { useAuth } from '@/src/context/AuthContext'
import { useNavigate } from 'react-router-dom'
const Navbar = () => {
const { logout } = useAuth ()
const navigate = useNavigate ()
const handleLogout = async () => {
await logout ()
navigate ( '/login' )
}
return (
< Button onClick = { handleLogout } >
Cerrar sesión
</ Button >
)
}
Session Management
Automatic Session Restoration
On app load, AuthContext automatically attempts to restore the user session:
const fetchSession = async () => {
setLoading ( true )
try {
const res = await api . get ( '/auth/me' )
if ( res ?. data . data ) {
setUser ( res . data . data ) // Session restored
} else {
setUser ( null ) // No active session
}
} catch {
setUser ( null ) // Session invalid
} finally {
setLoading ( false )
}
}
useEffect (() => {
fetchSession ()
}, [])
Loading State
While checking authentication, the app shows a loading spinner:
import { useAuth } from '../context/AuthContext'
import LoadingSpinner from '../components/layout/LoadingSpinner'
export const AppRoutes = () => {
const { loading } = useAuth ()
if ( loading ) return < LoadingSpinner />
return < Routes > ... </ Routes >
}
Location: apps/frontend/src/routes/AppRoutes.tsx:21
Protected Routes
The ProtectedRoute component uses useAuth to guard routes:
import { Navigate } from 'react-router-dom'
import { useAuth } from '../context/AuthContext'
import Loading from '@/src/components/layout/Loading'
interface ProtectedRouteProps {
children : React . ReactNode
}
const ProtectedRoute = ({ children } : ProtectedRouteProps ) => {
const { user , loading } = useAuth ()
if ( loading ) {
return < Loading />
}
if ( ! user ) {
return < Navigate to = "/login" replace />
}
return <> { children } </>
}
export default ProtectedRoute
Location: apps/frontend/src/routes/ProtectedRoute.tsx
Usage in Routes
< Route
element = {
< ProtectedRoute >
< Layout />
</ ProtectedRoute >
}
>
< Route path = "/dashboard" element = { < Dashboard /> } />
< Route path = "/produccion" element = { < Produccion /> } />
</ Route >
Authentication Hooks
Tambo360 provides custom hooks for auth operations:
useLogin
import { useMutation , useQueryClient } from '@tanstack/react-query'
import { loginUser } from '@/src/utils/api/auth.api'
import { queryKeys } from '@/src/utils/queryKeys'
export function useLogin () {
const queryClient = useQueryClient ()
return useMutation <
AxiosResponse < { user : User ; token : string } > ,
AxiosError < { message : string } > ,
LoginData
> ({
mutationFn : async ( values : LoginData ) => {
const { data } = await loginUser ( values )
return data
},
onSuccess : () => {
queryClient . invalidateQueries ({ queryKey: queryKeys . auth . currentUser })
},
})
}
Location: apps/frontend/src/hooks/auth/useLogin.ts
useLogout
Location: apps/frontend/src/hooks/auth/useLogout.ts
Other Auth Hooks
useRegister - Register new users
useForgotPassword - Request password reset
useResetPassword - Reset password with token
useVerifyEmail - Verify email address
useResendEmail - Resend verification email
All located in: apps/frontend/src/hooks/auth/
API Configuration
The authentication API uses Axios with interceptors:
import axios from 'axios'
import { API_ENDPOINTS } from '@/src/constants/routes'
export const api = axios . create ({
baseURL: API_ENDPOINTS . BASE ,
withCredentials: true , // Send cookies with requests
})
// Redirect to login on 401 errors
api . interceptors . response . use (
( response ) => response ,
( error ) => {
const isAuthMe = error . config ?. url ?. includes ( '/auth/me' )
const isLogout = error . config ?. url ?. includes ( '/auth/logout' )
if ( error . response ?. status === 401 ) {
if ( ! isAuthMe && ! isLogout && typeof window !== 'undefined' ) {
window . location . href = '/login'
}
}
return Promise . reject ( error )
}
)
Location: apps/frontend/src/services/api.ts
Cookie Management
Authentication tokens are stored in HTTP-only cookies:
import Cookies from 'js-cookie'
// Set cookie (usually done by backend)
Cookies . set ( 'token' , tokenValue , {
secure: true ,
sameSite: 'strict'
})
// Remove cookie on logout
Cookies . remove ( 'token' )
// Read cookie (usually automatic with withCredentials)
const token = Cookies . get ( 'token' )
Never manually set authentication tokens in cookies from the frontend. The backend should set HTTP-only cookies to prevent XSS attacks.
Best Practices
Always check loading state Show loading UI while loading is true to prevent flashes of wrong content
Use useAuth hook Never access AuthContext directly; always use the useAuth() hook
Handle errors gracefully Check for errors and display user-friendly messages
Secure token storage Use HTTP-only cookies, never localStorage for auth tokens
Troubleshooting
”useAuth must be used within an AuthProvider” Error
This error occurs when a component uses useAuth() outside of the AuthProvider:
// ❌ Wrong
const root = ReactDOM . createRoot ( document . getElementById ( 'root' ) ! )
root . render ( < MyComponent /> ) // No AuthProvider!
// ✅ Correct
root . render (
< AuthProvider >
< MyComponent />
</ AuthProvider >
)
Session Not Persisting
Ensure cookies are being sent:
// In api.ts
export const api = axios . create ({
baseURL: API_ENDPOINTS . BASE ,
withCredentials: true , // Required!
})
And ensure backend sends Set-Cookie headers with proper flags:
Set-Cookie: token=...; HttpOnly; Secure; SameSite=Strict
Protected Routes Learn how routes integrate with auth
Auth API Endpoints Backend authentication endpoints
React Query How auth hooks use TanStack Query
Login Page Complete login form implementation