Composables
useUserSession
The primary composable for accessing authentication state. Returns reactive user, session, and auth client.
const { loggedIn, user, session, client, signIn, signOut } = useUserSession()
true if the user is currently authenticated.true when initial session resolution is complete (from SSR hydration or client fetch).null during SSR/server runtime.useUserSession().client is browser-only. During SSR/server runtime it is null.For SSR-safe auth access:- Use
user,session,loggedIn,ready, andfetchSessionfromuseUserSession(). - Use
useAuthRequestFetch()oruseAuthAsyncData()for auth-bound data in pages. - Use server helpers like
serverAuth(event),getUserSession(event), orrequireUserSession(event)inserver/handlers.
Methods
signIn
Proxies Better Auth signIn.
await signIn.email({
email: 'user@example.com',
password: 'password'
})
Promise Behavior
Methods like signIn return a promise that resolves when the server responds, not when local state updates.
// This awaits the server response
await client.signIn.email({ email, password })
// Local state updates asynchronously after
// Use onSuccess callback for actions that depend on updated state
await client.signIn.email(
{ email, password },
{ onSuccess: () => navigateTo('/dashboard') }
)
If no onSuccess callback is provided in method options, signIn will:
- navigate to
route.query.redirect(or customauth.redirectQueryKey) when it is a local path - otherwise fallback to
auth.redirects.authenticatedwhen configured and an authenticated session is established - otherwise no automatic navigation
await signIn.email({ email, password }) // redirects to safe `?redirect=...` or auth.redirects.authenticated
signUp
Proxies Better Auth signUp with the same onSuccess behavior as signIn.
await signUp.email({
email: 'user@example.com',
password: 'password'
})
Like signIn, if no callback is provided, signUp follows the same redirect precedence:
query redirect > auth.redirects.authenticated (only when authenticated) > no auto-redirect.
signOut
Signs the user out and clears the local session state.
await signOut()
If auth.redirects.logout is configured, signOut() will navigate there automatically (client-side), unless you provide onSuccess.
Options
await signOut({
onSuccess: () => navigateTo('/'),
})
waitForSession()
Waits for session state to be ready. Resolves when user is logged in or after 5 second timeout.
await waitForSession()
// Session is now ready (or timed out)
fetchSession
Manually triggers a session refresh. Useful if you've updated user data on the server via a side channel.
await fetchSession()
Options
await fetchSession({
headers, // optional HeadersInit
force: true, // disables Better Auth cookie cache for this fetch
})
updateUser
Updates the user on the server and optimistically patches local state. Local state reverts if the server call fails.
await updateUser({ name: 'New Name' })
updateUser only patches local state since no client is available.user and session are global states using useState. Changes in one component are instantly reflected everywhere.useAuthRequestFetch
Returns Nuxt's request-scoped fetch function. On SSR it preserves request context (including cookies); on client it behaves like regular fetch.
useAuthRequestFetch() defaults to GET. For endpoints that only allow POST (or another method), pass method explicitly to preserve response type inference.
When endpoint typing is enabled, useFetch('/api/auth/...') and useLazyFetch('/api/auth/...') infer payloads directly from your Better Auth config:
const { data: customerState } = await useFetch('/api/auth/customer/state')
customerState.value?.activeSubscriptions[0]?.toUpperCase()
const { data: customerById } = await useLazyFetch('/api/auth/customer/123/state')
customerById.value?.customerId.toUpperCase()
Global $fetch keeps Nitro InternalApi response inference, but path autocomplete is best on useFetch/useLazyFetch and useAuthRequestFetch.
const requestFetch = useAuthRequestFetch()
const state = await requestFetch('/api/auth/customer/state')
const postOnly = await requestFetch('/api/auth/customer/post-only', { method: 'POST' })
Use this when you need low-level control and want to build your own data loader pattern.
useAuthAsyncData
SSR-safe helper for auth-bound data loading.
const { data: customerState, pending, error } = await useAuthAsyncData(
'customer-state',
requestFetch => requestFetch('/api/auth/customer/state'),
)
When route typing is enabled, payload types for /api/auth/* endpoints are inferred automatically.
By default:
requireAuthistrue(unauthenticated users resolve tonullwithout calling the endpoint).datadefaults tonull.- errors are exposed through
error.
const { data } = await useAuthAsyncData(
'public-profile',
requestFetch => requestFetch('/api/profile/public'),
{ requireAuth: false },
)
useAction
Creates a reusable async action handle with normalized error state.
const saveProfile = useAction(async (payload: { name: string }) => {
return await $fetch('/api/profile', { method: 'PATCH', body: payload })
})
await saveProfile.execute({ name: 'Max' })
if (saveProfile.status.value === 'error') {
console.error(saveProfile.error.value?.message)
}
useAction returns the same handle shape as useSignIn/useSignUp:
execute() call.{ error } responses.useAuthClientAction
Wraps plugin/client methods from useUserSession().client in the same action handle pattern.
const checkout = useAuthClientAction((client) => client.checkout)
await checkout.execute({ slug: 'pro' })
if (checkout.status.value === 'error') {
console.error(checkout.error.value?.message)
}
Nested methods are supported via selector functions:
const openPortal = useAuthClientAction((client) => client.customer.portal)
await openPortal.execute()
useSignIn
Returns a keyed action handle for useUserSession().signIn.*. Each method handle exposes template-friendly async state, similar to composables like useFetch.
const { execute, data, status, error } = useSignIn('email')
await execute(
{ email, password, rememberMe },
{
onSuccess: () => navigateTo('/app'),
onError: (ctx) => console.error(ctx.error),
},
)
if (status.value === 'error') {
console.error(error.value?.message)
}
For OAuth sign-in, use the social key and pass the provider in the payload:
await useSignIn('social').execute({ provider: 'github' })
Provider keys are inferred from server/auth.config.ts socialProviders keys.
When callbackURL is omitted, the module auto-fills it using the same safe redirect order as other auth flows:
- safe query redirect (
auth.redirectQueryKey) auth.redirects.authenticated- otherwise no fallback callback URL
Use renaming to avoid collisions when you use multiple methods in the same scope:
const {
execute: loginWithEmail,
status: statusEmail,
error: errorEmail,
} = useSignIn('email')
const {
execute: loginWithPasskey,
status: statusPasskey,
error: errorPasskey,
} = useSignIn('passkey')
Each method returns an action handle:
execute() call.execute() and on errors.execute()).execute() multiple times, only the latest call updates status and error.Error state and promise behavior
Use status, data, and error as your source of truth. The action handle always sets status='error' and populates normalized error when a sign-in attempt fails.
{ error } result. In both cases, the action handle updates status and error, and await execute() always resolves.Recommended flow (execute)
const { execute, data, status, error } = useSignIn('email')
await execute({ email, password })
if (status.value === 'error') {
console.error(error.value?.message)
}
if (status.value === 'success') {
console.log(data.value)
}
useUserSignIn and useUserSignUp were renamed to useSignIn and useSignUp in alpha.
The API switched from map-style access (useUserSignIn().email) to keyed access (useSignIn('email')) in alpha.
OAuth provider aliases (for example useSignIn('github')) were removed. Use useSignIn('social').execute({ provider: 'github' }).
error changed from unknown | null to AuthActionError | null in alpha.
The message alias field was removed in alpha. Use error.value?.message.
execute() changed twice in alpha:- old:
await execute()could reject - previous alpha:
await execute()resolved{ ok: true, data } | { ok: false, error } - new:
await execute()resolvesvoid, and you readstatus/data/error
error.raw.useSignUp
Same API as useSignIn, but wraps useUserSession().signUp.*.
const { execute, data, status, error } = useSignUp('email')
await execute(
{ email, password, name },
{
onSuccess: () => navigateTo('/welcome'),
onError: (ctx) => console.error(ctx.error),
},
)
useSignUp returns the same action handle shape (execute, status, data, error) and follows the same error normalization semantics as useSignIn.