TryMellon
Navigation

Cross-Device Authentication

Allow users to log in on desktop by scanning a QR code with their mobile device.

Cross-Device Authentication (QR Login)

Enable users to sign in on a desktop device by scanning a QR code with their mobile phone (where their passkey is stored).

TryMellon provides a seamless, zero-friction flow for cross-device authentication without needing WebSockets or complex state synchronization on your end.


1. Desktop: Initialize and Show QR

On the desktop interface (e.g., your login page), call init() to create a cross-device session. This returns a session_id and a qr_url.

approval_context (auth-link): To show a custom message on the mobile screen (e.g. “Access to orders”), send it when creating the session. The API accepts POST /v1/auth/cross-device/init with body { "approval_context": "Your message" } (max 200 chars; use the same Authorization: Bearer <publishableKey> and Origin as for the SDK). The SDK init() does not yet accept options; use the API directly for approval_context until the SDK exposes it.

import { TryMellon } from '@trymellon/js'

// 1. Create TryMellon Client
const clientResult = TryMellon.create({ appId: 'YOUR_APP_ID', publishableKey: 'cli_xxxx' });
if (!clientResult.ok) throw clientResult.error;
const client = clientResult.value;

// 2. Initialize cross-device session
const initResult = await client.auth.crossDevice.init()
if (!initResult.ok) { 
  console.error("Failed to init QR flow:", initResult.error.message); 
  return;
}

const { session_id, qr_url } = initResult.value;

// 3. Render the QR code
// You can use libraries like 'qrcode.react', 'svelte-qrcode', or 'qrcode' to render 'qr_url'
renderQrCode(qr_url);

// 4. Start polling for approval (Desktop waits here)
const controller = new AbortController()
const pollResult = await client.auth.crossDevice.waitForSession(
  session_id, 
  controller.signal
)

if (!pollResult.ok) {
  if (pollResult.error.code === 'TIMEOUT') {
    console.error('QR code expired. Please refresh the page.')
  }
  return;
}

// 5. Success! Send the session_token to your backend
console.log('Session token:', pollResult.value.session_token)

[!WARNING] About qr_url and the mobile app: The API returns qr_url in the form {baseUrl}/mobile-auth?session_id={session_id}. Your mobile web app must be deployed and its URL/origin allowed in the TryMellon dashboard for that application.


2. Mobile: Approve login screen

When the user scans the QR code, they are sent to your mobile page (e.g. /mobile-auth?session_id=...):

  1. GET context: Call client.auth.crossDevice.getContext(sessionId) to obtain session type, WebAuthn options, and optionally approval_context (a custom message set by the desktop app, e.g. “Authorize payment”) and application_name from the backend.
  2. Show context: Display the application name and, if present, the approval_context so the user understands what they are approving before tapping the CTA.
  3. Approve: Call approve(sessionId). On success, show a confirmation message (e.g. “Done. You can close this window.”). On 410/404 (session expired or not found) or no passkey, prompt the user to go back and retry or register a passkey.

3. Mobile: Approve login (code)

On your mobile page (e.g., /mobile-auth), extract the ID and trigger the approval.

// 1. Extract session_id from URL
const urlParams = new URLSearchParams(window.location.search);
const sessionId = urlParams.get('session_id');

if (!sessionId) {
  console.error("No session ID found in URL.");
  return;
}

// 2. Create the TryMellon client (same as desktop)
const clientResult = TryMellon.create({ appId: 'YOUR_APP_ID', publishableKey: 'cli_xxxx' });
if (!clientResult.ok) throw clientResult.error;
const client = clientResult.value;

// 3. Trigger WebAuthn approval on mobile
const approveResult = await client.auth.crossDevice.approve(sessionId)

if (approveResult.ok) {
  // Passkey validated — show a confirmation, e.g. "Done. You can close this window."
  alert('Process complete! You are now logged in on your desktop.')
} else {
  alert('Failed to approve login: ' + approveResult.error.message)
}

RFC 8628 (Device Authorization Grant) mapping

The TryMellon cross-device flow is conceptually equivalent to the Device Authorization Grant (RFC 8628). If your team is familiar with device codes, here is the mapping:

RFC 8628TryMellon cross-device
device_codesession_id (UUID of the session)
verification_uriYour app’s base URL (e.g. https://app.yourdomain.com)
verification_uri_completeqr_url (full URL with ?session_id=... for the mobile-auth page)
Poll POST /token with device_codePoll GET /v1/auth/cross-device/status/:sessionId
access_token in poll responsesession_token in the body when status === 'completed'
expires_in (device_code TTL)Session TTL in Redis (e.g. 5 min); expires_at returned by init

TryMellon does not expose user_code or interval. Poll at a reasonable interval (2–3 s) and enforce a global timeout (2–3 min) on the client side.


QR Default Domain (Bridge)

Don’t have a /mobile-auth page yet? Use the QR default domain — TryMellon hosts the mobile approval screen for you.

With the bridge domain, init() returns a qr_url pointing to TryMellon’s hosted page. Your desktop app only needs the SDK; no mobile page deployment required.

When to use it

  • Getting started fast: ship cross-device auth without building a mobile-auth page.
  • POCs and demos: show the QR flow to stakeholders in minutes.
  • Production with custom domain later: switch by changing primary_qr_base_url — no user migration.

Migrating to your own domain

  1. Deploy your own /mobile-auth page (see sections above for the mobile approval code).
  2. In your app settings, set primary_qr_base_url to https://yourdomain.com.
  3. Add https://yourdomain.com to Allowed origins.

Existing passkeys keep working — no re-registration needed.

[!TIP] For a complete integration guide with code snippets, dashboard config, and framework examples, see QR Default Domain — Integration Guide.


Under the Hood

  1. init(): Creates a temporary challenge in the TryMellon Redis cache (valid for 5 minutes).
  2. waitForSession(): Uses resilient HTTP Long-Polling. It doesn’t overload the network and will gracefully timeout if the user abandons the process.
  3. approve(): Leverages the user’s mobile platform authenticator (FaceID / TouchID) to sign the challenge, converting it into a full sessionToken that the desktop immediately receives.