Session gates
A user can in principle be signed in but not yet cleared to proceed. Torii
models this as a pending session carrying one or more gates. The server is
authoritative: while any gate is outstanding the session stays pending and
your protected UI must not render.
Nothing currently produces a gate. The two situations that used to gate a session, email verification and legal consent, are now both handled inside the sign-up ceremony (email verification is a 6-digit code entered on the sign-up form; legal consent is the checkbox on that same form). So an authenticated session is always
activetoday. The gate machinery below (gateUrls,<RedirectToGates>,session.status === 'pending') is retained as a forward-compatible hook for future custom gates: it’s wired and safe to mount, it just has nothing to act on right now.
Pending sessions
Section titled “Pending sessions”The session state lives on the /me payload the provider fetches at boot. You
rarely read it directly (the control components below consume it for you) but
its shape is:
interface SessionInfo { status: 'active' | 'pending'; gates: Gate[]; currentGate: Gate | null; // gates[0], or null when active}
interface Gate { key: GateKey;}
type GateKey = 'legal_acceptance' | 'email_verification';status === 'active': protected UI may render. This is always the case today for an authenticated session.status === 'pending': the user has an outstanding gate;currentGateis the first one (server-defined order). No built-in flow produces this state at the moment.
The two gate keys that historically existed, email_verification and
legal_acceptance, are no longer produced:
| Former gate | Now handled by |
|---|---|
email_verification | A 6-digit code entered on the sign-up form, before the session is created. See the sign-up form. |
legal_acceptance | The consent checkbox on the sign-up form (and on the signup-continue consent step <SignUp> auto-mounts for OAuth sign-ups), driven by the environment’s Terms / Privacy settings. |
<SignedIn> already gates pending sessions
Section titled “<SignedIn> already gates pending sessions”You don’t have to check session.status yourself.
<SignedIn> renders its children only
when the user is signed in and the session is active. A pending session
renders as null:
<SignedIn> <Dashboard /> {/* hidden while the session is pending on any gate */}</SignedIn>Conversely, <SignedOut> treats a pending session as “not signed in for
rendering purposes”, so your signed-out shell (which hosts the sign-in card)
can also host the gate-completion UI.
Routing a pending user
Section titled “Routing a pending user”Gating to null is safe but invisible: a pending user would see a blank
screen. Route them to the screen that clears the gate using two pieces:
1. Map each gate key to a URL
Section titled “1. Map each gate key to a URL”Pass gateUrls to <ToriiProvider>, a Partial<Record<GateKey, string>>
mapping any of the fixed, Torii-defined gate keys ('legal_acceptance',
'email_verification') to the route that clears it:
<ToriiProvider publishableKey="pk_live_…" gateUrls={{ legal_acceptance: '/legal', }}> <App /></ToriiProvider>2. Mount <RedirectToGates>
Section titled “2. Mount <RedirectToGates>”<RedirectToGates> reads session.currentGate.key, looks up
gateUrls[key], and navigates there. It honours ToriiProvider.navigate when
set, otherwise does a full-page redirect. It renders nothing.
import { SignedOut, RedirectToGates, SignIn } from '@torii-js/torii-react';
<SignedOut> <RedirectToGates /> <SignIn /></SignedOut>If the current gate has no gateUrls entry, <RedirectToGates> is a no-op but
logs a one-shot warning, so a pending session never silently strands the user.
Don’t mount
<RedirectToGates>alongside<RedirectToSignIn>. Both fire navigation effects on the same render; if both qualify at once the destination is undefined. Render exactly one, gated on your own condition.
Clearing a gate
Section titled “Clearing a gate”Gates clear server-side. After the user completes whatever action a custom gate
requires, call refreshSession() (or rely on the provider’s automatic refetch
on tab refocus) to pull the now-active session:
const { refreshSession } = useAuth();
async function onGateCleared() { await completeGateAction(); await refreshSession(); // session flips to 'active', <SignedIn> renders}End-to-end shape
Section titled “End-to-end shape”function App() { return ( <> <AuthLoading> <Spinner /> </AuthLoading>
{/* Pending sessions count as signed-out for rendering, so the gate redirect + sign-in card both live here. */} <SignedOut> <RedirectToGates /> <SignIn /> </SignedOut>
<SignedIn> <Dashboard /> </SignedIn> </> );}See also
Section titled “See also”<ToriiProvider>:gateUrls,navigate- Control components:
<SignedIn>/<SignedOut>