# Identity and authentication

Lakebed Auth is the built-in identity layer for capsules. Guest auth works with zero setup, and Google sign-in is first-party: no OAuth dashboards, no keys to configure, no redirect URIs to register. Render `<SignInWithGoogle />` and it works, in dev and in hosted deploys.

## Identity contract

An authenticated identity has one durable authorization key:

```ts
ctx.auth.userId === ctx.auth.subject; // google:usr_...
```

`userId` is opaque and immutable. It does not encode the deploy hostname, so a generated deploy URL and a custom domain resolve to the same `userId`. Key all user-owned data on it.

`ctx.auth.identityAliases` lists audience-scoped legacy aliases (`google:ps_...`), including at least the current audience's alias even for apps that never changed hostnames. Aliases exist only so an app with rows keyed before stable subjects existed can find and rewrite them. Never key new data on an alias; always use `userId`.

Email, `emailVerified`, name, and picture are profile data. A verified email may help your app locate a pending invitation, but it is never an authorization key and never evidence of access — not even as a fallback while verification is unavailable. Email changes do not change `userId`, and two accounts with the same email remain distinct users.

## What your app can rely on

- Tokens are bound to their origin. A token issued for one hostname cannot be replayed at another.
- Sign-in state distinguishes a bad token from an unreachable verifier. Public capsules fall back to an unauthenticated guest when verification fails or is unavailable; an outage does not erase the browser's stored session, so authentication recovers on its own when service returns.
- Revoking access takes effect immediately for new requests and live WebSocket subscriptions. Signing out clears private query caches in the browser.
- Signing in again after revocation restores the same `userId`, but previously issued tokens stay dead.
- Deleting an account invalidates it: subsequent verification of its tokens fails.
- When an app requests profile fields, the user approves first. Unapproved profile data is never exposed to the app.

## Auth shape

```ts
type Auth = {
  userId: string;
  subject?: string; // same immutable key as userId for authenticated identities
  identityAliases?: string[]; // migration-only audience-scoped aliases
  displayName: string;
  provider: "guest" | "google";
  isGuest: boolean;
  isAuthenticated: boolean;
  isLoading?: boolean; // client-only
  email?: string;
  emailVerified?: boolean;
  picture?: string;
};
```

## Local dev

`npx lakebed dev` uses first-party Lakebed Auth automatically, including real Google sign-in on localhost. Nothing to configure.

For guest identities in dev, set the global guest with `npx lakebed auth as alice`, or use per-tab identities with `?lakebed_guest=alice` in the app URL.

## Migrating from older identities

Identity records created by the previous external broker are not automatically linked to first-party `google:usr_...` identities; users must sign in again. If application data is keyed by an older provider subject, migrate it through explicit reauthorization or account linking. Never infer a link from email. `identityAliases` does not cover external-broker subjects; it only lists Lakebed Auth pairwise aliases.
