Client-side auth with AuthProvider, useAuth(), and form() integration
AuthProvider gives your app session management: sign-in, sign-up, sign-out, token refresh, MFA, and SSR hydration. Wrap your app, use useAuth() to read state, and use form(auth.signIn) for login — same patterns as the rest of the framework.
Vertz uses httpOnly cookies for JWT tokens — the client never reads the token directly. The server returns expiresAt in the response body, and the client schedules proactive refresh:
signIn/signUp → { user, expiresAt } → schedule refresh at expiresAt - 10s → POST /refresh → repeat
Token refresh is automatic:
Scheduled 10 seconds before expiry
Deduplicated (concurrent calls share one in-flight request)
Deferred when the tab is hidden (refreshes on focus if stale)
Route guard that handles loading, authentication, entitlements, and redirect — all in one component. Wraps useAuth(), the router, and can() so you don’t have to.
Entitlements the user must have (checked via can()). Type-safe when codegen is active.
forbidden
() => unknown
null
Rendered when authenticated but lacking entitlements
returnTo
boolean
true
Append ?returnTo=<currentPath> to the login redirect
Behavior:
Auth state
Result
idle / loading
Renders fallback
authenticated (entitlements met)
Renders children
authenticated (entitlements denied)
Renders forbidden (no redirect)
unauthenticated / error / mfa_required
Navigates to loginPath
ProtectedRoute does NOT redirect when entitlements fail — it renders forbidden instead. This
avoids redirect loops where a logged-in user is sent to login but still lacks permissions after
signing in.
With entitlement checks:
'/admin': { component: () => ( <ProtectedRoute requires={['admin:manage']} forbidden={() => <p>You don't have access to this page.</p>} > <AdminPanel /> </ProtectedRoute> ),}
Without a provider (fail-open): If there’s no AuthProvider in the tree, ProtectedRoute renders children and logs a dev-mode warning. This matches AuthGate’s fail-open behavior.SSR: During server rendering, ProtectedRoute renders the fallback. The redirect fires client-side after hydration.
Every auth method (signIn, signUp, mfaChallenge, forgotPassword, resetPassword) is an SdkMethod. This means:
form() works directly — validation, submission, field errors, all automatic
.url and .method are available for <form action={...} method={...}>
.meta.bodySchema provides the validation schema
// All of these work the same wayconst loginForm = form(auth.signIn);const signupForm = form(auth.signUp);const mfaForm = form(auth.mfaChallenge);const forgotForm = form(auth.forgotPassword);const resetForm = form(auth.resetPassword);
import { createSessionScript } from 'vertz/ui-server';// In your SSR handler, after validating the JWT:const sessionScript = createSessionScript({ user: { id: '1', email: 'user@example.com', role: 'admin' }, expiresAt: session.expiresAt.getTime(),});// Include in the HTML response <head>
The signUp input accepts { email, password, ...extra }, but reserved auth fields such as
role, plan, emailVerified, id, and timestamps are ignored by the auth handler.