Portal
astroidPortal, astroidPortalGuardConfig, portalGuard, guardResponse,
requireCustomer, resolvePortalSession, isSameOrigin, portalSignOut,
definePortalNav.
A second Better Auth instance for customers. The mount, cookie prefix, and
portal_* table prefix are fixed, not configurable—the studio keeps Better
Auth’s defaults because the editor client hardcodes them, so the portal is the one
that moves. Two instances sharing a cookie prefix fails intermittently in
production and looks like a session bug.
The guard is fail-closed: a session resolver that throws degrades to signed out, never to signed-in.
Signing out
Section titled “Signing out”Ending a session is a write. The portal’s session cookie is SameSite=Lax,
which a browser sends with a top-level GET from any site, so a /logout that
signs out on a GET lets any other website sign your users out with a link.
portalSignOut signs out only on a same-origin POST:
---import { portalSignOut } from "astroidjs";import { redirectWithCookies } from "louise-toolkit/auth";import { signOutPortal } from "../portal-auth.js";
const result = await portalSignOut(Astro.request, { user: Astro.locals.portalUser, signOut: signOutPortal,});if (result.state === "signed-out") return redirectWithCookies(result.cookies, "/");Astro.response.status = result.status;---<form method="post" action="/logout"><button>Sign out</button></form>| Request | state |
status |
|---|---|---|
| A same-origin POST | signed-out, with cookies |
200 |
| A same-origin POST whose sign-out fails | failed |
503 |
| A GET, with no session | signed-out |
200 |
| A GET while signed in | confirm |
200 |
| Any other POST while signed in | confirm |
403 |
- Same-origin is louise-toolkit’s
isSameOrigin, the strict one: a POST with neitherOriginnorRefererdoesn’t pass. The portal’s ownisSameOrigin, whichrequireCustomeruses, lets that request through. - A same-origin POST always signs out, even with no session resolved. A session lookup that failed reads as signed out, and Better Auth’s sign-out expires the cookies whether or not it finds a session.
cookiesholds everySet-Cookiethe sign-out expired, one header each. Pass it toredirectWithCookies, or append eachcookies.getSetCookie()entry to the page’s response.- A failed sign-out leaves the session alone and calls
reportDegradedwithportal.sign-out. signOutreceives the request, so the auth instance can be built for its origin. The scaffoldedsrc/portal-auth.tsexportssignOutPortal, which callsauth.api.signOut(louise-toolkit 0.41).
A project with a portal gets src/pages/logout.astro scaffolded once, in its
own layout, for you to restyle. Make every Sign out control a
<form method="post" action="/logout">. A link only ever reaches the
confirm state. The page imports signOutPortal(request: Request): Promise<Response>
from src/portal-auth.ts, so a project with its own portal-auth.ts adds
that export before it runs astroid generate.