Getting Started

QR Sign-In

Start a desktop challenge, approve it on the phone, and claim the Better Auth session back on desktop.

QR Sign-In

Use @onmax/better-auth-mobile-qr when the user starts on desktop but the signing key lives on the phone inside the Nimiq Pay mini-app.

Register the QR plugin

server/auth.config.ts
import { mobileQrSignIn } from '@onmax/better-auth-mobile-qr'
import { createNimiqPayMobileQrProvider } from '@onmax/mobile-signer-nimiq-pay'

export default {
  plugins: [
    mobileQrSignIn({
      appName: 'My Desktop Login',
      endpointPrefix: '/mobile-qr',
      trustedOrigins: [
        'http://127.0.0.1:3000',
        'http://localhost:3000',
      ],
      providers: [
        createNimiqPayMobileQrProvider(),
      ],
      returnTo: '/qr/result',
      signingPathTemplate: '/qr/phone/{challengeId}',
    }),
  ],
}

Desktop flow

qr-desktop.ts
import { pollMobileQrSignIn, startMobileQrSignIn } from '@onmax/better-auth-mobile-qr/client'

const started = await startMobileQrSignIn(authClient.$fetch.bind(authClient), {
  endpointPrefix: '/mobile-qr',
  providerId: 'nimiq-pay',
  returnTo: '/qr/result',
})

const status = await pollMobileQrSignIn(authClient.$fetch.bind(authClient), {
  endpointPrefix: '/mobile-qr',
  challengeId: started.challengeId,
})

Phone flow

qr-phone.ts
import { completeMobileQrChallenge } from '@onmax/mobile-signer-bridge'
import { createNimiqPaySignerBridge } from '@onmax/mobile-signer-nimiq-pay'

await completeMobileQrChallenge($fetch, {
  challengeId,
  endpointPrefix: '/api/auth/mobile-qr',
  providerId: 'nimiq-pay',
  bridge: createNimiqPaySignerBridge(),
})

Endpoint contract

EndpointPurpose
POST /mobile-qr/startCreates a pending challenge and sets the desktop poll cookie
GET /mobile-qr/challenge/:challengeIdReturns the server-issued message for the phone
POST /mobile-qr/completeVerifies the phone assertion and marks the challenge approved
GET /mobile-qr/status?challengeId=...Checks the desktop poll cookie and claims the Better Auth session

Session ownership

The phone never receives a Better Auth session token in this flow. The phone only approves the challenge. The desktop creates and stores the actual Better Auth session after the status endpoint confirms the poll cookie and sees an approved signer.

Version one is same-origin only. Bind trustedOrigins to the exact desktop origins you expect and keep the QR page on that origin.
Copyright © 2026