Platform
Platform
Drop-In Authentication
Passkeys
Phishing-resistant, FIDO2/WebAuthn
Biometric authentication
Face and device-native biometrics
WhatsApp OTP
Global reach, lower cost than SMS
Email OTP
Universal second factor
App push authentication
Native device push notifications
Palm biometrics
Contactless identity verification
All authentication methods
KEY FEATURES
No-code rules engine
Step-up auth, risk policies, alerts
User observability
Audit trails, dynamic linking
Risk-based authentication
Adaptive MFA by context
Session management
Persistent sessions across channels
Digital credentials API
Verify sovereign digital IDs
Pre-built UI
Fastest deployment path
Passkeys
Deploy phishing-resistant passkeys across web and mobile in days
Solutions
Solutions
USE CASE
Account Takeover Prevention
Stop ATO without adding friction
Go Passwordless
Replace passwords with passkeys
Account Recovery
Secure self-service recovery flows
Call Centre Authentication
Verify callers without KBA
SMS Cost Optimisation
Cut OTP costs up to 90%
Existing Apps (Drop-In)
Add auth without re-architecting
Palm biometric payments
Contactless in-store payments
INDUSTRY
Financial Services & Banking
Secure high-value transactions
Healthcare
Authentication for ePHI access
Loyalty Programs
Protect points and member accounts
ROLE
Engineering
Fast integration, flexible SDKs
Product
No-code flows, conversion insights
Cybersecurity
Risk policies, compliance, audit trails
PricingCustomers
Resources
Resources
LEARN
Blog
Guides
Regulatory
Templates
Docs
COMPANY
About Us
Why Authsignal
Partners
Careers
Security
Contact
INTEGRATIONS
Amazon Cognito
Auth0
Azure AD B2C
Custom identity provider
Duende IdentityServer
All integrations
Docs
Start a trial
Book a callLogin
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
Passkeys
Step up authentication
Implementation

How to add passkey step-up auth in your app?

Ashutosh Bhadauriya
⬤
June 10, 2025
Share
How to add passkey step-up auth in your app?

You must have come across a moment when you're in your banking app, about to transfer a large amount of money, and suddenly it asks you to verify your identity again. Maybe it sends you an SMS code, asks for your password, or prompts for additional security questions. That's step-up authentication in action.

The idea is simple: routine actions get minimal friction, but sensitive operations require extra verification. It's a smart security pattern that balances usability with protection.But traditional step-up methods have problems. SMS codes can be intercepted. Passwords get forgotten or stolen. Security questions are often terrible. Users end up frustrated, and security teams worry about vulnerabilities.

Passkeys offer a better way. They're fast, secure, and users already understand the interaction. Instead of typing codes or remembering passwords, users just touch their fingerprint or look at their phone. The user experience is smooth, and there's nothing for attackers to steal.

‍

What we're building

Let's build a practical example: a money transfer app that uses passkeys for step-up authentication. Here's how it works:

  • Small transfers (under $5,000): go through immediately
  • Large transfers (over $5,000): require passkey verification

The user experience feels natural. Small transfers are frictionless. Large transfers get one quick biometric check.

‍

Architecture overview

Before we start coding, let's understand how the pieces fit together. We’ll be using Authsignal for our passkey step-auth implementation. The app has four main components:

Passkey enrollment: This is where users register their biometric with your app.

Transaction check: Every time someone initiates a transfer, we evaluate whether it needs extra verification. This is where your business logic lives. Amount thresholds, user risk levels, recipient verification - whatever rules make sense for your application.

Step-Up Challenge: When extra verification is needed, we present the passkey prompt.

Validation: After the user completes the passkey flow, we need to verify on the server that it actually happened. This prevents client-side tampering and gives us an audit trail.

‍

Github repo

All the code for this implementation is available in our GitHub repository. You can clone it, run it locally, and adapt it for your own use case.

This implementation uses Next.js, but no worries if that's not your stack. We have SDKs for Python, Ruby, Go, PHP, and other platforms that follow the same approach with language-specific examples. Head to our docs for examples in your preferred language.

‍

Let’s get to building

First, we need to configure Authsignal in our application.

‍

Install the required packages:

npm install @authsignal/node @authsignal/browser

‍

Set up your configuration:

// lib/authsignal.ts
import { Authsignal } from '@authsignal/node'

export const authsignal = new Authsignal({
  apiSecretKey: process.env.AUTHSIGNAL_API_SECRET_KEY,
  apiUrl: 'https://api.authsignal.com/v1', // your region api url
})

export const authsignalConfig = {
  tenantId: process.env.NEXT_PUBLIC_AUTHSIGNAL_TENANT_ID,
  baseUrl: 'https://api.authsignal.com/v1', // your region api url
}

‍

The server-side config handles authentication and API calls to Authsignal. The client-side config allows your frontend to communicate with Authsignal's browser SDK.

‍

Passkey enrollment

Before users can verify high-value transactions, they need to enroll a passkey. This is a one-time setup that registers their biometric authentication method with your application.

The enrollment process has three steps: request permission, create the passkey, and validate success. Let’s build each part

‍

Backend enrollment endpoint:

// api/passkey/enroll/route.ts
export async function POST(request: NextRequest) {
  const userId = await getUserId()

// Tell Authsignal we want to start passkey enrollment for this user
  const response = await authsignal.track({
    userId,
    action: 'enroll-passkey',
    attributes: { scope: 'add:authenticators' },
  })

// Return a token that authorizes the frontend to proceed
  return NextResponse.json({ token: response.token })
}

‍

Only generate tokens with the add:authenticators scope in strongly authenticated contexts. Generating this token from an unauthenticated endpoint creates a security vulnerability where attackers could potentially enroll authenticators for other users.

Frontend enrollment flow:

// components/passkey-manager.tsx
const enrollPasskey = async () => {
  try {
// Step 1: Get permission to enroll
    const challengeResponse = await fetch('/api/passkey/enroll', {
      method: 'POST',
      body: JSON.stringify({ userId }),
    })
    const { token } = await challengeResponse.json()

// Step 2: Create the passkey using browser APIs
    const passkeyResponse = await authsignalClient.passkey.signUp({
      token,
      username: userEmail,
      displayName: userEmail. // can skip it, if both userName and displayName are same
    })

// Step 3: Validate that enrollment succeeded
   if (passkeyResponse.data?.token) {
        // Step 3: Validate the passkey creation on server
        const validationResponse = await fetch('/api/passkey/validate', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ token: passkeyResponse.data.token }),
        })

        if (validationResponse.ok) {
          setIsEnrolled(true)
          setMessage({ type: 'success', text: 'Passkey successfully enrolled!' })
        } else {
          throw new Error('Failed to validate passkey')
        }
      }
    } catch (error) {
      console.error('Passkey enrollment error:', error)
      setMessage({ 
        type: 'error', 
        text: error instanceof Error ? error.message : 'Failed to enroll passkey' 
      })
  }
}

Here's what happens from the user's perspective: they click "Set up passkey," their device prompts for Touch ID or Face ID, and then they see a success message.

The username and displayName parameters help users identify the passkey later if they manage multiple accounts. Use email addresses or usernames that users will recognise.

‍

Transaction verification check

Decide when to require step-up auth based on transfer amount:

// api/passkey/verify-transaction/route.ts
export async function POST(request: NextRequest) {
	const userId = await getUserId()
  const { amount, recipient } = await request.json()

// Small transfers go through
  if (amount <= 5000) {
    return NextResponse.json({
      requiresVerification: false,
      message: 'Transaction approved'
    })
  }

// Large transfers need verification
  const response = await authsignal.track({
    userId,
    action: 'high-value-transfer',
  })

  if (response.state === 'CHALLENGE_REQUIRED') {
    return NextResponse.json({
      requiresVerification: true,
      token: response.token,
      state: response.state,
    })
  }
}

‍

Step-up challenge

When verification is needed, prompt for passkey:

// components/passkey-challenge.tsx
const performPasskeyVerification = async () => {
  try {
    const passkeyResponse = await authsignalClient.passkey.signIn({
      action: 'high-value-transfer'
    })

    if (passkeyResponse.data?.token) {
      const validationResponse = await fetch('/api/passkey/validate', {
        method: 'POST',
        body: JSON.stringify({ token: passkeyResponse.data.token }),
      })

      const validationData = await validationResponse.json()

      if (validationData.isValid) {
        onVerificationComplete(true, 'Verification successful')
      }
    }
  } catch (error) {
    if (error.message.includes('user_canceled')) {
      onVerificationComplete(false, 'Verification cancelled')
    } else if (error.message.includes('no_credential')) {
      onVerificationComplete(false, 'No passkey found on this device')
    }
  }
}

The action parameter connects the server challenge to the client verification.

‍

Validation

Confirm that passkey verification actually happened:

// api/passkey/validate/route.ts
export async function POST(request: NextRequest) {
  const { token } = await request.json()

  const response = await authsignal.validateChallenge({ token })

  return NextResponse.json({
    state: response.state,
    isValid: response.state === 'CHALLENGE_SUCCEEDED',
    userId: response.userId,
  })
}

‍

Complete flow

High-value transfer ($7,500):

  1. User enters transfer details
  2. App detects high value, calls verification endpoint
  3. Server responds with challenge token
  4. App prompts for passkey
  5. User provides biometric
  6. App validates token with server
  7. Transfer proceeds

Low-value transfer ($50):

  1. User enters transfer details
  2. App detects low value
  3. Transfer proceeds immediately

We recommend only enrolling passkeys after users have already enrolled with another authentication factor (like email OTP, SMS, or authenticator app).

‍

Extensions

You can use this pattern for any sensitive action:

  • Password changes
  • Data exports
  • Admin operations
  • Account deletions

You can also add risk-based triggers beyond just amount thresholds, like unusual locations or recipient verification.

‍

Wrapping up

Implementing passkey-based step-up authentication might seem complex, but with Authsignal handling the heavy lifting, it's pretty straightforward. The combination of strong security and excellent user experience makes passkeys ideal for protecting high-value transactions.

The key is to start simple, implement basic passkey enrollment and verification, then gradually add sophistication based on your users' needs. With the foundation we've built here, you're well on your way to providing bank-grade security for your most critical operations.

Ready to implement passkeys in your own application? Check out the Authsignal documentation and start building!

Question icon
Have a question?
Talk to an expert
NewsletterDemo PasskeysView docs
Passkeys
Step up authentication
Implementation

You might also like

The passkey UX patterns that drive adoption in 2026
Passkeys
Passkeys implementation

The passkey UX patterns that drive adoption in 2026

July 2, 2026
What is silent network authentication, and how does it work?
Silent Network Authentication
SNA

What is silent network authentication, and how does it work?

June 26, 2026
HIPAA MFA requirements and what to do before the final rule lands
Passkeys
Step up authentication
Adaptive MFA
SMS Alternative

HIPAA MFA requirements and what to do before the final rule lands

July 4, 2026

Secure your customers’ accounts today with Authsignal

Passkey demoCreate free account
Authsignal Purple Logo

Authsignal is a drop-in authentication and orchestration layer for consumer-facing businesses. Passkeys, adaptive MFA, and omnichannel verification on top of your existing identity stack. Deployed in weeks, not quarters.

AICPA SOCFido Certified
LinkedInTwitter
Platform
Drop-In Authentication
PasskeysBiometric authenticationWhatsApp OTPEmail OTPApp push authenticationPalm biometricsSMS OTPEmail OTPMagic LinksAuthenticator apps (TOTP)
View all auth methods
KEY FEATURES
No-code rules engineUser observabilityRisk-based authenticationSession managementDigital credentials APIPre-built UI
All authentication methods
Pricing
Customers
Solutions
USE CASE
Account takeovers (ATO)
Go passwordless
Call center
SMS cost optimization
Existing apps
Palm biometric payments
View all use cases
industry
Financial services
Healthcare
Marketplace
View all use cases
ROLE
Engineering
ProductCybersecurityDocs
Compare
Twilio Verify vs AuthsignalAuth0 vs AuthsignalAWS Cognito vs Authsignal + AWS Cognito
Resources
LEARN
BlogGuidesRegulatoryTemplatesDocs
COMPANY
About usWhy AuthsignalPartnersCareersSecurityContact
Integrations
Amazon Cognito
Azure AD B2C
Duende IdentityServer
Keycloak
Auth0
NextAuth.js
Custom identity provider
All integrations
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