meowerse_
docs menu

AuthGate

Shows its children only to a signed-in user: it waits visibly, explains a failure with a retry, and sends people to sign in only on a real signed-out answer.

import { AuthGate } from "@meowerse/ui"; · source packages/ui/src/components/AuthGate.tsx

variants and states

checking the session — its first render, on the server and in the browser

try it

account service answers

the stand-in account service answers "signed in", so the gate shows what it wraps.

signed out isn't shown here: on a real signed-out answer the gate sends people to sign in (location.replace to loginPath), which would take you away from this page.

demo source
tsx
import { useEffect, useState } from "react";
import { AuthGate, RadioGroup, Spinner, clearSessionCache } from "@meowerse/ui";

// Each state points AuthGate at a static stand-in for the account service under public/ui-demo/, so
// the gate runs for real (fetch, parse, render) at no cost and never touches a real session:
// - signed in: ui-demo/signed-in/api/session is a session JSON in exactly the shape useSession reads.
//   (It's served without a JSON content type: fetch's .json() never looks at it, and it's same-origin.)
// - service error: ui-demo/no-account/api/session isn't JSON at all, so the check fails honestly (an
//   unreadable answer, not a network error) and the gate shows its error, whose "try again" re-checks.
// - checking: a static host can't hold a request open, so this one is a still of the gate's wait state:
//   the same markup AuthGate renders while it waits (an e2e test compares the two).
// Signed out isn't offered: on a real signed-out answer AuthGate leaves the page for loginPath.
const BASES = { "signed-in": "/ui-demo/signed-in", error: "/ui-demo/no-account" } as const;
const NOTES: Record<string, string> = {
  "signed-in": "the stand-in account service answers \"signed in\", so the gate shows what it wraps.",
  checking: "a still of the wait: a real check shows this until the account service answers (at most 8 s).",
  error: "a simulated failure: the stand-in answer isn't one the gate can read. \"try again\" checks again, and fails the same way.",
};

export default function Demo() {
  const [k, setK] = useState("signed-in");
  // useSession shares one cache and one in-flight check per page: drop both on a switch, so the next
  // gate asks its own stand-in instead of reusing the last one's answer.
  const pick = (v: string) => { clearSessionCache(); setK(v); };
  // ...and on the way out: the stand-in's "signed in" must never outlive the demo as a cached session.
  useEffect(() => clearSessionCache, []);
  return (
    <div className="demo">
      <RadioGroup name="demo-gate" legend="account service answers" value={k} onChange={pick} options={[
        { label: "signed in", value: "signed-in" }, { label: "still checking", value: "checking" }, { label: "service error (simulated)", value: "error" },
      ]} />
      <p className="demo__out">{NOTES[k]}</p>
      <div className="demo__frame demo__frame--gate">
        {k === "checking"
          ? <div className="mw-gate"><Spinner size="lg" label="checking your session" /></div>
          : <AuthGate key={k} base={BASES[k as keyof typeof BASES]}><p>account settings</p></AuthGate>}
      </div>
      <p className="mw-muted">signed out isn't shown here: on a real signed-out answer the gate sends people to sign in (location.replace to loginPath), which would take you away from this page.</p>
    </div>
  );
}

props

proptypedefaultrequired
basestring the origin of the account service to check the session against.—yes
childrenReactNode shown once the session is confirmed signed in.—yes
loginPathstring where to send a signed-out visitor; the current path is appended as next=."/login"no

css custom properties

variabledarklight
--c-danger#ff6b63#b42318
--c-fg-muted#b3b3af#46464a
--c-info#7cc4ff#1d5fa8
--c-ok#3ddc84#11652f
--c-warn#ffb454#8a5200
--fs-30.875remsame
--leading-normal1.5same
--sp-28pxsame
--sp-416pxsame

Classes: .mw-gate, .mw-status. Set any of these variables on an ancestor (or wrap a subtree in .mw-theme--dark / .mw-theme--light) to restyle just that part of a page.

usage

checking the session

tsx
import { AuthGate } from "@meowerse/ui";

<AuthGate base="https://auth.alxnko.dev">
  <p>account settings</p>
</AuthGate>

accessibility

  • The wait is a labelled status: "checking your session".
  • A failure is an alert that says what happened, with a "try again" button.
  • It redirects only when the account service really answers "signed out", never on a timeout or a network error (B18).
keydoes
Tabreaches "try again" after a failure

do and don't

do

  • Wrap whole pages that need an account.
  • Keep loginPath on the same origin; the return path (next=) is added for you.

don't

  • Don't treat a failed session check as signed out.
  • Don't nest gates; use one per page.