React SDK
React SDK
Section titled “React SDK”@seamless-auth/react is the official React integration for Seamless Auth.
It gives your app an auth provider, optional built-in auth screens, and a headless client for building custom sign-in and account-management UI.
What It Exports
Section titled “What It Exports”Core exports:
AuthProvideruseAuth()AuthRoutescreateSeamlessAuthClient()useAuthClient()usePasskeySupport()hasScopedRole()androleGrantsAccess()- WebAuthn PRF helpers such as
encodePrfSalt()andisPasskeyPrfSupported()
Important exported types include AuthContextType, User, Credential, Organization,
OAuthProvider, StepUpStatus, and the headless client input/result types.
Choose Your Integration Style
Section titled “Choose Your Integration Style”| Style | Use it when |
|---|---|
AuthProvider + useAuth() | You want app-wide auth state and high-level actions |
createSeamlessAuthClient() | You want fully custom auth screens outside React context |
useAuthClient() | You want custom React screens while reusing the provider apiHost |
AuthRoutes | You want the built-in login, OTP, magic-link, and passkey screens |
Most apps still wrap the route tree in AuthProvider.
Basic Setup
Section titled “Basic Setup”import { BrowserRouter } from 'react-router-dom'import { AuthProvider } from '@seamless-auth/react'
export function Root() { return ( <BrowserRouter> <AuthProvider apiHost="http://localhost:3000"> <App /> </AuthProvider> </BrowserRouter> )}apiHost points at your backend API, not the raw auth server. The SDK sends requests to
${apiHost}/auth/..., so the backend usually mounts @seamless-auth/express at /auth.
AuthProvider currently accepts:
| Prop | Purpose |
|---|---|
apiHost | Backend API base URL |
autoDetectPreviousSignin | Optional localStorage-backed returning-user hint |
Reading Auth State
Section titled “Reading Auth State”import { useAuth } from '@seamless-auth/react'
function Dashboard() { const { user, isAuthenticated, hasScopedRole, logout } = useAuth()
if (!isAuthenticated) return <div>Not signed in</div>
return ( <div> <p>{user?.email}</p> {hasScopedRole('admin:read') && <a href="/admin">Admin</a>} <button onClick={() => void logout()}>Log out</button> </div> )}The provider state includes user, credentials, organizations, activeOrganization,
stepUpStatus, isAuthenticated, and loading.
Provider helpers include session refresh, logout, credential management, organization switching, OAuth start/callback helpers, passkey login, and step-up verification.
Built-In Auth Routes
Section titled “Built-In Auth Routes”AuthRoutes currently provides screens for:
/login/passKeyLogin/verifyPhoneOTP/verifyEmailOTP/verify-magiclink/oauth/callback/registerPasskey/magiclinks-sent
The sign-in view registers with just an email (a phone is optional): submit an email, verify the
emailed code, and you are signed in. It also renders a button for each configured OAuth provider and
completes the flow at the built-in /oauth/callback route, so OAuth needs no extra UI wiring when
you use AuthRoutes.
The built-in routes are optional wrappers over the same primitives exposed by useAuth() and the
headless client. Your app still owns route protection and redirects.
Headless Client
Section titled “Headless Client”Use createSeamlessAuthClient() when you want your own UI flow.
import { createSeamlessAuthClient } from '@seamless-auth/react'
const authClient = createSeamlessAuthClient({ apiHost: 'http://localhost:3000',})
const loginStart = await authClient.login({ identifier: 'user@example.com', passkeyAvailable: true,})The headless client covers:
- current-user/session lookup
- login and passkey login
- registration
- phone and email OTP
- magic-link request, polling, and verification
- OAuth provider listing, start, and callback completion
- passkey registration
- step-up status and verification
- logout and user deletion
- credential update and deletion
- organization and organization-member operations
Most methods return Response. Convenience methods such as passkey login, OAuth provider listing,
and step-up verification return typed result objects.
Custom Auth UI
Section titled “Custom Auth UI”For custom React screens, combine useAuth(), useAuthClient(), and usePasskeySupport().
import { useAuth, useAuthClient, usePasskeySupport } from '@seamless-auth/react'
function CustomLogin() { const { refreshSession } = useAuth() const authClient = useAuthClient() const { passkeySupported, loading } = usePasskeySupport()
async function startLogin() { const response = await authClient.login({ identifier: 'user@example.com', passkeyAvailable: passkeySupported, })
if (response.ok) { await refreshSession() } }
return ( <button disabled={loading} onClick={() => void startLogin()}> Sign in </button> )}Use refreshSession() after completing any custom flow that should update provider state.
OAuth Login
Section titled “OAuth Login”OAuth starts in the frontend, but provider secrets and callback policy belong on the auth API.
import { useEffect, useState } from 'react'import { useAuth, type OAuthProvider } from '@seamless-auth/react'
function OAuthButtons() { const { listOAuthProviders, startOAuthLogin } = useAuth() const [providers, setProviders] = useState<OAuthProvider[]>([])
useEffect(() => { void listOAuthProviders().then((result) => setProviders(result.providers)) }, [listOAuthProviders])
async function signIn(providerId: string) { const result = await startOAuthLogin({ providerId, redirectUri: `${window.location.origin}/oauth/callback`, returnTo: `${window.location.origin}/dashboard`, })
window.location.assign(result.authorizationUrl) }
return providers.map((provider) => ( <button key={provider.id} onClick={() => void signIn(provider.id)}> Continue with {provider.name} </button> ))}Complete the callback by passing the provider code and Seamless Auth state back to the SDK:
const { finishOAuthLogin } = useAuth()
await finishOAuthLogin({ providerId: 'google', code: new URLSearchParams(window.location.search).get('code')!, state: new URLSearchParams(window.location.search).get('state')!,})OAuth requires LOGIN_METHODS to include oauth and at least one configured OAuth provider on the
auth API.
Organizations
Section titled “Organizations”useAuth() exposes organizations, activeOrganization, and switchOrganization().
For full custom organization management, use the headless client methods:
listOrganizations()createOrganization(input)getOrganization(organizationId)updateOrganization(organizationId, input)switchOrganization(organizationId)listOrganizationMembers(organizationId)addOrganizationMember(organizationId, input)updateOrganizationMember(organizationId, userId, input)removeOrganizationMember(organizationId, userId)
Switching organizations refreshes provider state so the active organization in useAuth() stays in
sync with the session.
Scoped Roles
Section titled “Scoped Roles”hasRole(role) is an exact role check. Use hasScopedRole(required) for scoped authorization.
const { hasRole, hasScopedRole } = useAuth()
hasRole('admin') // exact legacy checkhasScopedRole('admin:read') // true for admin, admin:read, or admin:writehasScopedRole('admin:write') // true for admin or admin:writeThe package also exports standalone hasScopedRole(roles, required) and roleGrantsAccess(...)
helpers for code outside React context.
Step-up Before Sensitive Actions
Section titled “Step-up Before Sensitive Actions”Use step-up auth when a signed-in user needs fresh verification before a sensitive action.
import { useAuth } from '@seamless-auth/react'
function SensitiveActionButton() { const { refreshStepUpStatus, verifyStepUpWithPasskey } = useAuth()
async function runSensitiveAction() { const status = await refreshStepUpStatus() const verified = status?.fresh ? true : (await verifyStepUpWithPasskey()).success
if (!verified) return
await performSensitiveAction() }
return <button onClick={() => void runSensitiveAction()}>Continue</button>}The current React SDK supports WebAuthn/passkey step-up helpers. The API also supports TOTP step-up through the TOTP route surface.
WebAuthn PRF
Section titled “WebAuthn PRF”WebAuthn PRF lets compatible passkeys derive browser-local key material during a passkey assertion. Seamless Auth verifies the assertion, while PRF output stays in the browser caller.
const supported = await authClient.isPasskeyPrfSupported()
if (supported) { await authClient.registerPasskey({ metadata: { friendlyName: 'My laptop', platform: 'macOS', browser: 'Chrome', deviceInfo: navigator.userAgent, }, requestPrf: true, })}For local key-unlock flows, request PRF during step-up:
const result = await authClient.verifyStepUpWithPasskeyPrf({ salt: vaultSaltBase64url, credentialId,})
if (result.success && result.prf) { const keyMaterial = result.prf.output}Do not log PRF salts or output. Do not send PRF output to Seamless Auth.
Important Boundary
Section titled “Important Boundary”The React SDK is not a backend and does not replace authorization enforcement.
The intended shape is:
React app -> your backend API with /auth adapter routes -> Seamless Auth APIThe backend layer owns cookie validation, role enforcement, and service-to-service auth.