Skip to content
OpenPulseDocs

Build

Sign in with OpenPulse

Add "Continue with OpenPulse" to a web app with openpulseAuth: passkeys, sealed session cookie, local token checks.

openpulseAuth() makes your app a relying party of openpulse.org/auth. People sign in with a passkey (or a magic link), and your app gets one sealed, httpOnly cookie. Reading the session costs a decrypt and a clock check; tokens are verified locally against the issuer’s keys and renewed about every 15 minutes.

  1. Register your app

    We register a client for your app with its callback URL, for example https://app.acme.com/auth/callback. You get a client id and secret.

  2. Set the environment

    Shell
    OPENPULSE_CLIENT_ID=acmeOPENPULSE_CLIENT_SECRET=…OPENPULSE_AUTH_SECRET=…          # 32+ characters; seals the cookie. Comma-separate to rotate.OPENPULSE_APP_URL=https://app.acme.com
  3. Mount the handlers (Next.js)

    auth.ts
    import { openpulseAuth } from '@openpulse/sdk/auth'export const auth = openpulseAuth({  // Your rules decide who gets in.  onSignIn: async (user) => ((await isMember(user.did)) ? { data: { role: 'member' } } : { error: 'not_invited' }),})
    app/auth/[...openpulse]/route.ts
    import { auth } from '@/auth'export const { GET, POST } = auth.handlers
    proxy.ts
    import { openpulseProxy } from '@openpulse/sdk/auth/next'import { auth } from '@/auth'// Renews the session before the access token lapses; guards the paths you name.export const proxy = openpulseProxy(auth, { protect: (path) => path.startsWith('/app') })
  4. Link to it

    Send people to /auth/login?returnTo=/app. The button should say Continue with OpenPulse (see UX guidelines).

Routes it adds#

RouteDoes
GET /auth/login?returnTo=&hint=Starts sign-in with PKCE, state and nonce
GET /auth/callbackFinishes it and sets the sealed cookie
GET /auth/sessionThe current user, or 401
POST /auth/tokenA same-origin access token for calling your own API
POST /auth/logoutEnds the session

What you get about the user#

sub is the person’s DID (store this), plus handle, preferred_username and, when they signed in to a realm handle, realm_id. On the server, read it with await auth.getSession(request). To verify bearer tokens in an API, use createVerifier({ audience }) from the same package; it also checks locally.

Payments and other sensitive actions#

Everyday sign-in is not enough to move money or mint keys. For those, send the person to the OpenPulse-hosted step-up page; it returns a short-lived assertion (su1.…) you pass to the call. Do not build your own second factor for these.