Farther ShoreDocs
Go to Farther Shore
Frontend SDK
Root & data components
Auth & sessions
Mount auth onceRead the normalized auth surfacePreview persona browser sessionsLocal live preview as a personaGate an application routeSession failure behavior
Access-aware UI
Permission gates
Custom components
Variables
@farthershore/farthershore-js
Share with another member
Shared and private in one portal
@farthershore/farthershore-js exports
@farthershore/farthershore-js/react exports
@farthershore/farthershore-js/test-utils exports
@farthershore/farthershore-js/errors exports
@farthershore/farthershore-js/components exports
@farthershore/farthershore-js/components/docs-chrome exports
frontend-sdk HTTP contracts
Status
Docs/Build the customer UI/Auth & sessions

Auth & sessions

Use the environment-selected subscriber auth strategy and gate application routes without owning token plumbing.

<FartherShoreRoot> selects the subscriber auth strategy from the environment's bootstrap data and mounts the matching provider:

  • clerk uses the platform-injected Clerk connection and installs the current subscriber session token on SDK requests;
  • test-personas reads Core's server-owned browser session through the SDK;
  • local mock mode supplies a deterministic signed-in owner without making network requests.

These are subscriber sessions inside the hosted frontend. They are separate from builder CLI login and backend runtime tokens.

<FartherShoreRoot> mounts the provider and auth context, but it does not render a sign-in surface for signed-out visitors. A custom portal must render a managed auth component explicitly and keep its application under the signed-in gate.

Mount auth once

tsx
import { createFartherShoreClient } from "@farthershore/farthershore-js";
import {
  FartherShoreRoot,
  FsSignIn,
  SignedIn,
  SignedOut,
} from "@farthershore/farthershore-js/components";

const fs = createFartherShoreClient();

export function App() {
  return (
    <FartherShoreRoot client={fs}>
      <SignedOut>
        <FsSignIn />
      </SignedOut>
      <SignedIn>
        <Application />
      </SignedIn>
    </FartherShoreRoot>
  );
}

Hosted environments normally need no Clerk prop: the edge injects the public connection into window.__FS_CONFIG__. A custom non-hosted shell can pass a public Clerk configuration to the root, but it must never embed a secret key.

Read the normalized auth surface

useFsAuth() presents the same shape for either live strategy:

tsx
import { useFsAuth } from "@farthershore/farthershore-js/react";

function AccountButton() {
  const auth = useFsAuth();
  if (!auth.loaded) return <span aria-busy="true" />;

  return auth.signedIn ? (
    <button onClick={() => void auth.signOut()}>Sign out</button>
  ) : null;
}

The important fields are:

FieldMeaning
strategyclerk or test-personas
loadedauth initialization has completed
signedIna subscriber session is present
usernormalized current user or null
signOut()revokes the active server session
roles, permissions, hasPermission()server-resolved member authorization
authzLoadedpermission resolution has completed

useFsAuth() must be under the managed auth provider. Use useOptionalFsAuth() only for a shared component that intentionally renders outside it.

The component subpath also exports SignedIn, SignedOut, AuthLoading, FsSignInButton, FsSignOutButton, and FsUserButton for declarative chrome.

Preview persona browser sessions

Create a persona, then start its browser session from the authenticated CLI:

bash
farthershore persona bootstrap my-business \
  --env preview \
  --idempotency-key <persisted-persona-bootstrap-attempt-key> \
  --plan starter

farthershore persona login my-business <personaId> --env preview

persona login opens the platform-owned /persona-sign-in page on the hosted portal origin. Its single-use handoff secret is carried only in the URL fragment, which the page clears before making a same-origin exchange. Core then sets a server-owned HttpOnly cookie and the page returns to the requested portal route. The custom frontend bundle never receives the handoff secret, a bearer, or an access key.

The bootstrap key is returned once for CLI and gateway testing only. Do not paste it into a web form, add it to frontend configuration, or persist it in browser storage. A persona key belongs to its selected preview environment and is not a production credential.

Use the safe identity projection on the SDK session to render custom persona-aware UI:

tsx
import { useSession } from "@farthershore/farthershore-js/react";

function PersonaName() {
  const session = useSession();
  const persona = session.data?.authSession;

  if (!persona) return null;
  return <span>{persona.displayName ?? persona.userId}</span>;
}

Local live preview as a persona

While building the frontend, run the checkout itself against the real environment, already signed in, with hot reload (CLI 0.33.5+):

bash
farthershore frontend dev --live --business my-business --env preview \
  --persona <personaId-or-name> --port 5173 --format json

The CLI resolves the business, environment, and persona, issues a short-lived local-preview lease that never leaves the CLI process, starts Vite on a private loopback port, and serves http://localhost:5173 through a CLI-owned proxy: /_fs/api/* calls carry the lease to Core, /_fs/secure/* (fs.fetch) goes to the environment's gateway, and everything else reaches Vite with the session cookie, Authorization, and all trust headers stripped — including HMR WebSocket upgrades. The browser opens a platform-owned /persona-sign-in page on localhost whose URL carries no secret; the proxy attaches the one-time handoff itself and Core sets the same server-owned HttpOnly cookie as the hosted portal. frontend preview serves the production bundle the same way.

The JSON envelope is emitted only once the preview is signed in and carries localUrl, signInUrl, and vitePort. The lease renews itself while the command runs; SIGINT, SIGTERM, or SIGHUP stops Vite and revokes it, and the cookie stops authenticating immediately. Lifecycle notices (a renewal that keeps failing, Vite exiting) are written to stderr in every output mode. --mock and --core-url cannot be combined with --persona. The persona.local_preview.issue|renew|revoke operations exist only for this command; never call them by hand.

authSession contains only verified, non-secret session metadata. It is not a token source. For a custom logout control, call fs.auth.signOut() (or the signOut() returned by useSession()); it asks Core to revoke the server cookie before the SDK clears its local read caches. If revocation fails, retain the authenticated UI and surface the failure rather than pretending the browser is signed out.

Gate an application route

The SDK is router-agnostic. <RequireAuth> renders only after auth is allowed; your router performs navigation through onRedirect:

tsx
import { RequireAuth } from "@farthershore/farthershore-js/components";
import { useNavigate } from "react-router-dom";

function JobsPage() {
  const navigate = useNavigate();
  return (
    <RequireAuth
      fallback={<p>Signing in…</p>}
      redirectTo="/"
      onRedirect={(target) => navigate(target)}
    >
      <Jobs />
    </RequireAuth>
  );
}

For custom routing, useAuthGuard({ requireAuth: true }) returns loading, allowed, redirecting, or denied plus a redirect target when relevant. It does not navigate. By default it stores the current path in sessionStorage under fs-return-to; this is a return-location hint, never a credential. Pass returnToKey: null to disable that behavior.

RequireAuth and useAuthGuard can also require one permission, but that check only controls presentation. The gateway remains the security boundary for the route call, and the backend must still scope application records to the verified organization.

Session failure behavior

The SDK uses the active provider for every request instead of asking application code to cache credentials. When Core no longer accepts the persona cookie, the managed provider returns the UI to its signed-out flow. Application code should handle the typed 401/permission/limit errors from the requested operation and must never copy a bearer or access key into browser storage.

PreviousRoot & data componentsNextAccess-aware UI

On this page

Mount auth onceRead the normalized auth surfacePreview persona browser sessionsLocal live preview as a personaGate an application routeSession failure behavior