TILLAUTH · SDK · REACT

@tilldev/auth-react

Provider + hooks for SPAs and Next.js apps. Auto-refresh, OAuth fragment capture, MFA, passkeys, optional cookie-mode for HttpOnly sessions. Works with React 18+ and React 19.

pnpm add @tilldev/auth-react

TillAuthProvider

Wrap your root once.

tsx
import { TillAuthProvider } from '@tilldev/auth-react'

<TillAuthProvider appId={process.env.NEXT_PUBLIC_TILLAUTH_APP_ID!}>
  <App />
</TillAuthProvider>

Props:

  • appId — your TillAuth public ID (tau_pub_…). Required.
  • apiBase — override the API base. Defaults to https://auth.tilldev.dev.
  • storagePrefix — storage key prefix. Defaults to tau_.
  • storage — custom storage. Defaults to localStorage in browser, in-memory otherwise.
  • sessionMode'local' (default) or 'cookie'. See below.
  • cookieProxyBase — when sessionMode='cookie', the base URL of your same-site proxy (typically /api/auth/*).

useSession · useUser · useAuthStatus

tsx
import { useSession, useUser, useAuthStatus } from '@tilldev/auth-react'

const session = useSession()         // { access_token, user } | null
const user    = useUser()            // User | null
const status  = useAuthStatus()      // 'loading' | 'unauthenticated' | 'authenticated'

User has { id, email, email_verified, display_name, mfa_enrolled }. The provider auto-refreshes the access token 60 seconds before expiry, so session.access_token is always current.

useSignIn

tsx
const { signIn, pending, error } = useSignIn()

async function submit() {
  const r = await signIn(email, password)
  if (r.ok) {
    // session is set; redirect
  } else if ('mfa_required' in r && r.mfa_required) {
    // r.challenge_token — pass to useMfa().verify(...)
  } else {
    // r.error
  }
}

useSignUp

tsx
const { signUp, pending, error } = useSignUp()

await signUp({ email, password, display_name })
// On success: session set + verification email sent.

useSignOut

tsx
const signOut = useSignOut()
await signOut()  // Revokes the session, clears storage, transitions to unauthenticated.

useMfa

For TOTP challenges after a primary signin.

tsx
const { verify, pending, error } = useMfa()

// challenge_token came back from useSignIn().signIn()
const r = await verify({ challenge_token, code })
if (r.ok) {
  // session minted
}

Passkeys

tsx
import {
  passkeysSupported,
  usePasskeys,
  useRegisterPasskey,
  useSignInWithPasskey,
} from '@tilldev/auth-react'

if (!passkeysSupported()) return null  // unsupported browser

// List the user's passkeys.
const { passkeys, pending, refresh } = usePasskeys()

// Register a new one (must be authenticated).
const { register } = useRegisterPasskey()
await register({ label: 'MacBook · TouchID' })

// Sign in with a passkey (no email needed; discoverable-credential flow).
const { signIn } = useSignInWithPasskey()
const r = await signIn()
if (r.ok) {
  // session set
}

Session modes

Two ways to store and present the access token:

sessionMode: 'local' (default)

Tokens live in localStorage. The SDK setsAuthorization: Bearer <access_token> on its own fetches. You're responsible for adding the same header to your own backend calls — read it from useSession().access_token.

XSS that runs in your app can steal the token. Mitigations: strict CSP, no third-party scripts, sanitize all user-supplied HTML.

sessionMode: 'cookie'

Tokens live in HttpOnly cookies set by a same-site proxy you run atcookieProxyBase (usually /api/auth on your own backend). The SDK does credentials: 'include' on every request; cookies ride along automatically.

Use this when XSS-driven token theft matters more than cross-origin convenience. The matching backend helper is expressCookieProxy / createFetchHandler in @tilldev/auth-node.

OAuth fragments

When the user returns from an OAuth provider, the URL looks like?tau_session=<…>. The provider captures and consumes this on mount automatically, no extra wiring. Just make sure the redirect target renders <TillAuthProvider>.

Errors

TillAuthError is what the SDK throws on programmatic misuse. For typed runtime failures from signin/signup/etc., the hook returns a discriminated union and you branch onr.ok.


Server side: @tilldev/auth-node.