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.comapplications - 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 firebaseQuick 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:abc123Server Requirements
This client requires the following Cloud Functions to be deployed:
POST /exchange- Exchange passport for Firebase custom tokenPOST /logout- Invalidate passport and session
These are typically deployed as part of the Bitxu Auth backend.
License
MIT