Skip to content
anchr.ing
Guide

Sign in from a single-page app

A browser app signs users in with @anchring/auth/browser: hosted login with passkeys, PKCE, refresh before expiry and a fetch that adds the token.

Register a public, PKCE app (kind spa) in the dashboard (Apps) with the redirect http://localhost:5173/auth/callback (your dev server's origin) and the audience of your API, e.g. acme-api. Without an audience, tokens carry the client id as aud and your API answers 401 wrong_audience.

web/auth.ts
import { createAuth } from '@anchring/auth/browser';

const auth = createAuth({ project: 'acme', env: 'test', clientId: 'test_…' }); // the id the dashboard shows (Apps)

await auth.ready();                    // finishes a pending callback / restores the session
auth.user();                           // { sub, email, name, claims } or null
auth.user.subscribe((u) => render(u)); // signal / Svelte-store compatible

await auth.signIn();                   // redirects to hosted login: passkey, password, email code
await auth.fetch('/api/reports');      // adds Authorization
await auth.token();                    // fresh access token if you need it yourself
await auth.signOut();                  // revokes the refresh token, RP-initiated logout

The callback

The SDK handles the callback: when the page loads on the redirect path it exchanges the code (PKCE S256 with state and nonce), verifies the ID token and restores the URL you signed in from. Nothing to write. To route it yourself, turn autoCallback off:

const auth = createAuth({ project: 'acme', env: 'test', clientId: 'test_…', autoCallback: false, refresh: 'cookie' });

// on your /auth/callback route:
location.assign((await auth.handleCallback()) ?? '/');

Sign-in options

await auth.signIn({ redirectTo: '/reports' });              // back to /reports after the callback
await auth.signIn({ prompt: 'create' });                    // open hosted login on sign-up (self sign-up on)
await auth.signIn({ loginHint: 'ada@acme.example' });       // prefill the email
await auth.signIn({ prompt: 'login', params: { acr_values: 'mfa' } }); // step up to MFA

redirectTo must be a same-origin URL or path; anything else throws a TypeError, so it cannot become an open redirect. prompt: 'create' opens hosted login on its sign-up wording when the project has self sign-up turned on (dashboard, Settings).

Sessions and storage

Use refresh: 'cookie': the access token stays in memory and the refresh token goes into an HttpOnly cookie on the project's host, out of reach of page scripts. A reload or a new tab restores the session from that cookie, tabs refresh one at a time, and signOut() revokes it. Without it, tokens are kept in memory only and a reload signs in again (silently while the tenant session is alive). localStorage is deliberately not offered. More in Keeping browser tokens safe.

Email and name in the API

In the browser, auth.user() has the email from the ID token. Access tokens carry no email or name by default; give the app a claims template (Apps → Custom claims template (JSON)) if your API needs them:

{ "email": "{{user.email}}", "email_verified": "{{user.email_verified}}", "name": "{{user.name}}" }

Next: protect the API the app calls.

Every option, error and token type: the SDK README.