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

Getting Started (Vanilla JS)

Add Google sign-in with a script tag, a callback page, and a verified backend call

The hosted script needs no bundler. It still needs two things from your site: a page at the callback path, and a backend that verifies the token.

1. Add the script

Add it to every page that signs users in or reads identity:

<script src="https://shoo.dev/shoo.js"></script>

Load it once per page. Do not also bundle @shoojs/auth on the same page.

2. Add a sign-in button

<button id="sign-in">Sign in</button>
<script>
  document.getElementById("sign-in").addEventListener("click", () => {
    // Returns to the current page after sign-in.
    window.Shoo.startSignIn();
  });
</script>

startSignIn creates a PKCE verifier and state, saves them in sessionStorage, and navigates to Shoo.

A plain link also works. The script adds PKCE and state when the link is clicked:

<a href="https://shoo.dev/authorize?redirect_uri=https://your-app.com/shoo/callback">Sign in</a>

The script only changes links to its own Shoo server. By default, that is the origin the script loads from. If you serve a copy of shoo.js from your own origin, set the Shoo server on the tag. Otherwise the link opens without PKCE, and Shoo rejects it.

<script src="/shoo.js" data-shoo-base-url="https://shoo.dev"></script>

3. Serve the callback path

After sign-in, Shoo sends the browser to https://your-app.com/shoo/callback?code=…&state=…. Your host must return a page there that loads shoo.js. The script then exchanges the code, saves the token, and returns to the page where sign-in started.

The script does not create this page. Pick one option.

Static site: add a callback page

shoo/callback/index.html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Signing in…</title>
    <script src="https://shoo.dev/shoo.js" data-shoo-auto-callback="false"></script>
  </head>
  <body>
    <p id="status">Signing in…</p>
    <script>
      // Exchanges the code, saves the token, and returns to the start page.
      window.Shoo.handleCallback().catch(() => {
        document.getElementById("status").innerHTML =
          'Sign-in was cancelled or failed. <a href="/">Back to the app</a>';
      });
    </script>
  </body>
</html>

If you set other data-shoo-* attributes on your script tags, use the same attributes on the callback page.

If you pass storageKey or pkceStorageKey to startSignIn, pass the same values to handleCallback on this page. The automatic callback restores them from the stored sign-in. A manual call with data-shoo-auto-callback="false" does not.

Single-page app: rewrite the path to your app

Serve your index.html at /shoo/callback. Your index.html must load shoo.js.

Netlify: public/_redirects
/shoo/callback  /index.html  200
Vercel: vercel.json
{
  "rewrites": [{ "source": "/shoo/callback", "destination": "/index.html" }]
}
nginx
location = /shoo/callback {
  try_files /index.html =404;
}

The Vite dev server already serves index.html for unknown paths.

Callback rules

  • Use a dedicated path. Shoo supports /shoo/callback (script default) and /auth/callback (npm package default).
  • The callback URL must not contain a query string, fragment, or credentials.
  • Do not put other logic on the callback page. Never forward its query string to another site.

To use /auth/callback with the script, set it on every script tag:

<script src="https://shoo.dev/shoo.js" data-shoo-callback-path="/auth/callback"></script>

4. Read the identity

const identity = window.Shoo.getIdentity();
if (identity.userId) {
  console.log("Signed in as", identity.userId);
} else {
  console.log("Signed out");
}

identity.userId is the pairwise_sub. identity.token is the signed token. An expired or unreadable token reads as signed out.

To update your UI when the identity changes, including from another tab or on expiry:

const unsubscribe = window.Shoo.subscribe((identity) => {
  render(identity.userId);
});

The script checks with Shoo every 60 seconds by default. If the user disconnects your app, it clears the stored token and dispatches a shoo:login_required event:

window.addEventListener("shoo:login_required", () => {
  render(null);
});

5. Call your backend

const { token } = window.Shoo.getIdentity();
const res = await fetch("/api/me", {
  headers: { Authorization: `Bearer ${token}` }
});

Your backend must verify the token. See server verification.

6. Sign out

window.Shoo.clearIdentity();

This removes the token from this browser only. See what each control does.

Expiry

Tokens last one hour. When the token expires, getIdentity() reads as signed out. Call startSignIn() when the user acts. If the user still has a Shoo session, they return without a Google prompt.

Expiry uses the device clock. See known limits.

Profile data

window.Shoo.startSignIn({ requestPii: true });

Or request it on every sign-in:

<script src="https://shoo.dev/shoo.js" data-shoo-scope="profile email"></script>

Shoo asks the user before it shares email, name, picture, and pii_sub. See identity and privacy.

Next steps