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

Tokens and Sessions

Token lifetime, renewal, revocation delay, and how app sessions differ

There are three separate sessions. They have different lifetimes and controls.

SessionWhere it livesLifetimeWho controls it
Shoo sessionSession cookie on shoo.devUp to 30 daysShoo and the user
App tokenYour app, usually localStorage1 hour (expires_in: 3600)Shoo sets exp
Your app sessionWhatever your backend createsYou decideYou

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

App tokens

The app token is the id_token JWT. It is a bearer token: anyone who has it can use it until exp.

  • The default lifetime is one hour. Read expires_in from the token response or exp from the token. Do not hard-code it.
  • jti is a unique ID. Shoo does not reject a reused token, so jti is not replay protection.
  • Send it to your backend in an Authorization: Bearer header over HTTPS.
  • Do not log it.

Renewal

Shoo has no refresh tokens. To get a new token, the user signs in again.

  • When the token expires, the client reports a signed-out or expired state. It does not navigate by itself.
  • Call signIn() on app load or when the user acts, for example from a "Sign in again" button. Pass returnTo to come back to the same page.
  • Sign-in is a full-page redirect to shoo.dev. If the user still has a Shoo session and your app is still approved, Shoo redirects back without a Google prompt. It is still a page navigation, not a background refresh.
  • Background token fetches, such as Convex's fetchAccessToken, return null when the token is expired. They never start a redirect.
  • The browser SDK compares exp with the device clock. A wrong clock changes when the token reads as expired. See known limits.

Revocation

A user can disconnect your app at shoo.dev/me. What happens next depends on how you check tokens.

CheckAfter the user disconnects your app
Offline JWT verificationStill passes until the token's exp
Online check (/session/check)Fails at once with revoked

The maximum delay for offline checks is the remaining token lifetime, up to one hour.

Browser polling (startSessionMonitor) only clears the token in that browser. It cannot stop someone who copied the token from calling your backend. If an action is sensitive, check online from your backend.

Online checks from your backend

Use the server helper with authorizationCheck: "required":

import { createShooVerifier } from "@shoojs/auth/server";

export const verifyShooTokenOnline = createShooVerifier({
  appOrigin: process.env.APP_ORIGIN!,
  authorizationCheck: "required"
});

It verifies the token offline, then asks Shoo whether the app is still approved. It denies access on revocation, errors, and timeouts.

Run offline verification for ordinary requests. Use the online check only for actions that need it, such as payments, deletes, or permission changes. See server verification.

Online check endpoint

For other languages, call the endpoint directly:

POST https://shoo.dev/session/check
Authorization: Bearer <token>
Origin: https://app.example.com

Shoo derives the expected audience from Origin. Send your exact app origin.

  • 200 { "status": "active" }: allow.
  • 401 { "status": "login_required", "reason": "revoked" | "expired" | "invalid_token" }: deny.
  • Anything else, a timeout, or no response: deny. A required check that cannot complete must not allow access.

Do not follow redirects with the bearer token.

Your own app session

If your backend exchanges a Shoo token for its own session cookie, that session has its own expiry and revocation rules. Shoo cannot end it.

  • A long app session keeps working after the user disconnects your app in Shoo.
  • If you want Shoo revocation to apply, keep your session no longer than the token, or call the online check on sensitive actions.

What each control does

ActionWhereEffect
App sign-outYour app (clearIdentity)Removes the token from this browser. A copied token still works offline until exp.
Shoo logoutshoo.dev/meEnds every Shoo session of this user, in all browsers. Copied Shoo session cookies stop working. Unused authorization codes and sign-ins in progress stop working. Next sign-in needs Google.
Disconnect appshoo.dev/meOnline checks fail. Offline checks pass until exp. Next sign-in asks for approval again.
Delete Shoo accountshoo.dev/meDeletes the account. Keeps a hashed deletion record. Online checks fail. Offline checks pass until exp. Does not delete your app's data.

Shoo logout does not sign the user out of apps:

  • App tokens that Shoo already issued stay valid until exp, up to one hour. Offline and online checks both still pass, because the app is still approved.
  • Your own app session is separate. Shoo cannot end it.

To stop one app, the user disconnects it. To end your app's session, sign the user out in your app.

Signing keys

Tokens are signed with ES256. The token header has a kid. Public keys are at https://shoo.dev/.well-known/jwks.json.

Fetch keys from the JWKS URL and cache them, as jose's createRemoteJWKSet does. Do not copy a key into your code. Keys can change.