JSPM

bitxu-auth-client

1.0.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 3
  • Score
    100M100P100Q61384F
  • License MIT

Client library for Bitxu SSO authentication with passport-based single sign-on across all .bitxu.com subdomains

Package Exports

  • bitxu-auth-client

Readme

bitxu-auth-client

Client library for Bitxu SSO authentication. Provides seamless single sign-on across all .bitxu.com subdomains using a passport-based authentication system.

Features

  • Single Sign-On: One login across all .bitxu.com applications
  • Passport System: Secure JWT-based passport stored as HttpOnly cookie
  • Firebase Integration: Automatic Firebase custom token exchange
  • Permission-Based Access Control: Fine-grained permissions and roles
  • React Components: Ready-to-use provider, hooks, and components
  • TypeScript: Full type safety out of the box
  • Zero Config: Works with sensible defaults

Installation

npm install bitxu-auth-client firebase

Quick Start

1. Wrap your app with the provider

import { BitxuAuthProvider } from 'bitxu-auth-client'

const firebaseConfig = {
  apiKey: 'YOUR_API_KEY',
  authDomain: 'YOUR_PROJECT.firebaseapp.com',
  projectId: 'YOUR_PROJECT_ID',
}

function App({ children }) {
  return (
    <BitxuAuthProvider
      firebaseConfig={firebaseConfig}
      authDomain="https://auth.bitxu.com"
    >
      {children}
    </BitxuAuthProvider>
  )
}

2. Use the auth hook

import { useBitxuAuth } from 'bitxu-auth-client'

function Profile() {
  const { user, loading, logout, hasPermission } = useBitxuAuth()

  if (loading) return <div>Loading...</div>
  
  return (
    <div>
      <h1>Welcome, {user?.displayName}</h1>
      <button onClick={logout}>Logout</button>
    </div>
  )
}

API Reference

Components

<BitxuAuthProvider>

The main provider component that manages authentication state.

interface BitxuAuthProviderProps {
  firebaseConfig: {
    apiKey: string
    authDomain: string
    projectId: string
    storageBucket?: string
    messagingSenderId?: string
    appId?: string
  }
  authDomain?: string        // Default: https://auth.bitxu.com
  apiBaseUrl?: string        // Default: Firebase Cloud Functions URL
  defaultRedirect?: string   // Default: window.location.origin
  children: React.ReactNode
}

<ProtectedRoute>

Protects content based on permissions and roles.

import { ProtectedRoute } from 'bitxu-auth-client'

<ProtectedRoute
  permissions={['users:read', 'users:write']}
  roles={['admin']}
  fallback={<Unauthorized />}
  loadingFallback={<Loading />}
>
  <AdminPanel />
</ProtectedRoute>

<HasPermission>

Conditionally renders children based on permissions.

import { HasPermission } from 'bitxu-auth-client'

<HasPermission 
  permission="users:delete"
  fallback={<span>Not allowed</span>}
>
  <DeleteButton />
</HasPermission>

// Check multiple permissions
<HasPermission 
  permission={['users:read', 'users:write']}
  requireAll={true}
>
  <UserEditor />
</HasPermission>

<LogoutButton>

A simple logout button component.

import { LogoutButton } from 'bitxu-auth-client'

<LogoutButton 
  onLogout={() => console.log('Logged out')}
  className="btn btn-danger"
>
  Sign out everywhere
</LogoutButton>

Hooks

useBitxuAuth()

The main hook for accessing authentication state and methods.

const {
  user,           // BitxuUser | null
  claims,         // CustomClaims | null
  loading,        // boolean
  error,          // Error | null
  isAuthenticated,// boolean
  login,          // (email, password) => Promise<void>
  loginWithGoogle,// () => Promise<void>
  logout,         // () => Promise<void>
  hasPermission,  // (permission: string) => boolean
  hasRole,        // (role: string) => boolean
  refreshToken,   // () => Promise<void>
} = useBitxuAuth()

usePermissions()

A specialized hook for permission checking.

const {
  permissions,      // string[] - Current user's permissions
  role,             // string | null - Current user's role
  hasPermission,    // (permission: string) => boolean
  hasAnyPermission, // (permissions: string[]) => boolean
  hasAllPermissions,// (permissions: string[]) => boolean
} = usePermissions()

Types

interface BitxuUser {
  uid: string
  email: string | null
  displayName: string | null
  photoURL: string | null
}

interface CustomClaims {
  subdomain: string
  role: string
  permissions: string[]
  membership_id: string
}

How It Works

Authentication Flow

┌─────────────────────────────────────────────────────────────────┐
│  1. User visits app.bitxu.com                                   │
│     ↓                                                           │
│  2. BitxuAuthProvider checks for passport cookie               │
│     ↓                                                           │
│  3. If no passport → redirect to auth.bitxu.com                │
│     ↓                                                           │
│  4. User authenticates on auth.bitxu.com                       │
│     ↓                                                           │
│  5. auth.bitxu.com sets passport cookie (.bitxu.com)           │
│     ↓                                                           │
│  6. Redirect back to app.bitxu.com                             │
│     ↓                                                           │
│  7. Exchange passport for Firebase custom token                │
│     ↓                                                           │
│  8. Sign in with Firebase custom token                         │
│     ↓                                                           │
│  9. User authenticated with permissions in custom claims       │
└─────────────────────────────────────────────────────────────────┘

Passport System

The passport is a JWT stored as an HttpOnly cookie with domain .bitxu.com:

  • Secure: HttpOnly, Secure, SameSite=Lax
  • Long-lived: 30 days expiration
  • Global: Works across all subdomains
  • Revocable: Global logout invalidates all sessions

Examples

Complete App Setup

import { 
  BitxuAuthProvider, 
  useBitxuAuth, 
  ProtectedRoute,
  HasPermission 
} from 'bitxu-auth-client'

const firebaseConfig = {
  apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
  authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
  projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
}

function App() {
  return (
    <BitxuAuthProvider firebaseConfig={firebaseConfig}>
      <Router>
        <Routes>
          <Route path="/public" element={<PublicPage />} />
          <Route 
            path="/dashboard" 
            element={
              <ProtectedRoute fallback={<Navigate to="/login" />}>
                <Dashboard />
              </ProtectedRoute>
            } 
          />
          <Route 
            path="/admin" 
            element={
              <ProtectedRoute 
                roles={['admin']} 
                fallback={<Unauthorized />}
              >
                <AdminPanel />
              </ProtectedRoute>
            } 
          />
        </Routes>
      </Router>
    </BitxuAuthProvider>
  )
}

function Dashboard() {
  const { user, logout } = useBitxuAuth()

  return (
    <div>
      <header>
        <span>{user?.email}</span>
        <button onClick={logout}>Logout</button>
      </header>
      
      <main>
        <HasPermission permission="users:read">
          <UserList />
        </HasPermission>
        
        <HasPermission permission="settings:write">
          <SettingsPanel />
        </HasPermission>
      </main>
    </div>
  )
}

Permission-Based UI

import { HasPermission, usePermissions } from 'bitxu-auth-client'

function UserActions({ userId }) {
  const { hasAllPermissions } = usePermissions()

  return (
    <div className="flex gap-2">
      <HasPermission permission="users:read">
        <ViewButton userId={userId} />
      </HasPermission>
      
      <HasPermission permission="users:write">
        <EditButton userId={userId} />
      </HasPermission>
      
      <HasPermission permission="users:delete">
        <DeleteButton userId={userId} />
      </HasPermission>
    </div>
  )
}

// Or use the hook directly
function ConditionalMenu() {
  const { hasPermission, hasAnyPermission, role } = usePermissions()

  return (
    <nav>
      {hasPermission('dashboard:view') && <a href="/dashboard">Dashboard</a>}
      {hasAnyPermission(['users:read', 'users:write']) && <a href="/users">Users</a>}
      {role === 'admin' && <a href="/admin">Admin</a>}
    </nav>
  )
}

Global Logout

import { LogoutButton, useBitxuAuth } from 'bitxu-auth-client'

function ProfileMenu() {
  const { user, logout } = useBitxuAuth()

  const handleLogout = async () => {
    await logout()
    // User is now logged out from ALL .bitxu.com apps
    // They will be redirected to auth.bitxu.com
  }

  return (
    <div className="dropdown">
      <span>{user?.displayName}</span>
      <div className="dropdown-menu">
        <a href="/settings">Settings</a>
        <button onClick={handleLogout}>
          Sign out everywhere
        </button>
      </div>
    </div>
  )
}

Environment Variables

VITE_FIREBASE_API_KEY=your_api_key
VITE_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=your_project_id
VITE_FIREBASE_STORAGE_BUCKET=your_project.appspot.com
VITE_FIREBASE_MESSAGING_SENDER_ID=123456789
VITE_FIREBASE_APP_ID=1:123456789:web:abc123

Server Requirements

This client requires the following Cloud Functions to be deployed:

  • POST /exchange - Exchange passport for Firebase custom token
  • POST /logout - Invalidate passport and session

These are typically deployed as part of the Bitxu Auth backend.

License

MIT