Skip to content

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.


Core exports:

  • AuthProvider
  • useAuth()
  • AuthRoutes
  • createSeamlessAuthClient()
  • useAuthClient()
  • usePasskeySupport()
  • hasScopedRole() and roleGrantsAccess()
  • WebAuthn PRF helpers such as encodePrfSalt() and isPasskeyPrfSupported()

Important exported types include AuthContextType, User, Credential, Organization, OAuthProvider, StepUpStatus, and the headless client input/result types.


StyleUse 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
AuthRoutesYou want the built-in login, OTP, magic-link, and passkey screens

Most apps still wrap the route tree in AuthProvider.


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:

PropPurpose
apiHostBackend API base URL
autoDetectPreviousSigninOptional localStorage-backed returning-user hint

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.


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.


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.


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 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.


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.


hasRole(role) is an exact role check. Use hasScopedRole(required) for scoped authorization.

const { hasRole, hasScopedRole } = useAuth()
hasRole('admin') // exact legacy check
hasScopedRole('admin:read') // true for admin, admin:read, or admin:write
hasScopedRole('admin:write') // true for admin or admin:write

The package also exports standalone hasScopedRole(roles, required) and roleGrantsAccess(...) helpers for code outside React context.


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 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.


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 API

The backend layer owns cookie validation, role enforcement, and service-to-service auth.