@shoojs/auth
Vanilla browser client for Shoo authentication
Install
bun add @shoojs/authFramework-agnostic. Works in any browser environment. If you're using React, see @shoojs/react instead.
createShooAuth(options)
Creates a Shoo auth client instance. This is the main entry point.
import { createShooAuth } from "@shoojs/auth";
const auth = createShooAuth({
shooBaseUrl: "https://shoo.dev",
});ShooAuthOptions
| Option | Type | Default | Description |
|---|---|---|---|
shooBaseUrl | string | "https://shoo.dev" | Shoo server URL |
callbackPath | string | "/auth/callback" | Path Shoo redirects to after auth |
redirectUri | string | Auto-derived from callbackPath | Full callback URL |
clientId | string | "origin:{your_origin}" | Auto-derived from redirect URI origin |
requestPii | boolean | false | Request email/name/picture by default |
storageKey | string | "shoo_identity" | localStorage key for identity |
pkceStorageKey | string | "shoo_pkce" | sessionStorage key for PKCE bundle |
returnToStorageKey | string | "shoo_return_to" | sessionStorage key for return URL |
fallbackPath | string | "/" | Redirect target when no return-to is stored |
Returns a ShooAuthClient.
ShooAuthClient
client.startSignIn(params?)
Generates a PKCE bundle, stores the verifier in sessionStorage, and redirects the browser to the Shoo authorize endpoint.
await auth.startSignIn();
// With options
await auth.startSignIn({
requestPii: true,
returnTo: "/dashboard",
});StartSignInOptions:
| Option | Type | Description |
|---|---|---|
requestPii | boolean | Override default PII setting |
returnTo | string | URL to redirect to after sign-in |
redirectUri | string | Override callback URL |
clientId | string | Override client ID |
shooBaseUrl | string | Override Shoo server URL |
Returns Promise<{ url: string; bundle: PkceBundle }>.
client.handleCallback(params?)
The high-level callback handler. Exchanges the authorization code, persists identity, and redirects to the stored return-to URL.
const token = await auth.handleCallback();
// Browser redirects after this callUse this in your callback page. It handles everything: code exchange, identity persistence, URL cleanup, and redirection.
HandleCallbackOptions:
| Option | Type | Description |
|---|---|---|
url | string | URL to parse (defaults to window.location.href) |
redirectTo | string | Explicit redirect target (overrides stored return-to) |
returnTo | string | Alias for redirectTo |
fallbackPath | string | Fallback if no return-to is stored |
Returns Promise<TokenResponse | null>. Returns null if no callback params are present.
client.finishSignIn(params?)
Lower-level code exchange. Unlike handleCallback, this does not automatically redirect. Use this when you need more control.
const token = await auth.finishSignIn({
redirectAfter: false,
});FinishSignInOptions:
| Option | Type | Default | Description |
|---|---|---|---|
url | string | window.location.href | URL to parse |
clearCallbackParams | boolean | true | Remove code/state from URL bar |
redirectAfter | boolean | false | Redirect to return-to URL |
consumeReturnTo | boolean | true | Remove stored return-to after reading |
redirectTo | string | — | Explicit redirect target |
returnTo | string | — | Alias for redirectTo |
fallbackPath | string | "/" | Fallback path |
redirectUri | string | — | Override callback URL |
clientId | string | — | Override client ID |
shooBaseUrl | string | — | Override Shoo server URL |
Returns Promise<TokenResponse | null>.
client.getIdentity(storageKey?)
Reads the persisted identity from localStorage.
const identity = auth.getIdentity();
// { userId: "ps_a1B2...", token: "eyJ...", expiresIn: 300, receivedAt: 1234567890 }Returns ShooIdentity:
| Property | Type | Description |
|---|---|---|
userId | string | null | The user's domain-scoped ID, or null if signed out |
token | string | undefined | The raw id_token JWT |
expiresIn | number | undefined | Token lifetime in seconds |
receivedAt | number | undefined | Timestamp when the token was stored |
client.persistIdentity(userId, token, storageKey?, extras?)
Writes identity to localStorage. Called automatically by handleCallback and finishSignIn.
client.clearIdentity(storageKey?)
Removes the persisted identity from localStorage. Use this for sign-out.
auth.clearIdentity();client.decodeIdentityClaims(idToken?)
Decodes the JWT payload without verifying the signature. For display purposes only — always verify tokens on your server.
const claims = auth.decodeIdentityClaims();
// { iss, aud, sub, pairwise_sub, iat, exp, jti, email?, name?, picture? }Returns IdentityClaims | null.
client.exchangeCode(params)
Exchanges an authorization code for an id_token via POST /token.
const token = await auth.exchangeCode({
shooBaseUrl: "https://shoo.dev",
clientId: "origin:https://myapp.com",
redirectUri: "https://myapp.com/auth/callback",
code: "the_auth_code",
codeVerifier: "the_pkce_verifier",
});Returns Promise<TokenResponse>:
| Property | Type | Description |
|---|---|---|
token_type | "Bearer" | Always "Bearer" |
expires_in | number | Token lifetime in seconds |
pairwise_sub | string | Domain-scoped user ID |
id_token | string | Signed JWT |
client.checkSession(params?)
Checks whether the current token/session is still valid with Shoo (including revocation).
const result = await auth.checkSession();
if (result.status === "login_required") {
auth.clearIdentity();
}Returns:
{ status: "active" }{ status: "login_required", reason: "revoked" | "expired" | "invalid_token" }{ status: "unsupported" }when connected to an older Shoo server that does not expose/session/checkyet.
client.startSessionMonitor(options?)
Starts periodic background checks using checkSession.
const monitor = auth.startSessionMonitor({
intervalMs: 60_000,
onLoginRequired: () => auth.clearIdentity(),
});
// later
monitor.stop();SessionMonitorOptions:
| Option | Type | Default | Description |
|---|---|---|---|
intervalMs | number | 60000 | Check cadence |
immediate | boolean | true | Run one check immediately |
onLoginRequired | (result) => void | — | Called on revoked/expired/invalid token |
onError | (error) => void | — | Called on network/server errors |
token | string | Stored identity token | Override token source |
storageKey | string | "shoo_identity" | localStorage identity key |
clientId | string | Derived from redirect URI | Optional audience guard |
redirectUri | string | Client default redirect URI | Used when deriving clientId |
shooBaseUrl | string | Client default shooBaseUrl | Shoo API origin |
client.createPkceBundle()
Generates a PKCE bundle with a random state, verifier, and S256 challenge.
const bundle = await auth.createPkceBundle();
// { state: "...", verifier: "...", challenge: "..." }client.createSignInUrl(params)
Builds an authorize URL without navigating. Useful for custom sign-in flows.
const url = auth.createSignInUrl({
state: bundle.state,
codeChallenge: bundle.challenge,
});client.parseCallback(url?)
Extracts code and state from a callback URL. Returns null if no callback params are present.
client.clearCallbackParams()
Removes code, state, and error from the browser URL bar using history.replaceState.
Standalone functions
These are also exported at the top level for use without creating a client:
import {
createPkceBundle,
createSignInUrl,
parseCallback,
clearCallbackParams,
getIdentity,
persistIdentity,
clearIdentity,
decodeIdentityClaims,
exchangeCode,
checkSession,
startSessionMonitor,
deriveClientIdFromRedirectUri,
} from "@shoojs/auth";deriveClientIdFromRedirectUri(redirectUri)
Computes the client ID from a redirect URI: "origin:" + new URL(redirectUri).origin.
Types
All types are exported from @shoojs/auth:
import type {
PkceBundle,
ShooIdentity,
ShooAuthOptions,
StartSignInOptions,
ExchangeCodeParams,
CheckSessionOptions,
SessionCheckResult,
SessionMonitorOptions,
SessionMonitorHandle,
FinishSignInOptions,
HandleCallbackOptions,
ShooAuthClient,
} from "@shoojs/auth";IdentityClaims and TokenResponse are re-exported from @shoojs/types.