Push authentication

A push authentication request displayed on a mobile device.

Push authentication uses device credentials to securely authenticate users through their mobile app. This method leverages public key cryptography where private keys are securely stored on the user’s mobile device. The Mobile SDK is used for two key steps:
  1. Registering a mobile device for push authentication 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.
Although push authentication involves sending a push notification to a mobile app, the push notification is not part of the authentication mechanism and delivery is not required for the method to work. The push notification is only used to help prompt the user to open the app - if it doesn’t arrive, they can still open the app manually.

Sequence diagram

The diagram below illustrates the sequence for a push authentication challenge.

Configure push in the Authsignal Portal

Enable the device credential authentication method for your tenant and configure a webhook for sending push notifications.

Grab your Authsignal credentials

Head to Settings and grab your Tenant ID, API URL and API secret key. Add them as environment variables in your project:
AUTHSIGNAL_API_URL=your_region_api_url
AUTHSIGNAL_TENANT_ID=your_tenant_id
AUTHSIGNAL_SECRET_KEY=your_secret_key

Implementing registration

1. Backend – Generate registration token

Track an action (e.g. “addAuthenticator”) to generate a short-lived token which can be used to authorize adding a new authentication method for a user.
const request = {
  userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
  action: "addAuthenticator",
  attributes: {
    scope: "add:authenticators",
  },
};

const response = await authsignal.track(request);

const url = response.url;

2. Mobile app – Add device credential

Use the token obtained in step 1 to register a new device credential in the app.
await authsignal.device.addCredential(token: "eyJhbGciOiJ...")
Device registration should be implemented after login or when the app is launched in an authenticated state. Registration tokens should only ever be generated for authenticated users in order to ensure a strong binding between authentication methods.

Implementing authentication

1. Backend – Track an action

When a user performs an action that requires push authentication, your backend should track an action (e.g. “signIn”) using our Server SDK or Server API.
import { Authsignal } from "@authsignal/node";

const authsignal = new Authsignal({
  apiSecretKey: process.env.AUTHSIGNAL_SECRET_KEY,
});

const trackResponse = await authsignal.track({
  userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
  action: "signIn",
});

// Handle different action outcomes
if (trackResponse.state === "CHALLENGE_REQUIRED") {
  // User needs to complete challenge
  return { token: trackResponse.token };
} else if (trackResponse.state === "ALLOW") {
  // Proceed with the action - no challenge needed
  return { success: true };
} else if (trackResponse.state === "BLOCK") {
  // Block the action for security reasons
  return { error: "Action blocked for security reasons" };
}
Understanding action statesWhen you track an action, Authsignal returns one of four possible states:
  • CHALLENGE_REQUIRED - User must complete an authentication challenge (proceed to step 2)
  • ALLOW - Action is permitted without additional authentication
  • BLOCK - Action is blocked for security reasons
  • REVIEW - Action requires manual review
Learn more about action outcomes.

2. Web app – Challenge the user

Use our Web SDK or Client API to initiate a push challenge. This will trigger a call to your webhook with the push event schema so that it can send the user a push notification.

Start a push challenge

import { Authsignal } from "@authsignal/browser";

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

// Set the token from the track response
authsignal.setToken(token);

// Send the push challenge
const challengeResponse = await authsignal.push.challenge();

Poll for the challenge result

Poll for the challenge result using our Web SDK, Mobile SDKs or Client API.
// Poll for the challenge result
const { data, error } = await authsignal.push.verify({
  challengeId: challengeResponse.data.challengeId,
});

if (error || !data) {
  // Handle error
} else if (!data.isConsumed) {
  // The challenge has not yet been consumed
  // Continue polling
} else {
  // The challenge has been consumed
  // Stop polling and check if approved or rejected

  if (data.isVerified) {
    // Challenge has been approved
    // Obtain token to send to backend to validate challenge
    const validationToken = data.token;
  } else {
    // Challenge has been rejected
  }
}

3. Mobile app – Check for pending challenge

Use the mobile SDK’s Get Challenge method to check if there is a pending challenge for the device.
let response = await authsignal.device.getChallenge()

if let error = result.error {
    // The credential stored on the device is invalid
} else if let challenge = result.data {
    // A pending challenge request is available
    // Present the user with a prompt to approve or deny the request
    let challengeId = challenge.challengeId
} else {
    // No pending challenge request
}
This check should be done when launching or foregrounding the app - either from a push notification or when opened manually - as well as when the app receives a push notification while it is already foregrounded.

4. Mobile app – Present challenge

If there is a pending challenge, present a dialog to allow the user to approve or reject the challenge. To approve or reject the challenge, use the mobile SDK’s Update Challenge method.
await authsignal.device.updateChallenge(
    challengeId: challengeId,
    approved: true
)

5. Backend – Complete authentication

Once the challenge is approved, the polling request in step 2 will return a token that should be passed to your backend to validate the challenge in order to complete the authentication flow.
const request = {
  token: "eyJhbGciOiJ...",
};

const response = await authsignal.validateChallenge(request);

if (response.state === "CHALLENGE_SUCCEEDED") {
  // The user completed the challenge successfully
  // Proceed with authenticated action or integrate with IdP to create authenticated session
} else {
  // The user did not complete the challenge successfully
}

Next steps

  • Adaptive MFA - Set up smart rules to trigger authentication based on risk
  • QR code - Implement QR code authentication
  • Trusted device - Implement trusted device authentication
  • Passkeys - Offer the most secure and user-friendly passwordless authentication