Contact salesSign inSign up
AuthsignalAuthsignal
Product
Passwordless / multi-factor authentication (MFA)
Drop-in authentication
Risk-based authentication
Passkeys
Biometric authentication
WhatsApp OTP
Authenticator apps (TOTP)
Push authentication
SMS OTP
Email OTP
Magic links
See all authenticators
See less authenticators
Palm biometrics
Contactless payments & identity verification
Flexible integration modes
Pre-built UI
Low code
UI components
Customizable
Custom UI
Flexible
Digital credentials API Beta
Authenticate customers instantly using digital credentials
Session management
Keep users signed in across web and mobile after authentication
Fraud Controls
Rules and policies engine
Step-up authentication
No-code rule creation
Risk alerts
User observability
Audit trails
Dynamic linking
Why Authsignal?
Complete authentication infrastructure from enrollment to step-up auth, modular by design
Solutions
By USE CASE
View All
Account takeovers (ATO)
Go passwordless
Call center
SMS cost optimization
Existing apps
QR code payments
Step-up MFA
Palm biometrics payments
By INDUSTRY
View All
Financial services
Marketplace
e-Commerce
FinTech
Crypto
Healthcare
By Integration (identity provider)
Amazon Cognito
Azure AD B2C
Duende IdentityServer
Keycloak
Auth0
NextAuth.js
Custom identity provider
By ROLe
Engineers
Product
Passwordless / Multi-factor Authentication (MFA)
Flexible Integration Modes
Pre-built UI · Low code
UI Components · Customizable
Custom UI · Flexible
Digital credentials API Beta
Authenticate customers instantly using digital credentials
Session management
Issue JWT access and refresh tokens
Why Authsignal?
Plug in Authsignal to elevate your IDP — effortless integration with any architecture.
Drop-in Authentication
Risk-based authentication
Passkeys
Biometric authentication
WhatsApp OTP
SMS OTP
Email OTP
Magic links
Authenticator apps (TOTP)
Push notifications
Palm Biometrics
Contactless payments & identity verification
Fraud Controls
Rules and Policies Engine
Step-up Authentication
No Code Rule Creation
Risk Alerts
User Observability
Audit Trails
Use Cases
Financial services
Account takeovers (ATO)
Marketplace
Go passwordless
e-Commerce
Solutions
By Use Case
Account takeovers (ATO)
Go passwordless
Call center
SMS cost optimization
Existing apps
QR code payments
Step-up MFA
Palm Biometric Payments
View all Use Cases
By Industry
Financial services
Marketplace
e-Commerce
FinTech
Crypto
Healthcare
View all Industries
By Integration (identity provider)
Amazon Cognito
Azure AD B2C
Duende IdentityServer
Keycloak
Auth0
NextAuth.js
Custom identity provider
By Role
Engineers
PricingAboutDocsBlog
Schedule a call
Try Authsignal
AUS Flag

Authsignal secures millions of passkey transactions out of our hosted Sydney region.

AUS Flag

Authsignal secures millions of passkey transactions out of our hosted Sydney region.

Join us today!
Right icon
Blog
/
Current article
Push authentication
React native
Node.js
Multi-factor authentication
Guides

How to add push authentication to your app with Authsignal and React Native

Ashutosh Bhadauriya
⬤
March 27, 2026
Share
How to add push authentication to your app with Authsignal and React Native

Most second-factor flows put the user to work: open a different app, find the code, type it before it expires. Push authentication inverts that. The user clicks "Sign In" on the web, their phone buzzes, they tap "Approve", and that's it. Nothing to copy, nothing to intercept.

This guide builds that flow end-to-end using Authsignal: a React web app, a React Native mobile app, and a Node.js server coordinating between them.

How it works

Three actors coordinate the push flow. The web app initiates sign-in requests, the server orchestrates challenges through Authsignal, and the mobile app presents them to the user for approval.

The flow:

  1. User clicks "Sign In" on the web app.
  2. The server calls Authsignal's track API to create a challenge for the signIn action.
  3. The web app uses the returned token to create a push challenge via Authsignal's browser SDK.
  4. The mobile app detects the pending challenge and shows an approval screen.
  5. The user taps "Approve" or "Deny".
  6. The server updates the action state in Authsignal, and the web app receives the result.

Project structure

authsignal-push-auth/
├── apps/
│   ├── api/          # Express server
│   ├── web/          # React + Vite
│   └── mobile/       # React Native + Expo
└── .env

‍

Prerequisites

  • Authsignal account: get your tenant ID, API secret, and API URL from Settings > API Keys
  • Expo CLI (npm install -g expo-cli)
  • iOS Simulator (Xcode) or Android Emulator

Step 1: Server - Track the sign-in action

When the web app wants to authenticate a user, it calls the server. The server tells Authsignal to track the action, which creates a challenge that the mobile device will respond to.

//apps/api/src/index.ts: 

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

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

app.post("/api/auth/push/start", async (req, res) => {
  const { userId, action } = req.body;

  const tracked = await authsignal.track({
    userId,
    action, // "signIn"
    attributes: {
      custom: { note: "Web sign-in confirmation" }
    }
  });

  if (!tracked.isEnrolled) {
    return res.json({ error: "User has no enrolled device" });
  }

  // Store challenge context so mobile can discover it
  createChallengeContext({
    idempotencyKey: tracked.idempotencyKey,
    userId,
    action
  });

  return res.json({
    token: tracked.token,
    idempotencyKey: tracked.idempotencyKey,
    isEnrolled: tracked.isEnrolled
  });
});

‍

The isEnrolled check matters here. If the user hasn't registered a device yet, there's nowhere to send the push. The server returns early and the web app can fall back to another method. Device enrollment is covered in Step 4.

Step 2: Web - Create the push challenge

The web app takes the token from the server and uses Authsignal's browser SDK to create the actual push challenge. After that, it polls the server for the result.

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

const authsignal = new Authsignal({
  tenantId: config.tenantId,
  baseUrl: config.baseUrl
});

async function sendSignInPush() {
  // Tell the server to track the action
  const startResponse = await fetch("/api/auth/push/start", {
    method: "POST",
    body: JSON.stringify({ userId: "user_123", action: "signIn" })
  });
  const { token, idempotencyKey } = await startResponse.json();

  // Create the push challenge via Authsignal
  authsignal.setToken(token);
  const challenge = await authsignal.push.challenge({ action: "signIn" });

  // Register the challengeId with the server
  await fetch("/api/auth/push/challenge-created", {
    method: "POST",
    body: JSON.stringify({
      idempotencyKey,
      challengeId: challenge.data.challengeId
    })
  });

  // Poll for the result
  while (true) {
    const status = await fetch(`/api/auth/push/status/${idempotencyKey}`);
    const { status: result } = await status.json();

    if (result === "approved") break;
    if (result === "denied") break;

    await sleep(2000);
  }
}

‍

push.challenge() is what delivers the challenge to the mobile device. The server's track call creates the action, but the browser SDK triggers the push. Polling at 2-second intervals is fine for a demo. In production, use WebSockets or configure APNs/FCM through the Authsignal portal for real-time delivery.

Step 3: Mobile - Detect and approve the challenge

The mobile app polls the server for pending challenges. When one arrives, it surfaces an approval screen.

//apps/mobile/src/App.tsx

import { Authsignal } from "react-native-authsignal";

useEffect(() => {
  const poll = async () => {
    const response = await fetch(
      `/api/mobile/pending-challenge?userId=${userId}`
    );
    const { challenge } = await response.json();

    if (challenge) {
      setCurrentChallenge(challenge);
    }
  };

  const interval = setInterval(poll, 3000);
  return () => clearInterval(interval);
}, [userId]);

‍

When the user taps "Approve", the mobile app tells the server, which updates the action state in Authsignal:

// apps/api/src/index.ts
app.post("/api/mobile/resolve-challenge", async (req, res) => {
  const { idempotencyKey, approved } = req.body;
  const context = getChallengeContext(idempotencyKey);

  await authsignal.updateAction({
    userId: context.userId,
    action: context.action,
    idempotencyKey,
    attributes: {
      state: approved
        ? UserActionState.CHALLENGE_SUCCEEDED
        : UserActionState.CHALLENGE_FAILED
    }
  });

  resolveChallengeByIdempotencyKey(
    idempotencyKey,
    approved ? "approved" : "denied"
  );

  return res.json({ ok: true });
});

‍

Once updateAction is called with CHALLENGE_SUCCEEDED, the web app's polling picks up the result and grants access. The round trip typically completes in 3-5 seconds.

Step 4: Device enrollment

Before a user can approve push challenges, their device needs a credential. This generates a cryptographic key pair: the private key stays in the device's secure enclave, the public key is stored by Authsignal.

The server creates a scoped enrollment token:

app.post("/api/auth/enroll/start", async (req, res) => {
  const tracked = await authsignal.track({
    userId: req.body.userId,
    action: "enrollPushCredential",
    attributes: { scope: "add:authenticators" }
  });

  return res.json({ token: tracked.token });
});

‍

The mobile app uses it to register the device:

const enrollment = await fetch("/api/auth/enroll/start", {
  method: "POST",
  body: JSON.stringify({ userId })
});
const { token } = await enrollment.json();

await authsignal.push.addCredential({
  token,
  requireUserAuthentication: false
});

‍

The scope: "add:authenticators" restricts this token to credential registration only, it can't be used to approve actions. requireUserAuthentication: false means the private key can be used without biometric verification. Set this to true for higher-security contexts (banking, enterprise), this adds a Face ID or Touch ID prompt every time the key is used to approve a challenge, not just during enrollment.

Deploy and test

git clone https://github.com/authsignal-examples/authsignal-push-auth
npm install
cp .env.example .env

‍

Add your Authsignal credentials to .env:

AUTHSIGNAL_TENANT_ID=your_tenant_id
AUTHSIGNAL_SECRET_KEY=your_secret_key
AUTHSIGNAL_API_URL=https://api.authsignal.com/v1

‍

Start the applications:

npm run dev -w api      # Express server on port 4000
npm run dev -w web      # Vite dev server on port 5173

cd apps/mobile && npx expo run:ios

‍

Open the web app, enter a user ID, and tap "Enroll Device" on the mobile app first. After that, clicking "Sign In" on the web will surface an approval screen on the phone. Approve it, and the web app shows the result.

The full source code is available on GitHub.

What's next

This covers simple approve/deny push. In the next post, we’ll cover number matching: a short code shown on the web that the user must confirm on their phone before the approval goes through. This prevents push fatigue attacks, where an attacker repeatedly sends approval requests hoping the user taps "Approve" by mistake. It's the vector used in the Uber breach in 2022. Number matching is also required for financial transaction authorization under FCA/SCA.

Question icon
Have a question?
Talk to an expert
NewsletterDemo PasskeysView docs
Push authentication
React native
Node.js
Multi-factor authentication
Guides

You might also like

BSP Circular 1213: Philippine banks must replace SMS OTPs by June 2026
BSP Circular 1213
Philippine banking
SMS OTP
Risk based authentication

BSP Circular 1213: Philippine banks must replace SMS OTPs by June 2026

March 18, 2026
How to add adaptive MFA and passkeys to any web app with Authsignal and Lambda@Edge
AWS
Authentication
Security

How to add adaptive MFA and passkeys to any web app with Authsignal and Lambda@Edge

March 10, 2026
The real cost of building authentication in-house
Build vs. Buy
Authentication

The real cost of building authentication in-house

March 27, 2026

Secure your customers’ accounts today with Authsignal

Passkey demoCreate free account

Authsignal delivers passwordless and multi-factor authentication as a service. Focused on powering mid-market and enterprise businesses to rapidly deploy optimized good customer flows that enable a flexible and risk-based approach to authentication.

AICPA SOCFido Certified
LinkedInTwitter
Passwordless / multi-factor authentication (MFA)
Pre-built UI (low code)UI components (customizable)Custom UI (flexible)
Why Authsignal?
Drop-in authentication
Risk-based authentication PasskeysBiometric authenticationWhatsApp OTPSMS OTPEmail OTPMagic linksAuthenticator apps (TOTP)Push authenticationPalm biometricsDigital Credential Verification API
Rules and policies engine
User observability
Industries
Financial services
Marketplace
e-Commerce
FinTech
Crypto
View all industries
Teams
Engineers
Use cases
Account takeovers (ATO)
Go passwordless
Call center
SMS cost optimization
Existing apps
View all use cases
Identity providers (IDPs)
Amazon Cognito
Auth0
Azure AD B2C
Custom identity provider
Duende IdentityServer
Keycloak
NextAuth.js
Integrations
ASP.NET
C#
Java
Node.js
Open ID Connect (OIDC)
PHP
Python
React
Ruby
Ruby on Rails
Compare
Twilio Verify vs AuthsignalAuth0 vs AuthsignalAWS Cognito vs Authsignal + AWS Cognito
Resources
BlogDeveloper docsFree Figma mobile passkeys templateFree Figma desktop passkeys templateFree Figma webapp passkeys template
Company
About usWhy AuthsignalCareersPress releasesPartnersContact us
What is
SMS OTP
Risk Based Authentication
IP Spoofing
Passwordless authentication
Multi-Factor Authentication (MFA)
United States
+1 214 974-4877
Ireland
+353 12 676529
Australia
+61 387 715 810
New Zealand
+64 275 491 983
© 2026 Authsignal - All Rights Reserved
Terms of servicePrivacy policySecuritySystem statusCookies