Skip to content
Torii docs

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

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; currentGate is 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 gateNow handled by
email_verificationA 6-digit code entered on the sign-up form, before the session is created. See the sign-up form.
legal_acceptanceThe 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.

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.

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:

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>

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

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
}
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>
</>
);
}