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
Duende IdentityServer
Passkeys
Passkeys implementation
Passwordless authentication
Guides

How to Add Passkeys to Duende IdentityServer with Authsignal

Ashutosh Bhadauriya
⬤
May 14, 2025
Share
How to Add Passkeys to Duende IdentityServer with Authsignal

In our previous guide, we saw how you can implement multi-factor authentication (MFA) in Duende IdentityServer using Authsignal. Now, let's take your authentication system to the next level by adding passkey support.

Passkeys offer significant advantages over traditional authentication methods:

  • Improved security - Resistant to phishing and credential theft
  • Enhanced user experience - No passwords to remember or type
  • Reduced friction - Faster logins with biometric verification

Let's see how we can implement this powerful authentication method in your existing setup.

‍

Repository Structure

For your convenience, we've published the complete source code with all implementation details on our GitHub repository.

The solution continues to use the two projects from our previous guide:

  • IdentityServer — The authentication server that handles login requests and passkey challenges
    • Runs on https://localhost:5001
    • Contains the Duende IdentityServer core and Authsignal integration
  • WebClient — A sample client application protected by our enhanced authentication
    • Runs on https://localhost:5002
    • Now includes passkey enrollment capabilities

The code samples in this guide are extracted directly from this implementation.

‍

Enrolling a Passkey

Once a user has logged in with MFA (as we set up in part 1), we can use the Authsignal Web SDK to allow them to add a passkey. This enrollment process will take place in the application server (i.e., the WebClient).

‍

‍

Adding a Passkey Enrollment Page

First, we'll create a dedicated page for passkey management in our WebClient application. Let's implement this by adding a new page:

‍

// From /src/WebClient/Pages/Passkeys.cshtml

@page
@model PasskeysModel

@Html.HiddenFor(m => m.enrollmentToken)

<button id="add-passkey" onClick="addPasskey()">Add a passkey</button>

<script src="https://unpkg.com/@authsignal/browser@0.3.0/dist/index.min.js"></script>
<script src="~/js/passkeys.js"></script>

This page contains a button that will trigger the passkey enrollment process.The hidden field stores the enrollmentToken that our server creates when the page loads.

‍

Generating the Enrollment Token

The enrollmentToken is fetched server-side when the page loads:

// From /src/WebClient/Pages/Passkeys.cshtml.cs 

public async Task<IActionResult> OnGet()
{
  var trackRequest = new TrackRequest(
    UserId: User.Claims.First(x => x.Type == "sub").Value!,
    Action: "add-passkey",
    Attributes: new TrackAttributes(
      Scope: "add:authenticators"
    )
  );

  var trackResponse = await _authsignal.Track(trackRequest);

  this.enrollmentToken = trackResponse.Token;

  return Page();
}

This code:

  • Extracts the user's ID from their claims
  • Creates a track request with the action "add-passkey"
  • Sets a special scope "add" to indicate we're enrolling a new authenticator
  • Stores the resulting token in a property that will be passed to the view
Important note: The enrollment token is only valid for 10 minutes. If a user stays on the passkey enrollment page for longer than that without taking action, the "Add a passkey" button will fail. In a production application, you might want to implement token refresh functionality or provide clear messaging about this limitation.

‍

Client-Side Implementation

Finally, we need to add the client-side JavaScript implementation for the addPasskey function.

‍

// From /src/WebClient/wwwroot/js/passkeys.js

function addPasskey() {
  var client = new window.authsignal.Authsignal({
    tenantId: "YOUR_TENANT_ID",
    baseUrl: "https://api.authsignal.com/v1", // Update for your region
  });

  var token = document.getElementById("enrollmentToken").value;

  client.passkey.signUp({ token }).then((resultToken) => {
    if (resultToken) {
      alert("Passkey added");
    }
  });
}

This:

  • Initializes the Authsignal client with your tenant ID and region
  • Retrieves the enrollment token from the hidden field
  • Calls the passkey.signUp method to trigger the browser's passkey creation flow
  • Shows a simple alert when the passkey is successfully enrolled

‍

Logging in with a Passkey

Now that users can enroll passkeys, we need to update our login page to support passkey authentication. This enhancement gives users the option to use their passkey instead of typing a username and password.

‍

Updating the Login Form

First, we need to ensure that the username input field has the correct autocomplete attribute to support passkey authentication. Modify your login form:

// From /src/IdentityServer/Pages/Account/Login/Index.cshtml

<input
  id="passkey-username"
  class="form-control"
  placeholder="Username"
  asp-for="Input.Username"
  autocomplete="username webauthn"
  autofocus
/>

The critical addition here is autocomplete="username webauthn", which tells browsers that this field supports WebAuthn (the underlying technology for passkeys).

‍

Adding Passkey Authentication JavaScript

Next, we need to add JavaScript to initialize passkey authentication when the login page loads:

// From src/IdentityServer/wwwroot/js/login.js

function initPasskeyAutofill() {
  var client = new window.authsignal.Authsignal({
    tenantId: "YOUR_TENANT_ID",
    baseUrl: "https://api.authsignal.com/v1", // Update for your region
  });

  client.passkey.signIn({ autofill: true }).then((token) => {
    if (token) {
      var returnUrl = document.getElementById("Input_ReturnUrl").value;

      window.location = `https://localhost:5001/Account/Login/Callback?returnUrl=${returnUrl}&token=${token}`;
    }
  });
}

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", initPasskeyAutofill);
} else {
  initPasskeyAutofill();
}

This:

  • Initializes the Authsignal client
  • Calls passkey.signIn with autofill: true to enable the browser's passkey selection
  • When a passkey is successfully used, redirects to our callback endpoint with the resulting token
  • Includes logic to ensure it runs after the DOM is loaded

Don't forget to include this script and the Authsignal SDK in your login page:

<script src="https://unpkg.com/@authsignal/browser@0.3.0/dist/index.min.js"></script>
<script src="~/js/passkey-login.js"></script>

‍

‍

Handling the Passkey Authentication Result

We don't need to modify our callback handler from part 1. The validation process is the same whether the user authenticates with MFA or a passkey. In both cases:

  1. We receive a token (either from the redirect after MFA challenge or directly from the passkey authentication)
  2. We pass this token to our callback page
  3. The callback validates the token using Authsignal's ValidateChallenge method
  4. If validation succeeds, we log the user in

The callback handler we implemented in part 1 already handles this flow perfectly:

// From /src/IdentityServer/Pages/Account/Login/Callback.cshtml.cs

public async Task<IActionResult> OnGet(string returnUrl, string token)
{
  // Decode the Base64-encoded returnUrl
  var decodedReturnUrl = Encoding.UTF8.GetString(Convert.FromBase64String(returnUrl));
  
  if (token == null)
  {
    return Redirect("https://localhost:5001/Account/Login?ReturnUrl=" + decodedReturnUrl);
  }
  
  // Validate the challenge token from Authsignal
  var validateChallengeRequest = new ValidateChallengeRequest(token);
  var validateChallengeResponse = await _authsignal.ValidateChallenge(validateChallengeRequest);
  var userId = validateChallengeResponse.UserId;
  var user = _users.FindBySubjectId(userId);
  
  if (validateChallengeResponse.State != UserActionState.CHALLENGE_SUCCEEDED)
  {
    return Redirect("https://localhost:5001/Account/Login?ReturnUrl=" + decodedReturnUrl);
  }
  
  // Issue authentication cookie and redirect to the original URL
  var isuser = new IdentityServerUser(user.SubjectId) { DisplayName = user.Username };
  await HttpContext.SignInAsync(isuser);
  
  return Redirect(decodedReturnUrl);
}

‍

User Experience

Congrats! Your users now have two ways to log in:

  1. Traditional flow: Enter username and password, then complete MFA challenge
  2. Passkey flow: Select their passkey when focusing on the username field, authenticate with their device (via fingerprint, face recognition, etc.), and skip both password entry and MFA

The passkey flow significantly reduces friction while maintaining strong security, providing the best of both worlds.

‍

Wrapping Up

That's it! You've now enhanced your Duende IdentityServer implementation with passkey support, providing a more secure and user-friendly authentication experience. Your users can enjoy passwordless login while maintaining strong security.

By combining MFA (from part 1) with passkeys, you've created a more secure authentication system that protects against most common attacks while reducing friction for your users.

The complete code for this implementation is available on GitHub.

In the next part of this series, we'll explore how you can use Authsignal to implement a complete passwordless login flow with email OTP and passkeys to your IdentityServer. Stay tuned!

Question icon
Have a question?
Talk to an expert
NewsletterDemo PasskeysView docs
Duende IdentityServer
Passkeys
Passkeys implementation
Passwordless authentication
Guides

You might also like

How a global real estate company strengthened MFA with Authsignal
Azure AD B2C
Multi-factor authentication
Passkeys

How a global real estate company strengthened MFA with Authsignal

April 14, 2026
What is Visa VAMP? Thresholds, fees, and how it affects your dispute ratio
Visa VAMP
Chargebacks
Dispute Management

What is Visa VAMP? Thresholds, fees, and how it affects your dispute ratio

April 13, 2026
Authsignal joins IATA Strategic Partnership Program to advance digital identity adoption in travel and aviation
Partnerships
Airlines

Authsignal joins IATA Strategic Partnership Program to advance digital identity adoption in travel and aviation

April 10, 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