@identity-base/react-client
v0.7.9
Published
Headless React authentication client for Identity.Base
Maintainers
Readme
@identity-base/react-client
Headless React authentication client for Identity.Base. Provides all the heavy lifting for authentication flows while giving you complete control over your UI.
Built on (and depends on) @identity-base/client-core.
Features
- 🔐 Complete OAuth2/PKCE flow - Authorization code with PKCE
- 🔄 Automatic token management - Refresh tokens, storage, expiration
- 🎯 Headless design - Zero UI opinions, bring your own components
- 🛡️ Route protection -
<ProtectedRoute>anduseRequireAuth - 📱 MFA support - Email and SMS multi-factor authentication
- 👤 Profile management - User profiles with custom metadata
- 🔗 External providers - Google, Microsoft, Apple sign-in
- ⚡ Cross-tab sync - Authentication state synced across tabs
- 📦 TypeScript first - Complete type definitions
- 🪝 React hooks - Clean, composable API
Installation
npm install @identity-base/react-clientQuick Start
1. Wrap your app with IdentityProvider
import { IdentityProvider, enableDebugLogging } from '@identity-base/react-client'
// Enable debug logging in development
if (import.meta.env.DEV) {
enableDebugLogging(true)
}
function App() {
return (
<IdentityProvider
config={{
apiBase: 'https://your-identity-api.com',
clientId: 'your-spa-client-id',
redirectUri: 'https://your-app.com/auth/callback',
}}
>
<Router>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/dashboard" element={<DashboardPage />} />
<Route path="/auth/callback" element={<CallbackPage />} />
</Routes>
</Router>
</IdentityProvider>
)
}2. Create a login form
import { useLogin } from '@identity-base/react-client'
function LoginPage() {
const { login, isLoading, error } = useLogin({
onSuccess: () => navigate('/dashboard')
})
const handleSubmit = async (e: FormEvent) => {
e.preventDefault()
await login({ email, password })
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
disabled={isLoading}
/>
<input
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Signing in...' : 'Sign In'}
</button>
{error && <div className="error">{error.message}</div>}
</form>
)
}3. Protect routes
import { ProtectedRoute, useAuth } from '@identity-base/react-client'
function DashboardPage() {
return (
<ProtectedRoute fallback={<div>Please sign in...</div>}>
<Dashboard />
</ProtectedRoute>
)
}
function Dashboard() {
const { user, logout } = useAuth()
return (
<div>
<h1>Welcome {user?.displayName}!</h1>
<button onClick={logout}>Sign Out</button>
</div>
)
}4. Handle OAuth callback
import { useAuthorization } from '@identity-base/react-client'
import { useSearchParams } from 'react-router-dom'
function CallbackPage() {
const [params] = useSearchParams()
const { handleCallback, isLoading, error } = useAuthorization({
onSuccess: () => navigate('/dashboard')
})
useEffect(() => {
const code = params.get('code')
const state = params.get('state')
if (code && state) {
handleCallback(code, state)
}
}, [params, handleCallback])
if (isLoading) return <div>Completing sign in...</div>
if (error) return <div>Sign in failed: {error.message}</div>
return <div>Processing...</div>
}Configuration
interface IdentityConfig {
// Required
apiBase: string // 'https://your-identity-api.com'
clientId: string // Your OAuth2 client ID
redirectUri: string // Where to redirect after auth
// Optional
scope?: string // OAuth2 scopes (default: 'openid profile email offline_access')
tokenStorage?: 'localStorage' | 'sessionStorage' | 'memory'
autoRefresh?: boolean // Auto refresh access tokens (default: true)
timeout?: number // API timeout in ms (default: 10000)
}Token Auto-Refresh
- When
autoRefreshistrue(default), the SDK inspects the JWTexpclaim and requests a new access token 30 seconds before expiry. If refresh fails, cached tokens are cleared and the error is rethrown so the UI can prompt for a fresh sign-in. - When
autoRefreshisfalse, expired access tokens are removed from storage andensureValidToken()resolves tonull, leaving it to the host app to restart authentication. - Auto-refresh applies regardless of storage backend (
localStorage,sessionStorage, or in-memory) and requires a refresh token to be available.
Hooks API
useAuth()
const {
user, // Current user profile or null
isAuthenticated, // Boolean auth state
isLoading, // Initial loading state
error, // Any auth errors
refreshUser, // Manually refresh user data
} = useAuth()useLogin()
const {
login, // (credentials) => Promise<LoginResponse>
logout, // () => Promise<void>
isLoading, // Boolean loading state
error, // Login/logout errors
} = useLogin({
onSuccess: (response) => {}, // Optional success callback
onError: (error) => {}, // Optional error callback
})useRegister()
const {
register, // (userData) => Promise<{correlationId}>
isLoading, // Boolean loading state
error, // Registration errors
} = useRegister({
onSuccess: (response) => {}, // Optional success callback
})useAuthorization()
const {
startAuthorization, // () => Promise<void> (redirects to auth)
handleCallback, // (code, state) => Promise<User>
isLoading,
error,
} = useAuthorization({
onSuccess: () => {}, // Called after successful auth
})Route Protection
ProtectedRoute Component
<ProtectedRoute
fallback={<LoginPrompt />}
redirectTo="/login"
onUnauthenticated={() => {}}
>
<SecretContent />
</ProtectedRoute>useRequireAuth Hook
function SecurePage() {
const user = useRequireAuth({
redirectTo: '/login',
onUnauthenticated: () => notify('Please sign in')
})
// user is null during loading/redirecting
// user is UserProfile once authenticated
if (!user) return null
return <div>Hello {user.displayName}</div>
}Advanced Usage
Debug Logging
The client includes built-in debug logging to help troubleshoot authentication flows. Debug logging is disabled by default.
Enable via Code (Recommended)
import { enableDebugLogging } from '@identity-base/react-client'
// Enable only in development
if (import.meta.env.DEV) {
enableDebugLogging(true)
}
// Or conditionally based on environment variable
if (import.meta.env.VITE_ENABLE_IDENTITY_DEBUG === 'true') {
enableDebugLogging(true)
}Enable via Browser Console
// Enable debug logging at runtime
__enableIdentityDebug(true) // Returns true
__enableIdentityDebug(false) // Returns false
// Check current debug state
window.__identityDebugEnabled // true/falseDebug Output
When enabled, you'll see detailed logs for:
- Authentication flows (login, logout, token refresh)
- API requests and responses
- MFA challenges and verification
- User profile updates
- Authorization code exchanges
Example debug output:
IdentityProvider.tsx: Module loading
IdentityProvider: Creating new instance with config: {...}
IdentityProvider.refreshUser: Starting user refresh
Using Bearer token authentication for /users/me
useMfa.verifyChallenge: Starting MFA verificationCustom Token Storage
import { createTokenStorage } from '@identity-base/react-client'
const storage = createTokenStorage('sessionStorage')
// or implement your own TokenStorage interfaceDirect Auth Manager Access
import { useIdentityContext } from '@identity-base/react-client'
function AdvancedComponent() {
const { authManager } = useIdentityContext()
const handleExternalAuth = () => {
const url = authManager.buildExternalStartUrl('google', 'login', '/dashboard')
window.location.href = url
}
}TypeScript
The package is built with TypeScript and includes complete type definitions:
import type {
UserProfile,
LoginRequest,
IdentityConfig,
ApiError
} from '@identity-base/react-client'
// Debug utilities
import { enableDebugLogging, debugLog } from '@identity-base/react-client'Troubleshooting
Enable Debug Logging
If you're experiencing issues with authentication flows, enable debug logging to see detailed information:
import { enableDebugLogging } from '@identity-base/react-client'
// Enable in development
if (process.env.NODE_ENV === 'development') {
enableDebugLogging(true)
}
// Or enable via browser console
// __enableIdentityDebug(true)Common Issues
Authentication Not Working
- Check debug logs - Enable debug logging to see API requests/responses
- Verify configuration - Ensure
apiBase,clientId, andredirectUriare correct - CORS issues - Make sure your API allows requests from your frontend origin
- Token storage - Try different storage options (
localStorage,sessionStorage,memory)
MFA Issues
// Check MFA debug output
__enableIdentityDebug(true)
// Look for these debug messages:
// - "useMfa.verifyChallenge: Starting MFA verification"
// - "useMfa.verifyChallenge: MFA verification successful"
// - "useMfa.verifyChallenge: MFA verification failed"OAuth/PKCE Issues
// Debug OAuth flows
__enableIdentityDebug(true)
// Check for PKCE-related logs:
// - "IdentityProvider: Creating new instance with config"
// - Authorization URL generation
// - Token exchange logsBrowser Console Debug Commands
// Enable/disable debug logging
__enableIdentityDebug(true) // Enable
__enableIdentityDebug(false) // Disable
// Check current state
window.__identityDebugEnabled // true or false
// Manual user refresh (when authenticated)
window.__identityRefreshUser?.()Performance Tips
- Use memory storage for better performance in SPAs:
tokenStorage: 'memory' - Disable debug logging in production to reduce console output
- Use route-level protection with
<ProtectedRoute>for better UX
License
MIT
