TryMellon

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 sessionToken to your backend
console.log('Session token:', pollResult.value.sessionToken)

[!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.


When the user scans the QR code, they are sent to your mobile page (e.g. /mobile-auth?session_id=...). The auth-link flow uses the same API and adds binding transaccional:

  1. GET context: The mobile page calls client.auth.crossDevice.getContext(sessionId) to obtain session type, WebAuthn options, and optionally approval_context and application_name from the backend.
  2. Above the fold: The screen shows title “Confirmar identidad”, the client/application name, and when present the approval_context (what action is being authorized), then the consequence phrase and the CTA (“Usar passkey”). This follows the contract in docs/epic-auth-ia-link/contrato-approval-context-y-copy.md.
  3. Approve: When the user taps the CTA, call approve(sessionId) as below. On success, show “Listo. Ya puedes cerrar esta ventana.” On 410/404 or no passkey, show the contract messages and recovery/register links where applicable.

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 contract success message (e.g. "Listo. Ya puedes cerrar esta ventana.").
  alert('Process complete! You are now logged in on your desktop.')
} else {
  alert('Failed to approve login: ' + approveResult.error.message)
}

Compatibilidad con RFC 8628 (Device Authorization Grant)

El flujo cross-device de TryMellon es conceptualmente similar al Device Authorization Grant (RFC 8628). Para integradores que ya usan device codes, este es el mapeo:

RFC 8628TryMellon cross-device
device_codesession_id (UUID de la sesión)
verification_uriBase URL del landing (ej. https://app.trymellon.com)
verification_uri_completeqr_url (URL completa con ?session_id=... para la página mobile-auth)
Poll POST /token con device_codePoll GET /v1/auth/cross-device/status/:sessionId
access_token en respuesta al pollsession_token en el body cuando status === 'completed'
expires_in (device_code)Session in Redis with TTL (e.g. 5 min); expires_at in init

TryMellon no expone user_code ni interval; el cliente debe hacer polling con un intervalo razonable (ej. 2–3 s) y respetar un timeout global de 2–3 minutos.


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.