Skip to main content

Overview

The Inmobiliaria Web application uses Better Auth for authentication. This guide covers the complete integration including client setup, authentication flows, and session management.

Installation

1

Install Better Auth

Install the Better Auth client package in your frontend project:
npm install better-auth
2

Configure Environment Variables

Set the backend API URL in your environment configuration:
VITE_API_URL=http://localhost:10000/api

Client Initialization

The auth client is initialized in src/lib/auth-client.ts with the admin plugin enabled:
src/lib/auth-client.ts
import { createAuthClient } from "better-auth/react";
import { adminClient } from "better-auth/client/plugins";
import { API_ORIGIN } from "./api";

export const authClient = createAuthClient({
  baseURL: API_ORIGIN,
  plugins: [adminClient()],
});
The API_ORIGIN is dynamically determined from the VITE_API_URL environment variable, defaulting to http://localhost:10000 for development.

Authentication Context

The application uses a React Context (AuthContext) to manage authentication state globally. Located in src/contexts/AuthContext.tsx, it provides:
  • User session state
  • Authentication methods (sign in, sign up, sign out)
  • Password reset functionality
  • Favorites management

Provider Setup

Wrap your application with the AuthProvider:
import { AuthProvider } from "./contexts/AuthContext";

function App() {
  return (
    <AuthProvider>
      {/* Your app components */}
    </AuthProvider>
  );
}

Using the Auth Hook

Access authentication state and methods using the useAuth hook:
import { useAuth } from "./contexts/AuthContext";

function MyComponent() {
  const { user, loading, signIn, signOut } = useAuth();

  if (loading) return <div>Loading...</div>;
  if (!user) return <div>Not logged in</div>;

  return (
    <div>
      <h1>Welcome, {user.name}</h1>
      <button onClick={signOut}>Sign Out</button>
    </div>
  );
}

Authentication Flows

Sign Up

Create new user accounts with email verification:
const { signUp } = useAuth();

const handleSignUp = async () => {
  try {
    await signUp(email, password, name);
    // User created - email verification required
    alert("Check your email for verification!");
  } catch (error) {
    console.error(error.message);
  }
};
const signUp = async (email: string, password: string, name: string) => {
  const response = await authClient.signUp.email({
    email,
    password,
    name,
    callbackURL: "/",
  });

  if (response.error) {
    throw response.error;
  }
};

Sign In

Authenticate existing users:
const { signIn } = useAuth();

const handleSignIn = async () => {
  try {
    await signIn(email, password);
    // User signed in successfully
  } catch (error) {
    console.error(error.message);
  }
};
The signIn method automatically refreshes the session after successful authentication.

Google Social Sign In

Implement OAuth authentication with Google:
import { authClient } from "./lib/auth-client";

const handleGoogleSignIn = async () => {
  await authClient.signIn.social({
    provider: "google",
    callbackURL: "/dashboard",
  });
};

Password Reset

1

Request Reset

User requests a password reset email:
const { requestPasswordReset } = useAuth();

const handleForgotPassword = async () => {
  try {
    await requestPasswordReset(email);
    alert("Reset email sent!");
  } catch (error) {
    console.error(error.message);
  }
};
2

Reset Password

User submits new password with token from email:
const { resetPassword } = useAuth();

const handleResetPassword = async () => {
  // Get token from URL query params
  const token = searchParams.get("token");
  
  try {
    await resetPassword(token, newPassword);
    alert("Password reset successfully!");
    navigate("/login");
  } catch (error) {
    console.error(error.message);
  }
};
After a successful password reset, users must sign in again. The reset process does not automatically create a session.

Session Management

Automatic Session Refresh

The AuthContext automatically checks and refreshes the session on mount:
src/contexts/AuthContext.tsx
const refreshSession = async () => {
  const { data } = await authClient.getSession();

  if (data?.user) {
    const user = data.user as unknown as User;
    // Fetch user favorites
    const favoritesResponse = await api.users.getFavorites();
    const favoriteIds = favoritesResponse.data?.map(
      (property: Property) => property.id
    ) || [];

    setAuthState({
      user,
      loading: false,
      error: null,
      favoritePropertyIds: favoriteIds,
      refreshingSession: false,
    });
  }
};

Using Session Data

Access the current user session:
const { user, loading } = useAuth();

if (loading) {
  return <LoadingSpinner />;
}

if (!user) {
  return <LoginPrompt />;
}

return (
  <div>
    <p>Email: {user.email}</p>
    <p>Name: {user.name}</p>
    <p>Role: {user.role}</p>
  </div>
);

Protected Routes

Implement route protection based on authentication state:
import { useAuth } from "./contexts/AuthContext";
import { Navigate } from "@tanstack/react-router";

function ProtectedRoute({ children }) {
  const { user, loading } = useAuth();

  if (loading) {
    return <LoadingSpinner />;
  }

  if (!user) {
    return <Navigate to="/login" />;
  }

  return children;
}

Error Handling

The application includes error translation utilities for user-friendly messages:
src/utils/authErrorTranslator.ts
export const translateAuthError = (errorMessage: string): string => {
  // Map English error messages to Spanish
  if (errorMessage.includes("Invalid credentials")) {
    return "Credenciales inválidas";
  }
  // ... more translations
  return errorMessage;
};
Errors are automatically handled in the AuthContext and cleared after 5 seconds:
const { error } = useAuth();

{error && (
  <div className="error-message">
    {error}
  </div>
)}

Favorites Management

The auth context includes favorites management:
const { addToFavorites, removeFromFavorites, isFavorite } = useAuth();

// Check if property is favorited
const isPropertyFavorited = isFavorite(propertyId);

// Add to favorites
await addToFavorites(propertyId);

// Remove from favorites
await removeFromFavorites(propertyId);

Best Practices

  • Always check loading state before rendering user-dependent UI
  • Handle errors gracefully with user-friendly messages
  • Use the refreshSession method after operations that modify user state
  • Implement proper loading states during authentication operations
  • Clear sensitive data on sign out

API Reference

AuthContext Methods

MethodParametersDescription
signInemail, passwordSign in with email and password
signUpemail, password, nameCreate new user account
signOut-Sign out current user
refreshSession-Manually refresh user session
requestPasswordResetemailSend password reset email
resetPasswordtoken, newPasswordReset password with token
addToFavoritespropertyIdAdd property to favorites
removeFromFavoritespropertyIdRemove property from favorites
isFavoritepropertyIdCheck if property is favorited

Auth State

PropertyTypeDescription
userUser | nullCurrent authenticated user
loadingbooleanAuthentication operation in progress
errorstring | nullCurrent error message
favoritePropertyIdsstring[]Array of favorited property IDs

Build docs developers (and LLMs) love