SUPER EARLY WIP — USE AT YOUR OWN RISK■
shoo
GitHubCOMING SOON

Identity and Privacy

What Shoo stores, what your app receives, and where identity boundaries are

Read this page before you create user records keyed on Shoo IDs.

The origin is the app

Shoo has no app registration. Your app's identity is the origin of its callback URL: scheme, host, and port.

client_id = audience = "origin:" + new URL(redirect_uri).origin

Each of these is a different app to Shoo:

OriginAudience
https://app.example.comorigin:https://app.example.com
https://www.app.example.comorigin:https://www.app.example.com
http://localhost:5173origin:http://localhost:5173
https://my-app-git-pr-12.vercel.apporigin:https://my-app-git-pr-12.vercel.app

A different origin gets a different audience and a different pairwise_sub for the same Google account.

What this means for you:

  • Pick your production origin before launch. Users who sign in on www and on the bare domain become two different users.
  • Previews and localhost are separate apps. Their users do not match production users. Never accept a preview or localhost audience in your production backend.
  • Pin one exact audience per backend. Configure it per environment, for example with APP_ORIGIN. Do not derive it from the request Host header in production.
  • Shoo does not migrate domains. If you move to a new origin, your users get new IDs. Shoo does not merge accounts by email. To migrate, link the old and new IDs in your own database, for example by asking each user to sign in on both origins in one session. Do not link accounts by email alone.

Origins other than https: are accepted only for localhost, 127.0.0.1, and ::1.

Pairwise subject

pairwise_sub (also in sub) is the user ID your app should store.

  • It is the same every time the same Shoo user signs in to the same origin.
  • It is different for every origin.
  • It is opaque. It is not the Google account ID and it does not contain one.
  • Shoo creates it the first time the user signs in to your origin and stores it. Disconnecting and reconnecting the app keeps the same value.
  • If a user deletes their Shoo account and signs up again, they become a new Shoo user and get a new pairwise_sub.

The format is ps_ followed by a hex string. Treat it as an opaque string.

What your app receives

Every token contains:

ClaimValue
isshttps://shoo.dev
audorigin:<your-origin>
sub, pairwise_subThe pairwise subject
iat, expIssue and expiry time
jtiA unique token ID. It does not prevent replay.

Only when your app requests profile data and the user agrees:

ClaimValue
emailGoogle account email
email_verifiedGoogle's email verification flag
nameGoogle display name
pictureGoogle profile picture URL
pii_subA Shoo user ID that is the same for every app

pii_sub is a legacy claim. It is kept so existing apps continue to work.

It is the same value for one user in every app that received profile consent. Two apps that both store pii_sub can tell that they have the same user. The profile consent screen tells the user this before they agree.

Use pairwise_sub for your user records. Use pii_sub only if you run several origins and need to know that a user is the same person across them.

Pairwise subjects do not make a user anonymous. Apps that receive the same email address can also link the user.

What Shoo stores

DataWhy
Google account ID (sub)To recognize the user
Profile fields returned by GoogleOnly when profile access was requested
A random pii_sub per userLegacy cross-app ID
One pairwise_sub per user and originStable per-app ID
Per-origin approval and profile consentTo remember what the user allowed
Short-lived codes, tickets, rate-limit counters, IP addressesSecurity and abuse prevention
A signed session cookie on shoo.devKeeps the user signed in to Shoo
After account deletion: a deletion recordSee account deletion

On HTTPS the session cookie is __Host-shoo_session. A local HTTP development server uses shoo_session.

See the privacy policy.

User controls

Users manage Shoo at https://shoo.dev/me. Each control has a different effect. See tokens and sessions.

Account deletion

Deleting a Shoo account deletes the user's account, profile data, pii_sub, pairwise_sub values, app approvals, profile consents, and pending codes. Short-lived security rows, such as rate-limit counters, expire on their normal schedule.

Shoo keeps one deletion record per deleted Google account:

FieldValue
Identity hashSHA-256 of a fixed prefix and the Google account ID (sub)
Deletion timeWhen the deletion happened
Deletion checkpointAn internal sequence number that orders auth events

The record lets Shoo reject sign-ins that started before the deletion. It also lets Shoo match the account if a backup from before the deletion is restored. The hash has no secret key, so anyone who knows the Google account ID can match it to the record.

Shoo does not delete deletion records automatically today. A retention period is not set yet.

If the user signs in again later, Shoo creates a new account with new pairwise_sub values.

Deleting a Shoo account does not delete your app's records. If you store user data, give users a way to delete it in your app.