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.
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.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.comMount 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.handlersproxy.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') })Link to it
Send people to
/auth/login?returnTo=/app. The button should say Continue with OpenPulse (see UX guidelines).
Routes it adds#
| Route | Does |
|---|---|
GET /auth/login?returnTo=&hint= | Starts sign-in with PKCE, state and nonce |
GET /auth/callback | Finishes it and sets the sealed cookie |
GET /auth/session | The current user, or 401 |
POST /auth/token | A same-origin access token for calling your own API |
POST /auth/logout | Ends 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.