Skip to main content
QR code verification

A QR code displayed on a desktop or kiosk device.

QR code verification uses device credentials to enable cross-device authentication. This method leverages public key cryptography where private keys are securely stored on the user’s device. The Mobile SDK is used for two key steps:
  1. Registering a mobile device for QR code verification by adding a device credential. This step creates a new public/private key pair.
  2. Responding to an authentication request by approving or rejecting a device challenge. This step uses the device’s private key to sign a message which is verified on the server using the public key.

Use cases

  • Log into a desktop or web application from an application that is typically used on mobile
  • Identify a user on a kiosk in a quick service restaurant (QSR) to load loyalty programs, rewards and offers
  • Log into an application running on a TV
  • Complete a payment via a terminal

Sequence diagram

The general flow for QR code verification is as follows:
  1. Generate a challenge
  2. Display the QR code to the user
  3. Wait for the user to scan and respond on their mobile device
  4. Handle the authentication result (approved/rejected)

Portal setup

Enable the device credential authentication method for your tenant.

SDK setup

Server SDK

Initialize the SDK using your secret key from the API keys page and the API URL for your region.
import { Authsignal } from "@authsignal/node";

const authsignal = new Authsignal({
  apiSecretKey: "YOUR_SECRET_KEY",
  apiUrl: "YOUR_API_URL",
});

Web and Mobile SDKs

The Web SDK is used to initiate the challenge in a browser by generating a QR code. The Mobile SDK is used to respond to the challenge in a mobile app by scanning the QR code. Initialize both SDKs using your tenant ID from the API keys page and your API URL.
import { Authsignal } from "@authsignal/browser";

const authsignal = new Authsignal({
  tenantId: "YOUR_TENANT_ID",
  baseUrl: "YOUR_API_URL",
});

Enrollment

Scenario - Enroll users for QR code verification in your mobile app so it can be used as a method for authenticating on another device.

1. Generate enrollment token

In your backend, track an action for a user (e.g. “addAuthenticator”) to generate a short-lived token.
const request = {
  userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
  action: "addAuthenticator",
  attributes: {
    scope: "add:authenticators",
  },
};

const response = await authsignal.track(request);

const token = response.token;
This token will be used to authorize enrolling a new authentication method on their mobile device. The add:authenticators scope is required to enroll a new authentication factor for an existing user. This scope should only be used when the user is in an already authenticated state. For more information on using scopes safely refer to our documentation on authenticator binding.

2. Add credential

Use the token obtained in step 1 to enroll a new device credential for the user in the mobile app.
await authsignal.device.addCredential(token: "eyJhbGciOiJ...")

Authentication

Scenario - Let users scan a QR code with their mobile app to authenticate on another device.

1. Track action (optional)

You may optionally track an action from your backend if you want to create a QR code challenge that can only be completed by a specific user or run rules on the action for adaptive MFA. If you want to start a QR code challenge that can be claimed and completed by any user, skip to step 2. Track an action from your backend using our Server SDK.
const request = {
  userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
  action: "signIn",
};

const response = await authsignal.track(request);

if (response.state === "CHALLENGE_REQUIRED") {
  // Obtain token to present challenge
  const token = response.token;
}
Return the token to your frontend and call the Web SDK’s setToken method.
authsignal.setToken(response.token);
When creating a QR code challenge after tracking an action, the custom data and action code must be provided in the track request and cannot be overridden in the challenge request from the frontend.

2. Present QR code

Use our Web SDK or Client API to initiate a QR code challenge. Initiate QR code challenge Use the qrCode.challenge() method to initiate a challenge. The SDK will handle all the complexity of managing the challenge lifecycle, including automatic refreshing and state updates. By default the SDK will use WebSocket connections. If your environment does not support WebSockets, you can fall back to polling using REST API calls by setting polling to true.
const { data } = await authsignal.qrCode.challenge({
  action: "signIn",
  onStateChange: (state, token) => {
    switch (state) {
      case "claimed":
        // User has scanned the QR code - blur/hide it
        console.log("QR code claimed by user");
        break;
      case "approved":
        // Challenge approved - validate the token on your backend
        if (token) {
          validateChallengeOnBackend(token);
          // Continue with the application flow
        }
        break;
      case "rejected":
        // Challenge rejected - show error and offer retry
        console.log("Challenge rejected by user");
        break;
    }
  },
  onRefresh: ({ challengeId, expiresAt }) => {
    // Update your QR code display with the new challengeId
    updateQRCode(challengeId);
  },
  // Optional: Add context that will be shown on the user's device. Only available for anonymous challenges.
  custom: {
    deviceInfo: "Terminal A",
  },
  refreshInterval: 540000, // 9 minutes (default)
  polling: false, // Use WebSocket for real-time updates (default)
});

// Display the QR code using the challengeId
if (data?.challengeId) {
  displayQRCode(data.challengeId);
}
State changes
1

Initial state

After calling qrCode.challenge(), the QR code is ready to be scanned. Display it to the user.
2

Claimed

When onStateChange is called with state: "claimed", the user has scanned the QR code. You can use this state change to indicate progress in your UI.
3

Approved

When onStateChange is called with state: "approved" and a token, the user has approved the challenge. Pass the token to your backend for validation.
4

Rejected

When onStateChange is called with state: "rejected", the user has declined the challenge. Show an error message and provide a way to retry.
Refresh a challenge (optional) If you need to manually refresh a challenge you can use the qrCode.refresh() method:
// Manually refresh with optional updated context
await authsignal.qrCode.refresh({
  custom: {
    deviceInfo: "Terminal B",
  },
});
This must be called after qrCode.challenge() method has been called. The original callbacks will be used with the new challenge.

3. Scan QR code

Once the user scans the QR code, use the mobile sdk claimChallenge method to set the user attempting to complete the challenge. The claimChallenge method will return some context about the desktop or kiosk initiating the challenge such as ip address, location, user agent and custom data. This data can be shown to the user to help them decide if they want to approve or decline the challenge.
await authsignal.device.claimChallenge(
    challengeId: challengeId
)

4. Approve or decline the challenge

Present a dialog to allow the user to review the challenge context and approve or decline the challenge by calling the mobile sdk updateChallenge method.
await authsignal.device.updateChallenge(
    challengeId: challengeId,
    approved: true
)

5. Complete authentication

Once the challenge is approved, the onStateChange callback will return a token that should be passed to your backend to validate the challenge for the action and complete the authentication flow.
const response = await authsignal.validateChallenge({
  action: "signIn",
  token: "eyJhbGciOiJIUzI....",
});

if (response.state === "CHALLENGE_SUCCEEDED") {
  // User completed challenge successfully
}

Next steps

  • Passkeys - Offer the most secure and user-friendly passwordless authentication
I