@bastionauth/react
v0.1.7
Published
React SDK for BastionAuth - Beautiful, secure authentication components
Downloads
827
Maintainers
Readme
@bastionauth/react
📚 Table of Contents
- What is BastionAuth?
- Why Choose @bastionauth/react?
- Installation
- Quick Start
- Core Concepts
- Components Reference
- Hooks Reference
- Styling & Customization
- Advanced Usage
- Real-World Examples
- Best Practices
- Troubleshooting
- Framework Guides
- FAQs
- Migration Guide
- API Reference
🏰 What is BastionAuth?
BastionAuth is a complete, self-hostable enterprise authentication system that gives you the developer experience of Clerk with full data sovereignty and zero vendor lock-in.
Key Features
- 🔐 Complete Authentication - Email/password, OAuth (Google, GitHub, Microsoft, Apple, LinkedIn), magic links, passkeys
- 🔑 Multi-Factor Authentication - TOTP authenticator apps, backup codes, SMS, WebAuthn
- 🏢 Organizations & Teams - Full multi-tenancy with invitations and role management
- 🎨 Beautiful UI Components - Pre-built, customizable components with glass morphism design
- ⚡ Powerful Hooks - React hooks for every authentication need
- 🔒 Enterprise Security - Argon2id hashing, RS256 JWT, breach detection, audit logging
- 📱 Responsive Design - Mobile-first, accessible, and fully responsive
- 🎯 Zero Config - Works out of the box with sensible defaults
- 🛠️ Fully Customizable - Theme, styles, behavior - everything is customizable
- 🚀 Production Ready - Battle-tested in production environments
Learn more: https://bastionauth.dev
💎 Why Choose @bastionauth/react?
vs Building from Scratch
| Feature | From Scratch | @bastionauth/react | |---------|--------------|-------------------| | Time to Production | 6-12 weeks | < 1 hour | | UI Components | Build yourself | Pre-built & beautiful | | OAuth Integration | Complex setup | 5 providers included | | MFA Support | Weeks of work | Built-in | | Organization Management | Months of development | Ready to use | | Security Best Practices | Research required | Enterprise-grade | | Mobile Responsive | Custom CSS | Responsive by default | | Dark Mode | Manual implementation | Automatic |
vs Other Solutions
| Feature | Auth0 React SDK | Clerk React | @bastionauth/react | |---------|----------------|-------------|-------------------| | Self-Hosted | ❌ | ❌ | ✅ | | No Per-MAU Costs | ❌ | ❌ | ✅ | | Pre-built UI | Limited | ✅ | ✅ | | Full Customization | Limited | Limited | ✅ | | Organization Support | Paid | Paid | ✅ Free | | Open Source | ❌ | ❌ | ✅ | | Data Sovereignty | ❌ | ❌ | ✅ |
📦 Installation
npm install @bastionauth/reactOr using other package managers:
# pnpm (recommended)
pnpm add @bastionauth/react
# yarn
yarn add @bastionauth/react
# bun
bun add @bastionauth/reactPeer Dependencies
{
"react": "^18.0.0",
"react-dom": "^18.0.0"
}TypeScript Support
This package includes TypeScript definitions out of the box. No @types package needed!
🚀 Quick Start
1. Wrap Your App with BastionProvider
// src/App.tsx or src/index.tsx
import React from 'react';
import { BastionProvider } from '@bastionauth/react';
function App() {
return (
<BastionProvider
apiUrl="https://api.bastionauth.dev"
// or for self-hosted:
// apiUrl="http://localhost:4000"
>
<YourApp />
</BastionProvider>
);
}
export default App;2. Add Sign-In Component
import { SignIn } from '@bastionauth/react';
function SignInPage() {
return (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
minHeight: '100vh'
}}>
<SignIn redirectUrl="/dashboard" />
</div>
);
}3. Use Authentication State
import { useUser, useAuth } from '@bastionauth/react';
function Dashboard() {
const { user, isLoaded } = useUser();
const { signOut } = useAuth();
if (!isLoaded) {
return <div>Loading...</div>;
}
return (
<div>
<h1>Welcome, {user?.firstName}!</h1>
<p>Email: {user?.email}</p>
<button onClick={() => signOut()}>Sign Out</button>
</div>
);
}4. Protect Routes
import { useAuth } from '@bastionauth/react';
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isSignedIn, isLoaded } = useAuth();
if (!isLoaded) {
return <div>Loading...</div>;
}
if (!isSignedIn) {
return <Navigate to="/sign-in" replace />;
}
return <>{children}</>;
}
// Usage
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>That's it! 🎉 You now have a fully functional authentication system.
🎯 Core Concepts
BastionProvider
The BastionProvider is a React Context Provider that wraps your application and provides authentication state to all child components.
<BastionProvider
apiUrl="https://api.bastionauth.dev"
appearance={{
theme: 'dark',
variables: {
colorPrimary: '#6366f1',
},
}}
localization={{
locale: 'en-US',
}}
>
<App />
</BastionProvider>Authentication Flow
- User visits protected route → Redirected to sign-in
- User signs in → Session created, user data loaded
- Hooks update automatically → UI reflects authenticated state
- User navigates → Session persisted across pages
- User signs out → Session cleared, redirected
Session Management
- Automatic token refresh - Access tokens refresh transparently
- Persistent sessions - Sessions survive page reloads
- Secure storage - Tokens stored in secure, HTTP-only cookies
- Multi-tab sync - Auth state synced across browser tabs
🎨 Components Reference
<SignIn />
Pre-built sign-in form with email/password and OAuth options.
Basic Usage
import { SignIn } from '@bastionauth/react';
<SignIn />Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| redirectUrl | string | '/' | URL to redirect after successful sign-in |
| signUpUrl | string | '/sign-up' | Link to sign-up page |
| forgotPasswordUrl | string | '/forgot-password' | Link to password reset |
| appearance | Appearance | - | Customize theme and styles |
| showOAuthProviders | boolean | true | Show OAuth sign-in buttons |
| oAuthProviders | Provider[] | ['google', 'github'] | Which OAuth providers to show |
| onSuccess | (user) => void | - | Callback after successful sign-in |
| onError | (error) => void | - | Callback on sign-in error |
Advanced Example
<SignIn
redirectUrl="/dashboard"
signUpUrl="/sign-up"
appearance={{
theme: 'dark',
elements: {
formButton: {
fontSize: '16px',
padding: '12px 24px',
},
card: {
boxShadow: '0 10px 40px rgba(0,0,0,0.1)',
},
},
variables: {
colorPrimary: '#6366f1',
borderRadius: '8px',
},
}}
oAuthProviders={['google', 'github', 'microsoft']}
onSuccess={(user) => {
console.log('User signed in:', user.email);
// Track analytics, show welcome message, etc.
}}
onError={(error) => {
console.error('Sign-in error:', error);
// Show custom error notification
}}
/>OAuth Providers
Supported providers:
- 🔵 Google -
'google' - ⚫ GitHub -
'github' - 🔷 Microsoft -
'microsoft' - 🍎 Apple -
'apple' - 🔗 LinkedIn -
'linkedin'
<SignIn
oAuthProviders={['google', 'github', 'microsoft', 'apple', 'linkedin']}
/><SignUp />
Pre-built registration form with validation and password strength indicator.
Basic Usage
import { SignUp } from '@bastionauth/react';
<SignUp />Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| redirectUrl | string | '/' | URL to redirect after sign-up |
| signInUrl | string | '/sign-in' | Link to sign-in page |
| appearance | Appearance | - | Customize theme and styles |
| showOAuthProviders | boolean | true | Show OAuth sign-up buttons |
| oAuthProviders | Provider[] | ['google', 'github'] | Which OAuth providers to show |
| requirePhone | boolean | false | Require phone number during sign-up |
| requireUsername | boolean | false | Require username during sign-up |
| collectMetadata | MetadataField[] | [] | Additional fields to collect |
| onSuccess | (user) => void | - | Callback after successful sign-up |
| onError | (error) => void | - | Callback on sign-up error |
Collecting Custom Metadata
<SignUp
redirectUrl="/onboarding"
collectMetadata={[
{
name: 'company',
label: 'Company Name',
type: 'text',
required: true,
},
{
name: 'role',
label: 'Your Role',
type: 'select',
options: ['Developer', 'Designer', 'Manager', 'Other'],
required: true,
},
{
name: 'acceptTerms',
label: 'I agree to the Terms of Service',
type: 'checkbox',
required: true,
},
]}
onSuccess={(user) => {
// User metadata is stored in user.metadata
console.log('Company:', user.metadata.company);
console.log('Role:', user.metadata.role);
}}
/>Password Strength Indicator
The <SignUp /> component includes a built-in password strength indicator:
- Weak (red) - Basic password
- Medium (yellow) - Acceptable password
- Strong (green) - Good password
- Very Strong (blue) - Excellent password
Requirements:
- Minimum 8 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one number
- At least one special character
<UserButton />
User avatar dropdown with profile and sign-out options.
Basic Usage
import { UserButton } from '@bastionauth/react';
<UserButton />Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| afterSignOutUrl | string | '/' | URL to redirect after sign-out |
| showName | boolean | false | Show user name next to avatar |
| showEmail | boolean | false | Show user email in dropdown |
| appearance | Appearance | - | Customize theme and styles |
| userProfileUrl | string | '/profile' | Link to user profile page |
| organizationUrl | string | '/organization' | Link to organization page |
| customMenuItems | MenuItem[] | [] | Additional menu items |
Advanced Example
<UserButton
afterSignOutUrl="/goodbye"
showName={true}
showEmail={true}
customMenuItems={[
{
label: 'Settings',
onClick: () => navigate('/settings'),
icon: <SettingsIcon />,
},
{
label: 'Billing',
onClick: () => navigate('/billing'),
icon: <CreditCardIcon />,
},
{ type: 'divider' },
{
label: 'Help & Support',
onClick: () => window.open('https://support.example.com'),
icon: <HelpIcon />,
},
]}
appearance={{
elements: {
userButtonAvatarBox: {
width: '40px',
height: '40px',
},
},
}}
/><UserProfile />
Complete user profile management component.
Basic Usage
import { UserProfile } from '@bastionauth/react';
<UserProfile />Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| appearance | Appearance | - | Customize theme and styles |
| sections | Section[] | All sections | Which sections to show |
| onSuccess | () => void | - | Callback after profile update |
Available Sections
<UserProfile
sections={[
'account', // Email, name, username
'security', // Password, MFA
'sessions', // Active sessions
'connected-accounts', // OAuth connections
'delete-account', // Account deletion
]}
/>Custom Profile Page
function ProfilePage() {
return (
<div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
<h1>My Profile</h1>
<UserProfile
sections={['account', 'security']}
appearance={{
elements: {
card: {
boxShadow: 'none',
border: '1px solid #e5e7eb',
},
},
}}
onSuccess={() => {
alert('Profile updated successfully!');
}}
/>
</div>
);
}<OrganizationSwitcher />
Dropdown to switch between user's organizations.
Basic Usage
import { OrganizationSwitcher } from '@bastionauth/react';
<OrganizationSwitcher />Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| appearance | Appearance | - | Customize theme and styles |
| hidePersonal | boolean | false | Hide personal workspace option |
| createOrganizationUrl | string | - | Custom URL for org creation |
| organizationProfileUrl | string | - | Custom URL for org profile |
| afterSelectOrganization | (org) => void | - | Callback after org switch |
Advanced Example
<OrganizationSwitcher
hidePersonal={false}
createOrganizationUrl="/create-org"
afterSelectOrganization={(org) => {
console.log('Switched to:', org.name);
// Update app context, refetch data, etc.
}}
appearance={{
elements: {
organizationSwitcherTrigger: {
padding: '8px 16px',
fontSize: '14px',
},
},
}}
/><OrganizationProfile />
Complete organization management component.
Basic Usage
import { OrganizationProfile } from '@bastionauth/react';
<OrganizationProfile />Features
- ✅ Organization settings (name, logo, etc.)
- ✅ Member management (invite, remove, change roles)
- ✅ Role configuration
- ✅ Invitations management
- ✅ Billing & subscription (if enabled)
- ✅ Danger zone (delete organization)
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| appearance | Appearance | - | Customize theme and styles |
| sections | Section[] | All sections | Which sections to show |
<OrganizationProfile
sections={[
'general', // Name, logo, slug
'members', // Member list & management
'invitations', // Pending invitations
'settings', // Organization settings
]}
/><CreateOrganization />
Modal or page component for creating new organizations.
Basic Usage
import { CreateOrganization } from '@bastionauth/react';
<CreateOrganization />Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| afterCreateOrganization | (org) => void | - | Callback after creation |
| redirectUrl | string | - | URL to redirect after creation |
| skipInvitationScreen | boolean | false | Skip member invitation step |
| appearance | Appearance | - | Customize theme and styles |
<CreateOrganization
afterCreateOrganization={(org) => {
console.log('Created organization:', org.name);
navigate(`/org/${org.id}`);
}}
skipInvitationScreen={false}
/><RedirectToSignIn />
Component that automatically redirects to sign-in page.
Basic Usage
import { RedirectToSignIn, useAuth } from '@bastionauth/react';
function PrivatePage() {
const { isSignedIn, isLoaded } = useAuth();
if (!isLoaded) {
return <div>Loading...</div>;
}
if (!isSignedIn) {
return <RedirectToSignIn />;
}
return <div>Private content</div>;
}With Return URL
<RedirectToSignIn returnUrl={window.location.href} />🪝 Hooks Reference
useAuth()
Access authentication state and methods.
Returns
{
// Authentication Status
isSignedIn: boolean; // Is user authenticated?
isLoaded: boolean; // Is auth state loaded?
// User Information
userId: string | null; // Current user ID
sessionId: string | null; // Current session ID
orgId: string | null; // Active organization ID
orgRole: string | null; // User's role in active org
// Methods
signOut: () => Promise<void>;
getToken: () => Promise<string | null>;
}Example Usage
import { useAuth } from '@bastionauth/react';
function NavBar() {
const { isSignedIn, isLoaded, userId, signOut } = useAuth();
if (!isLoaded) {
return <div>Loading...</div>;
}
return (
<nav>
<h1>My App</h1>
{isSignedIn ? (
<>
<span>User ID: {userId}</span>
<button onClick={() => signOut()}>Sign Out</button>
</>
) : (
<a href="/sign-in">Sign In</a>
)}
</nav>
);
}Getting Access Token
const { getToken } = useAuth();
// Get token for API calls
const token = await getToken();
const response = await fetch('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});useUser()
Access and update user data.
Returns
{
// User Data
user: User | null; // Complete user object
isLoaded: boolean; // Is user data loaded?
// Methods
update: (data: Partial<User>) => Promise<User>;
reload: () => Promise<User>;
}Example Usage
import { useUser } from '@bastionauth/react';
function ProfileSettings() {
const { user, isLoaded, update } = useUser();
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
useEffect(() => {
if (user) {
setFirstName(user.firstName || '');
setLastName(user.lastName || '');
}
}, [user]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await update({ firstName, lastName });
alert('Profile updated!');
} catch (error) {
alert('Update failed');
}
};
if (!isLoaded || !user) {
return <div>Loading...</div>;
}
return (
<form onSubmit={handleSubmit}>
<input
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
placeholder="First Name"
/>
<input
value={lastName}
onChange={(e) => setLastName(e.target.value)}
placeholder="Last Name"
/>
<button type="submit">Save Changes</button>
</form>
);
}User Object Structure
interface User {
id: string;
email: string;
emailVerified: boolean;
firstName: string | null;
lastName: string | null;
username: string | null;
phone: string | null;
phoneVerified: boolean;
avatarUrl: string | null;
mfaEnabled: boolean;
status: 'active' | 'suspended' | 'banned';
role: 'user' | 'admin' | 'moderator';
metadata: Record<string, any>;
createdAt: Date;
updatedAt: Date;
lastActiveAt: Date;
}useSession()
Access session information.
Returns
{
session: Session | null; // Current session
isLoaded: boolean; // Is session loaded?
revoke: () => Promise<void>; // Revoke current session
}Example Usage
import { useSession } from '@bastionauth/react';
function SessionInfo() {
const { session, isLoaded, revoke } = useSession();
if (!isLoaded || !session) {
return <div>No active session</div>;
}
return (
<div>
<h2>Current Session</h2>
<p>ID: {session.id}</p>
<p>IP Address: {session.ipAddress}</p>
<p>User Agent: {session.userAgent}</p>
<p>Created: {session.createdAt.toLocaleString()}</p>
<p>Expires: {session.expiresAt.toLocaleString()}</p>
<button onClick={() => revoke()}>Revoke This Session</button>
</div>
);
}useOrganization()
Access active organization data.
Returns
{
organization: Organization | null;
membership: OrganizationMembership | null;
isLoaded: boolean;
}Example Usage
import { useOrganization } from '@bastionauth/react';
function OrganizationDashboard() {
const { organization, membership, isLoaded } = useOrganization();
if (!isLoaded) {
return <div>Loading...</div>;
}
if (!organization) {
return <div>No organization selected</div>;
}
return (
<div>
<h1>{organization.name}</h1>
<p>Your role: {membership?.role}</p>
<p>Member since: {membership?.joinedAt.toLocaleDateString()}</p>
{membership?.role === 'admin' && (
<button>Organization Settings</button>
)}
</div>
);
}useOrganizationList()
List user's organizations.
Returns
{
organizations: Organization[];
isLoaded: boolean;
setActive: (orgId: string) => Promise<void>;
createOrganization: (data) => Promise<Organization>;
}Example Usage
import { useOrganizationList } from '@bastionauth/react';
function OrganizationList() {
const { organizations, isLoaded, setActive, createOrganization } = useOrganizationList();
const [newOrgName, setNewOrgName] = useState('');
const handleCreate = async () => {
try {
const org = await createOrganization({ name: newOrgName });
await setActive(org.id);
alert(`Created and switched to ${org.name}`);
} catch (error) {
alert('Failed to create organization');
}
};
if (!isLoaded) {
return <div>Loading organizations...</div>;
}
return (
<div>
<h2>Your Organizations</h2>
{organizations.map((org) => (
<div key={org.id}>
<h3>{org.name}</h3>
<button onClick={() => setActive(org.id)}>
Switch to this org
</button>
</div>
))}
<h3>Create New Organization</h3>
<input
value={newOrgName}
onChange={(e) => setNewOrgName(e.target.value)}
placeholder="Organization name"
/>
<button onClick={handleCreate}>Create</button>
</div>
);
}useSignIn()
Programmatic sign-in control.
Returns
{
signIn: (credentials: SignInCredentials) => Promise<User>;
isLoading: boolean;
error: Error | null;
}Example Usage
import { useSignIn } from '@bastionauth/react';
function CustomSignInForm() {
const { signIn, isLoading, error } = useSignIn();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
const user = await signIn({ email, password });
console.log('Signed in as:', user.email);
// Navigate to dashboard
} catch (err) {
console.error('Sign in failed:', err);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
disabled={isLoading}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
disabled={isLoading}
/>
{error && <p style={{ color: 'red' }}>{error.message}</p>}
<button type="submit" disabled={isLoading}>
{isLoading ? 'Signing in...' : 'Sign In'}
</button>
</form>
);
}useSignUp()
Programmatic sign-up control.
Returns
{
signUp: (data: SignUpData) => Promise<User>;
isLoading: boolean;
error: Error | null;
}Example Usage
import { useSignUp } from '@bastionauth/react';
function CustomSignUpForm() {
const { signUp, isLoading, error } = useSignUp();
const [formData, setFormData] = useState({
email: '',
password: '',
firstName: '',
lastName: '',
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData(prev => ({
...prev,
[e.target.name]: e.target.value,
}));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
const user = await signUp(formData);
console.log('Account created:', user.email);
// Show success message, navigate, etc.
} catch (err) {
console.error('Sign up failed:', err);
}
};
return (
<form onSubmit={handleSubmit}>
<input
name="firstName"
value={formData.firstName}
onChange={handleChange}
placeholder="First Name"
disabled={isLoading}
/>
<input
name="lastName"
value={formData.lastName}
onChange={handleChange}
placeholder="Last Name"
disabled={isLoading}
/>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
disabled={isLoading}
/>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
placeholder="Password"
disabled={isLoading}
/>
{error && <p style={{ color: 'red' }}>{error.message}</p>}
<button type="submit" disabled={isLoading}>
{isLoading ? 'Creating account...' : 'Sign Up'}
</button>
</form>
);
}🎨 Styling & Customization
Theme Customization
Light & Dark Mode
<BastionProvider
apiUrl="..."
appearance={{
theme: 'dark', // or 'light'
}}
>
<App />
</BastionProvider>Custom Color Scheme
<BastionProvider
apiUrl="..."
appearance={{
theme: 'light',
variables: {
// Primary brand color
colorPrimary: '#6366f1',
// Background colors
colorBackground: '#ffffff',
colorBackgroundSecondary: '#f9fafb',
// Text colors
colorText: '#111827',
colorTextSecondary: '#6b7280',
// Border
colorBorder: '#e5e7eb',
borderRadius: '0.5rem',
// Input
colorInputBackground: '#ffffff',
colorInputText: '#111827',
// Button
colorButtonPrimary: '#6366f1',
colorButtonPrimaryHover: '#4f46e5',
colorButtonPrimaryText: '#ffffff',
// Success/Error/Warning
colorSuccess: '#10b981',
colorError: '#ef4444',
colorWarning: '#f59e0b',
// Spacing
spacingUnit: '1rem',
// Typography
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto',
fontSize: '14px',
fontWeight: '400',
fontWeightMedium: '500',
fontWeightBold: '600',
},
}}
>
<App />
</BastionProvider>Per-Component Styling
<SignIn
appearance={{
elements: {
// Target specific elements
card: {
backgroundColor: '#ffffff',
boxShadow: '0 10px 40px rgba(0,0,0,0.1)',
padding: '2rem',
},
headerTitle: {
fontSize: '24px',
fontWeight: '700',
color: '#111827',
},
headerSubtitle: {
color: '#6b7280',
},
formFieldInput: {
borderRadius: '8px',
padding: '12px 16px',
fontSize: '16px',
},
formButton: {
backgroundColor: '#6366f1',
color: '#ffffff',
padding: '12px 24px',
borderRadius: '8px',
fontSize: '16px',
fontWeight: '600',
},
footerActionLink: {
color: '#6366f1',
textDecoration: 'none',
},
},
}}
/>CSS Classes
All components include CSS classes for custom styling:
/* Sign In Card */
.bastion-sign-in {
/* Custom styles */
}
.bastion-sign-in__card {
max-width: 400px;
margin: 0 auto;
}
.bastion-sign-in__header {
text-align: center;
}
.bastion-sign-in__form {
/* Form styles */
}
/* User Button */
.bastion-user-button {
cursor: pointer;
}
.bastion-user-button__avatar {
width: 40px;
height: 40px;
border-radius: 50%;
}
.bastion-user-button__dropdown {
position: absolute;
top: 100%;
right: 0;
}Complete Styling Example
import { BastionProvider, SignIn } from '@bastionauth/react';
import './custom-auth.css';
function App() {
return (
<BastionProvider
apiUrl="..."
appearance={{
theme: 'light',
variables: {
colorPrimary: '#8b5cf6',
borderRadius: '12px',
fontFamily: '"Inter", -apple-system, sans-serif',
},
elements: {
card: {
boxShadow: '0 20px 60px rgba(139, 92, 246, 0.15)',
border: '1px solid rgba(139, 92, 246, 0.1)',
},
formButton: {
textTransform: 'uppercase',
letterSpacing: '0.05em',
fontWeight: '600',
},
},
}}
>
<SignIn className="custom-sign-in" />
</BastionProvider>
);
}🚀 Advanced Usage
Conditional Rendering Based on Auth
import { useAuth } from '@bastionauth/react';
function App() {
const { isSignedIn, isLoaded } = useAuth();
if (!isLoaded) {
return <LoadingSpinner />;
}
return isSignedIn ? <AuthenticatedApp /> : <PublicApp />;
}
function AuthenticatedApp() {
return (
<div>
<NavBar />
<Dashboard />
</div>
);
}
function PublicApp() {
return (
<div>
<LandingPage />
</div>
);
}Role-Based Access Control
import { useUser } from '@bastionauth/react';
function AdminPanel() {
const { user } = useUser();
if (!user || user.role !== 'admin') {
return <div>Access denied. Admin only.</div>;
}
return (
<div>
<h1>Admin Panel</h1>
{/* Admin-only content */}
</div>
);
}Organization-Based Features
import { useOrganization } from '@bastionauth/react';
function FeatureToggle({ children, requireOrg = false }) {
const { organization, membership } = useOrganization();
// Check if user is in an organization
if (requireOrg && !organization) {
return <div>This feature requires an organization.</div>;
}
// Check user's role in organization
if (membership && !['admin', 'owner'].includes(membership.role)) {
return <div>This feature requires admin access.</div>;
}
return <>{children}</>;
}
// Usage
<FeatureToggle requireOrg>
<OrganizationSettings />
</FeatureToggle>Multi-Step Authentication
import { useSignIn } from '@bastionauth/react';
import { useState } from 'react';
function MultiStepSignIn() {
const { signIn, isLoading, error } = useSignIn();
const [step, setStep] = useState<'credentials' | 'mfa'>('credentials');
const [credentials, setCredentials] = useState({ email: '', password: '' });
const [mfaCode, setMfaCode] = useState('');
const handleCredentials = async (e: React.FormEvent) => {
e.preventDefault();
try {
await signIn(credentials);
// If successful, user is signed in
} catch (error: any) {
if (error.code === 'mfa_required') {
// User has MFA enabled, show MFA step
setStep('mfa');
} else {
alert(error.message);
}
}
};
const handleMFA = async (e: React.FormEvent) => {
e.preventDefault();
try {
await signIn({
...credentials,
mfaCode,
});
// Successfully signed in with MFA
} catch (error: any) {
alert(error.message);
}
};
if (step === 'credentials') {
return (
<form onSubmit={handleCredentials}>
<input
type="email"
value={credentials.email}
onChange={(e) => setCredentials(prev => ({ ...prev, email: e.target.value }))}
placeholder="Email"
/>
<input
type="password"
value={credentials.password}
onChange={(e) => setCredentials(prev => ({ ...prev, password: e.target.value }))}
placeholder="Password"
/>
<button type="submit" disabled={isLoading}>
Continue
</button>
</form>
);
}
return (
<form onSubmit={handleMFA}>
<p>Enter the 6-digit code from your authenticator app</p>
<input
type="text"
value={mfaCode}
onChange={(e) => setMfaCode(e.target.value)}
placeholder="123456"
maxLength={6}
/>
<button type="submit" disabled={isLoading}>
Verify
</button>
</form>
);
}Session Monitoring
import { useSession } from '@bastionauth/react';
import { useEffect } from 'react';
function SessionMonitor() {
const { session } = useSession();
useEffect(() => {
if (!session) return;
const checkSession = () => {
const now = new Date();
const expiresAt = new Date(session.expiresAt);
const timeLeft = expiresAt.getTime() - now.getTime();
const minutesLeft = Math.floor(timeLeft / 60000);
if (minutesLeft <= 5) {
alert(`Your session will expire in ${minutesLeft} minutes`);
}
};
const interval = setInterval(checkSession, 60000); // Check every minute
return () => clearInterval(interval);
}, [session]);
return null;
}
// Add to your app
<SessionMonitor />💡 Real-World Examples
Complete React App with Routing
// App.tsx
import { BastionProvider } from '@bastionauth/react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { HomePage, SignInPage, SignUpPage, DashboardPage, ProfilePage } from './pages';
import { ProtectedRoute } from './components/ProtectedRoute';
export default function App() {
return (
<BastionProvider apiUrl={import.meta.env.VITE_BASTION_API_URL}>
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/sign-in" element={<SignInPage />} />
<Route path="/sign-up" element={<SignUpPage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<DashboardPage />
</ProtectedRoute>
}
/>
<Route
path="/profile"
element={
<ProtectedRoute>
<ProfilePage />
</ProtectedRoute>
}
/>
</Routes>
</BrowserRouter>
</BastionProvider>
);
}
// components/ProtectedRoute.tsx
import { useAuth } from '@bastionauth/react';
import { Navigate, useLocation } from 'react-router-dom';
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isSignedIn, isLoaded } = useAuth();
const location = useLocation();
if (!isLoaded) {
return <div>Loading...</div>;
}
if (!isSignedIn) {
return <Navigate to="/sign-in" state={{ from: location }} replace />;
}
return <>{children}</>;
}
// pages/SignInPage.tsx
import { SignIn } from '@bastionauth/react';
import { useNavigate, useLocation } from 'react-router-dom';
export function SignInPage() {
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname || '/dashboard';
return (
<div className="sign-in-container">
<SignIn
redirectUrl={from}
onSuccess={() => {
navigate(from, { replace: true });
}}
/>
</div>
);
}
// pages/DashboardPage.tsx
import { useUser, UserButton } from '@bastionauth/react';
export function DashboardPage() {
const { user } = useUser();
return (
<div>
<nav>
<h1>Dashboard</h1>
<UserButton showName afterSignOutUrl="/" />
</nav>
<main>
<h2>Welcome back, {user?.firstName}!</h2>
<p>Email: {user?.email}</p>
</main>
</div>
);
}✅ Best Practices
1. Always Check isLoaded
// ✅ Good
const { user, isLoaded } = useUser();
if (!isLoaded) {
return <LoadingSpinner />;
}
if (!user) {
return <SignIn />;
}
return <Dashboard user={user} />;
// ❌ Bad - Will flash errors
const { user } = useUser();
return <Dashboard user={user} />; // user might be null!2. Handle Loading States
// ✅ Good
const { isLoading, error, signIn } = useSignIn();
return (
<button disabled={isLoading}>
{isLoading ? 'Signing in...' : 'Sign In'}
</button>
);
// ❌ Bad - Poor UX
const { signIn } = useSignIn();
return <button>Sign In</button>;3. Proper Error Handling
// ✅ Good
const { error, signIn } = useSignIn();
const handleSubmit = async () => {
try {
await signIn({ email, password });
} catch (err) {
// Error is also available in the error state
console.error('Sign in failed:', err);
}
};
return (
<>
<form onSubmit={handleSubmit}>...</form>
{error && <ErrorMessage>{error.message}</ErrorMessage>}
</>
);4. Optimize Re-renders
// ✅ Good - Only re-render when specific data changes
const { userId } = useAuth();
// ❌ Bad - Re-renders on any auth state change
const auth = useAuth();5. Use TypeScript
// ✅ Good
import type { User } from '@bastionauth/react';
const { user } = useUser();
function displayUser(user: User) {
return `${user.firstName} ${user.lastName}`;
}
// ❌ Bad
function displayUser(user: any) {
return `${user.firstNam} ${user.lastName}`; // Typo won't be caught!
}🐛 Troubleshooting
Common Issues
Issue: "Cannot find module '@bastionauth/react'"
Solution:
# Make sure the package is installed
npm install @bastionauth/react
# Clear node_modules and reinstall
rm -rf node_modules package-lock.json
npm installIssue: User data not updating after profile change
Solution:
// After updating user data, reload it
const { update, reload } = useUser();
await update({ firstName: 'New Name' });
await reload(); // Force refresh user dataIssue: "hooks can only be called inside the body of a function component"
Solution:
// ✅ Good - Hooks inside component
function MyComponent() {
const { user } = useUser();
return <div>{user?.email}</div>;
}
// ❌ Bad - Hook outside component
const user = useUser();
function MyComponent() {
return <div>{user?.email}</div>;
}Issue: Redirects not working after sign-in
Solution:
// Make sure you're using proper navigation
import { useNavigate } from 'react-router-dom';
function SignInPage() {
const navigate = useNavigate();
return (
<SignIn
onSuccess={() => {
navigate('/dashboard'); // Programmatic navigation
}}
/>
);
}Issue: Session expires too quickly
Solution:
// Enable "Remember Me" for longer sessions
<SignIn
afterSignIn="/dashboard"
// User can choose to extend session
/>
// Or programmatically
const { signIn } = useSignIn();
await signIn({
email: '[email protected]',
password: 'password',
rememberMe: true, // Extends session to 30 days
});🎓 Framework Guides
Vite + React
# Create new Vite project
npm create vite@latest my-app -- --template react-ts
cd my-app
# Install BastionAuth
npm install @bastionauth/react
# Install React Router (optional but recommended)
npm install react-router-dom// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BastionProvider } from '@bastionauth/react';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<BastionProvider apiUrl={import.meta.env.VITE_BASTION_API_URL}>
<App />
</BastionProvider>
</React.StrictMode>
);Create React App
# Create new CRA project
npx create-react-app my-app --template typescript
cd my-app
# Install BastionAuth
npm install @bastionauth/react// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BastionProvider } from '@bastionauth/react';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<BastionProvider apiUrl={process.env.REACT_APP_BASTION_API_URL!}>
<App />
</BastionProvider>
</React.StrictMode>
);Remix
# Create new Remix project
npx create-remix@latest my-app
cd my-app
# Install BastionAuth
npm install @bastionauth/react// app/root.tsx
import { BastionProvider } from '@bastionauth/react';
import { Outlet } from '@remix-run/react';
export default function App() {
return (
<BastionProvider apiUrl={process.env.BASTION_API_URL}>
<Outlet />
</BastionProvider>
);
}❓ FAQs
Q: Do I need @bastionauth/core separately?
A: No! @bastionauth/react automatically includes @bastionauth/core as a dependency. Just install @bastionauth/react.
Q: Can I use this with Next.js?
A: For Next.js apps, use @bastionauth/nextjs instead, which includes everything from @bastionauth/react plus Next.js-specific features like middleware and server components.
Q: How do I customize the UI?
A: See the Styling & Customization section. You can customize colors, typography, spacing, and individual elements using the appearance prop.
Q: Does this work with React Native?
A: Not currently. This package is designed for React web applications. React Native support is planned for a future release.
Q: How do I handle API calls with authentication?
A:
const { getToken } = useAuth();
const token = await getToken();
fetch('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${token}`,
},
});Q: Can I build my own UI instead of using pre-built components?
A: Absolutely! Use the hooks (useSignIn, useSignUp, etc.) to build completely custom UI while still leveraging BastionAuth's authentication logic.
Q: Is this package production-ready?
A: Yes! BastionAuth is used in production by multiple companies and has been battle-tested with thousands of users.
Q: What's the bundle size?
A: Approximately 45KB (minified), 15KB (gzipped). Tree-shaking supported for optimal bundle sizes.
Q: How do I handle loading states?
A: All hooks return an isLoaded boolean. Always check this before rendering auth-dependent content:
const { user, isLoaded } = useUser();
if (!isLoaded) return <LoadingSpinner />;Q: Can I use multiple BastionProviders?
A: No, you should only have one BastionProvider at the root of your app. All child components will have access to the auth context.
🔄 Migration Guide
From Auth0 React SDK
// Before (Auth0)
import { Auth0Provider, useAuth0 } from '@auth0/auth0-react';
<Auth0Provider
domain="your-domain.auth0.com"
clientId="your-client-id"
>
<App />
</Auth0Provider>
function Component() {
const { user, isAuthenticated, loginWithRedirect, logout } = useAuth0();
}
// After (BastionAuth)
import { BastionProvider, useAuth, useUser } from '@bastionauth/react';
<BastionProvider apiUrl="https://api.bastionauth.dev">
<App />
</BastionProvider>
function Component() {
const { isSignedIn, signOut } = useAuth();
const { user } = useUser();
}From Clerk React
// Before (Clerk)
import { ClerkProvider, useUser, useAuth } from '@clerk/clerk-react';
<ClerkProvider publishableKey="pk_...">
<App />
</ClerkProvider>
// After (BastionAuth)
import { BastionProvider, useUser, useAuth } from '@bastionauth/react';
<BastionProvider apiUrl="https://api.bastionauth.dev">
<App />
</BastionProvider>
// API is nearly identical!📖 API Reference
Components
<BastionProvider><SignIn /><SignUp /><UserButton /><UserProfile /><OrganizationSwitcher /><OrganizationProfile /><CreateOrganization /><RedirectToSignIn />
Hooks
Types
import type {
User,
Session,
Organization,
OrganizationMembership,
Appearance,
SignInProps,
SignUpProps,
} from '@bastionauth/react';📦 Related Packages
BastionAuth is split into three packages for maximum flexibility:
| Package | Description | When to Use | |---------|-------------|-------------| | @bastionauth/core | Types, constants, utilities | Custom integrations, backends | | @bastionauth/react | React components & hooks | React apps (Vite, CRA, Remix) | | @bastionauth/nextjs | Next.js integration | Next.js apps |
📚 Documentation
- Full Documentation: https://docs.bastionauth.dev
- Live Demo: https://bastionauth.dev
- Pricing: https://bastionauth.dev/pricing
- API Reference: https://docs.bastionauth.dev/api/react
- Component Storybook: https://storybook.bastionauth.dev
💰 Pricing
BastionAuth offers self-hosted authentication with transparent pricing:
- Free Tier: Up to 1,000 monthly active users
- Pro: $29/month for up to 10,000 users
- Enterprise: Custom pricing for unlimited users + premium support
Why Self-Hosted?
- 📊 No per-user costs at scale - Pay for hosting, not for users
- 🔐 Complete data ownership - Your data stays on your infrastructure
- 🌍 GDPR, HIPAA, SOC2 compliance ready - Meet any regulatory requirement
- 🚀 No vendor lock-in - You control your auth infrastructure
💬 Support
Need help? We're here for you:
- Email: [email protected]
- Website: bastionauth.dev
- Documentation: docs.bastionauth.dev
- Discord: Join our community (coming soon)
- Twitter: @BastionAuth (coming soon)
