Flexible Authentication: This hook provides a complete authentication solution with customizable storage, fetching logic, automatic token refresh, and cross-tab synchronization. Perfect for applications that need custom authentication flows beyond typical OAuth providers.
Main Authentication
Not Authenticated
Demonstration of the main auth hook with customizable storage and auto-refresh
Demo credentials:
Email: user@example.com
Password: password
Storage Comparison
Compare localStorage vs sessionStorage behavior
localStorage
Persists across browser sessions
ActivesessionStorage
Cleared when tab closes
InactiveSwitch storage types and notice how authentication persists differently. Try logging in, switching tabs, or refreshing the page.
Installation
Props
Prop | Type | Default | Description |
---|---|---|---|
storageKey | string | 'better-auth' | Storage key prefix for storing authentication data |
storage | StorageLike | localStorage | Custom storage implementation (localStorage, sessionStorage, etc.) |
loginUrl | string | '/api/login' | Login endpoint URL |
refreshUrl | string | undefined | Token refresh endpoint URL |
logoutUrl | string | undefined | Logout endpoint URL for server-side logout |
refreshInterval | number | undefined | Auto-refresh interval in milliseconds |
onAuthChange | (token: string | null, user: TUser | null) => void | undefined | Callback fired when authentication state changes |
fetcher | (credentials: TCredentials, url: string) => Promise<BetterAuthResponse<TUser>> | undefined | Custom fetcher function for authentication requests |
Data
Property | Type | Description |
---|---|---|
token | string | null | Current authentication token |
user | TUser | null | Current user object |
loading | boolean | Loading state for authentication operations |
error | Error | null | Current error state |
isAuthenticated | boolean | Computed boolean indicating if user is authenticated |
login | (credentials: TCredentials) => Promise<void> | Function to authenticate user |
logout | () => void | Function to logout user |
refresh | () => Promise<void> | Function to manually refresh authentication token |
updateUser | (user: TUser, token?: string) => void | Function to update user data and optionally token |
clearError | () => void | Function to clear current error state |
Types
StorageLike
interface StorageLike {
getItem(key: string): string | null;
setItem(key: string, value: string): void;
removeItem(key: string): void;
}
BetterAuthResponse
interface BetterAuthResponse<TUser> {
token: string;
user: TUser;
}
DefaultCredentials
interface DefaultCredentials extends Record<string, string | number | boolean> {
email: string;
password: string;
}
Key Features & Details
- Customizable Storage: Use localStorage, sessionStorage, or provide your own storage implementation
- Custom Fetcher: Completely customize how authentication requests are made
- Automatic Token Refresh: Configure automatic token refresh at specified intervals
- Cross-tab Synchronization: Authentication state syncs across browser tabs automatically
- SSR Safe: Properly handles server-side rendering without hydration issues
- Type Safety: Full TypeScript support with generic user and credentials types
- Error Handling: Comprehensive error handling with clear error states
- Debug Logging: Development mode logging for debugging authentication flows
Common Use Cases
- Custom authentication APIs that don't use OAuth
- Applications requiring specific storage mechanisms (sessionStorage, custom storage)
- Token-based authentication with automatic refresh
- Multi-tab applications requiring synchronized auth state
- Applications with custom user credential structures
- Development and testing environments with mock authentication
Examples
Basic Usage
interface User {
id: string;
name: string;
email: string;
}
const auth = useBetterAuth<User>({
loginUrl: '/api/auth/login',
refreshUrl: '/api/auth/refresh',
});
const handleLogin = async () => {
try {
await auth.login({
email: 'user@example.com',
password: 'password',
});
} catch (error) {
console.error('Login failed:', error);
}
};
return (
<div>
{auth.isAuthenticated ? (
<div>
<p>Welcome, {auth.user?.name}!</p>
<button onClick={auth.logout}>Logout</button>
</div>
) : (
<button onClick={handleLogin}>Login</button>
)}
</div>
);
With Custom Storage
const auth = useBetterAuth<User>({
storage: sessionStorage, // Use sessionStorage instead of localStorage
storageKey: 'my-app-auth',
onAuthChange: (token, user) => {
console.log('Auth state changed:', { token: !!token, user });
},
});
return (
<div>
<p>Using sessionStorage - auth cleared when tab closes</p>
{/* ... rest of component */}
</div>
);
With Custom Fetcher
interface CustomCredentials extends Record<string, string | number | boolean> {
username: string;
password: string;
organizationId: string;
}
const auth = useBetterAuth<User, CustomCredentials>({
fetcher: async (credentials, url) => {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Organization-ID': credentials.organizationId,
},
body: JSON.stringify({
username: credentials.username,
password: credentials.password,
}),
});
if (!response.ok) {
throw new Error('Authentication failed');
}
const data = await response.json();
return {
token: data.access_token,
user: data.user_profile,
};
},
});
const handleLogin = async () => {
await auth.login({
username: 'john_doe',
password: 'secret123',
organizationId: 'org_123',
});
};
With Auto-Refresh
const auth = useBetterAuth<User>({
loginUrl: '/api/auth/login',
refreshUrl: '/api/auth/refresh',
refreshInterval: 5 * 60 * 1000, // Refresh every 5 minutes
onAuthChange: (token, user) => {
if (!token && user) {
// Token expired and refresh failed
console.log('Session expired, redirecting to login');
window.location.href = '/login';
}
},
});
return (
<div>
<p>Token refreshes automatically every 5 minutes</p>
<button onClick={auth.refresh}>Manual Refresh</button>
</div>
);
With Error Handling
const auth = useBetterAuth<User>({
loginUrl: '/api/auth/login',
});
const handleLogin = async () => {
try {
await auth.login(credentials);
} catch (error) {
// Error is also available in auth.error
console.error('Login error:', error.message);
}
};
return (
<div>
{auth.error && (
<div className="error">
<p>Error: {auth.error.message}</p>
<button onClick={auth.clearError}>Clear Error</button>
</div>
)}
<button onClick={handleLogin} disabled={auth.loading}>
{auth.loading ? 'Logging in...' : 'Login'}
</button>
</div>
);
Updating User Data
const auth = useBetterAuth<User>({
loginUrl: '/api/auth/login',
});
const handleUpdateProfile = async (newUserData: Partial<User>) => {
if (!auth.user) return;
try {
// Update user on server
const response = await fetch('/api/user/profile', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${auth.token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(newUserData),
});
const updatedUser = await response.json();
// Update local user state
auth.updateUser(updatedUser);
} catch (error) {
console.error('Profile update failed:', error);
}
};
return (
<div>
{auth.user && (
<div>
<p>Name: {auth.user.name}</p>
<button onClick={() => handleUpdateProfile({ name: 'New Name' })}>
Update Name
</button>
</div>
)}
</div>
);
Related
- Better Auth - Modern authentication library for TypeScript
- NextAuth.js - Authentication for Next.js applications
- Supabase Auth - Authentication with Supabase
- Firebase Auth - Authentication with Firebase