Skip to main content
Docs
SDK reference

Client auth reference

Hooks, providers, and components for authentication in React.

The auth surface is exported from deepspace. The Better Auth client is wrapped in a React provider, with hooks and components for everything you'd build by hand.

import {
  DeepSpaceAuthProvider,
  AuthGate, AuthOverlay,
  SignedIn, SignedOut, GuestBanner,
  useAuth, useAuthUser, useUser, useDisplayName,
  signIn, signOut, getAuthToken, clearAuthToken,
  authClient, useSession,
} from 'deepspace'

Providers#

DeepSpaceAuthProvider#

Wraps the tree and initializes the Better Auth client. Required as an ancestor of every auth hook and component.

<DeepSpaceAuthProvider>
  <App />
</DeepSpaceAuthProvider>

The scaffolded _app.tsx already mounts this provider at the root. You don't normally render it yourself.

Hooks#

useAuth(): AuthState#

Primary auth-state hook. Session-based; updates immediately on sign-in / sign-out.

Field Type Description
isLoaded boolean true once the first Better Auth session check resolves, and stays true for the page's lifetime - does not flap back to false on background refetches or tab refocus. Safe to gate RecordProvider / data-layer mounts on without remount churn.
isSignedIn boolean The canonical signed-in check.
userId string \| null JWT subject; null when signed out.
sessionId string \| null Better Auth session ID.
const { isLoaded, isSignedIn, userId } = useAuth()
if (!isLoaded) return <Skeleton />
if (!isSignedIn) return <SignInPrompt />

useAuthUser(): { isLoaded, isSignedIn, user }#

Returns the auth-layer user from Better Auth's session - { id, fullName, firstName, primaryEmailAddress, ... } | null. Use when you need fields from the OAuth provider's profile.

Different from useUser(), which merges in storage-layer fields like role and karma.

useUser(): { user, isLoading, refetch }#

Returns the storage-layer user, merged with the room-specific role from the app's users collection. Karma and credits are loaded from the API as nested objects when available.

type UserKarma = {
  total: number
  breakdown: { publishing: number; content: number; comment: number; curation: number }
  rank: number
  monthlyKarma: number
  monthlyRank: number
}

type UserCredits = {
  total: number
  subscription: number
  bonus: number
  purchased: number
}

type StorageUser = {
  id: string
  name: string
  email: string
  /** Free-form role string from the user-roles collection; defaults to `'viewer'`. */
  role: string
  imageUrl?: string
  isAdmin?: boolean
  publicUsername?: string | null
  subscriptionTier?: string | null
  subscriptionStatus?: string | null
  karma?: UserKarma | null
  credits?: UserCredits | null
}
const { user, isLoading } = useUser()
if (isLoading) return null
if (user?.role !== 'admin') return <p>Admins only</p>

// Karma and credits are objects, not numbers:
const totalKarma = user?.karma?.total ?? 0
const availableCredits = user?.credits?.total ?? 0

useDisplayName(): string | null#

Resolves the best available display name (full name → first name → email username). Returns null while loading or signed out.

useSession() and authClient#

Re-exports from Better Auth for advanced flows (custom OAuth providers, magic links, etc.).

Components#

<AuthOverlay providers={...} />#

Modal sign-in UI. Render without an onClose prop and gate on !isSignedIn; auto-hides when signed in.

Prop Type Description
providers Array<'github' \| 'google'> Which OAuth buttons to show. Defaults to ['github', 'google']. Email/password sign-in is always available below the OAuth options.
onClose () => void If provided, renders a close button. Omit for non-dismissible.
<AuthOverlay providers={['google', 'github']} />

<AuthGate fallback={...} redirectOnSignOut={...} />#

Renders children when signed in; renders fallback (default: <AuthOverlay />) otherwise.

Prop Type Description
fallback ReactNode UI shown to signed-out users. Defaults to non-dismissible <AuthOverlay />.
redirectOnSignOut string Where the user lands on sign-out. Default '/'. Triggers a full reload.
<AuthGate fallback={<TeaserPage />}>
  <ProtectedContent />
</AuthGate>

<SignedIn> and <SignedOut>#

Conditional rendering helpers:

<SignedIn><UserMenu /></SignedIn>
<SignedOut><SignInButton /></SignedOut>

<GuestBanner />#

A small inline banner prompting sign-in for anonymous visitors.

Functions#

signIn / signOut#

Re-exports from Better Auth.

import { signOut } from 'deepspace'
await signOut()

getAuthToken(): Promise<string | null>#

Returns the current JWT, refreshing it from the auth worker if necessary. Attach to outbound fetch calls:

const r = await fetch('/api/premium', {
  headers: { Authorization: `Bearer ${await getAuthToken()}` },
})

clearAuthToken(): void#

Clears the cached JWT. Forces the next getAuthToken() call to fetch a fresh one. Useful in tests or after explicit session changes.

Patterns#

Auth-state checks#

const { isLoaded, isSignedIn } = useAuth()
if (!isLoaded) return <Skeleton />
return isSignedIn ? <App /> : <Landing />

Profile access#

const { user, isLoading } = useUser()
if (isLoading) return null
return user ? <Hi name={user.name} /> : <SignInPrompt />

Sign-out button#

import { signOut } from 'deepspace'

<button onClick={() => signOut()}>Sign out</button>

See also#