npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bastionauth/react

v0.1.7

Published

React SDK for BastionAuth - Beautiful, secure authentication components

Downloads

827

Readme

@bastionauth/react


📚 Table of Contents


🏰 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/react

Or using other package managers:

# pnpm (recommended)
pnpm add @bastionauth/react

# yarn
yarn add @bastionauth/react

# bun
bun add @bastionauth/react

Peer 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

  1. User visits protected route → Redirected to sign-in
  2. User signs in → Session created, user data loaded
  3. Hooks update automatically → UI reflects authenticated state
  4. User navigates → Session persisted across pages
  5. 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 install

Issue: 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 data

Issue: "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

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

💰 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

View full pricing →

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: