How It Works
The sign-in flow, PKCE, callback rules, and server endpoints
The sign-in flow
Your app shoo.dev Google
│ │ │
│ 1. startSignIn() │ │
│ create PKCE + state │ │
│ ── redirect /authorize ──▶ │ │
│ │ 2. no Shoo session: │
│ │ ──── redirect ──────────▶ │
│ │ │ 3. user signs in
│ │ ◀──── callback ────────── │
│ │ 4. first visit from this │
│ │ origin: user confirms │
│ │ (and profile sharing) │
│ ◀── redirect ?code&state ─ │ │
│ │ │
│ 5. POST /token │ │
│ code + code_verifier ─▶ │ 6. check PKCE, sign JWT │
│ ◀── { id_token } ───────── │ │
│ │ │
│ 7. save token, return to │ │
│ the starting page │ │If the user already has a Shoo session, step 2 and 3 are skipped. If they already approved your origin, and any requested profile sharing, step 4 is skipped too. The browser still navigates to Shoo and back.
No app registration
Shoo derives your app identity from the callback URL:
client_id = "origin:" + new URL(redirect_uri).originFor https://myapp.com/auth/callback, this is origin:https://myapp.com. It is also the token audience. See identity and privacy for what this means for previews, localhost, and domain changes.
Origin confirmation
The first time a user signs in to an origin, Shoo shows that origin and asks them to continue. If your app requests profile data, the same page asks to share it. Later sign-ins to an approved origin continue without the page. If the app requests profile data that the user did not approve yet, the page shows again.
If the user disconnects your app at shoo.dev/me, the next sign-in shows the confirmation again.
PKCE
Browser apps cannot keep a secret, so every flow uses PKCE with S256.
- The client creates a random
code_verifierand sends its SHA-256 hash ascode_challenge. - The client keeps the verifier in
sessionStoragein the tab that started sign-in. - At
/token, the client sends the verifier. Shoo checks it against the challenge.
A stolen code is useless without the verifier. Codes are single use and expire after two minutes.
Callback rules
- Use a dedicated callback path:
/shoo/callback(hosted script default) or/auth/callback(npm default). - The callback URL must use
https:, orhttp:onlocalhost,127.0.0.1, or::1. - The callback URL must not contain a query string, fragment, or credentials.
- At
/token, theredirect_urimust match the one used at/authorizeexactly. - The callback page should only finish sign-in. Do not forward its query string or run other logic there.
Tokens
Shoo signs an id_token JWT with ES256. The header has a kid. Public keys are at /.well-known/jwks.json.
The token lasts one hour. It is a bearer token. See tokens and sessions for renewal and revocation, and identity and privacy for claims.
Protocol support
Shoo uses an OAuth 2.0 style authorization code flow with PKCE and returns a signed JWT. It publishes a discovery document at /.well-known/openid-configuration.
Shoo is not a complete OpenID Connect provider. It does not support nonce, userinfo, refresh tokens, or dynamic client registration. Use the Shoo clients or the documented endpoints.
Server endpoints
| Method | Endpoint | Use |
|---|---|---|
GET | /authorize | Start sign-in |
POST | /token | Exchange a code for an id_token. CORS-enabled. |
POST | /session/check | Check a token and the app's current approval |
GET | /.well-known/jwks.json | Public signing keys |
GET | /.well-known/openid-configuration | Discovery document |
GET | /shoo.js | Hosted browser script |
GET | /me | User account page: connected apps, logout, deletion |