Passwords create problems for both users and developers. Users forget them, reuse them across websites, and sometimes enter them on phishing pages. Applications must also store password hashes, enforce password policies, and handle password resets.
Passkeys offer another way to authenticate users. They use public-key cryptography through the Web Authentication API (WebAuthn). A user's device or password manager protects the private key, while the application stores the corresponding public key.
ASP.NET Core Identity includes passkey support in .NET 10 and later. It provides APIs for registering credentials and verifying sign-in requests without requiring developers to implement the WebAuthn protocol from scratch.
This article explains how passkeys work, how to configure ASP.NET Core Identity, and what to test before enabling passwordless sign-in in a production application.
What Is Passkey Authentication?
A passkey is a cryptographic credential associated with a website or application. During registration, the authenticator creates a key pair. The private key stays protected by the authenticator, while the application stores the public key and credential information.
When a user signs in, the application sends a challenge to the browser. The authenticator signs the challenge with the private key, and the server verifies the response using the stored public key.
A passkey can use Windows Hello, Face ID, Touch ID, a security key, or a compatible password manager. The exact experience depends on the device and browser.
Passkeys provide phishing resistance because credentials are bound to the website's relying-party identity. A fake website cannot normally use a passkey registered for the legitimate domain.
Authentication method | What the server stores | Common risk |
|---|
Password | Password hash and account metadata | Phishing, password reuse, and credential stuffing |
One-time code | Account information and verification state | Codes can be intercepted or phished |
Passkey | Public key and credential metadata | Account recovery and credential-management mistakes |
Passkeys reduce the risks associated with passwords, but they do not eliminate every authentication risk. Applications still need secure sessions, account recovery, access controls, and protection against compromised devices.
1. Create an ASP.NET Core Identity Application
Use .NET 10 or later and a modern browser that supports WebAuthn. HTTPS is required for passkey operations in production.
If you are starting a new project, create a Blazor Web App with individual accounts:
dotnet new blazor -au Individual -o PasskeyDemo
cd PasskeyDemo
The Blazor Web App template includes Identity integration and passkey support. This is the simplest starting point because it already provides much of the registration and sign-in flow.
Run the application:
dotnet run
Open the HTTPS address shown in the terminal. Register a test account and inspect the account-management pages to understand the existing authentication flow.
For an existing MVC, Razor Pages, Minimal API, or custom Identity application, check the current framework and Identity configuration before adding passkey support. The built-in APIs are designed for ASP.NET Core Identity authentication scenarios, not as a general-purpose WebAuthn implementation for every authentication system.
2. Configure the Passkey Domain
A passkey is tied to a relying-party identifier, usually the domain used by the application.
For example, an application hosted at app.example.com might use example.com as its relying-party identifier when that domain arrangement is appropriate. The browser enforces the relationship between the passkey and the website.
Configure the intended domain explicitly:
using Microsoft.AspNetCore.Identity;
builder.Services.Configure<IdentityPasskeyOptions>(options =>
{
options.ServerDomain = "example.com";
options.AuthenticatorTimeout = TimeSpan.FromMinutes(3);
options.ChallengeSize = 32;
});
Replace example.com with the real domain used by the application. Do not deploy this example unchanged.
The ServerDomain setting controls the relying-party identifier. AuthenticatorTimeout sets the time allowed for the authenticator operation, while ChallengeSize controls the challenge size in bytes.
Use the same intended domain consistently across registration and authentication. If the domain changes, existing passkeys may no longer work.
For applications behind a reverse proxy, configure forwarded headers and allowed hosts correctly. If the relying-party identifier is inferred from the request host, untrusted host headers can create security problems.
3. Understand the Passkey Registration Flow
Registration creates a credential and associates it with an authenticated user account.
The process has several steps:
The signed-in user selects the option to add a passkey.
The server creates registration options containing a unique challenge.
The browser passes those options to the WebAuthn API.
The authenticator creates a credential and returns the response.
The server verifies the response and stores the credential.
The challenge prevents an attacker from reusing an old registration response. The server must verify the challenge, origin, credential data, and other required WebAuthn fields before saving the public key.
ASP.NET Core Identity provides methods such as MakePasskeyCreationOptionsAsync and PerformPasskeyAttestationAsync for this flow. The registration process also needs to preserve the temporary attestation state securely between requests.
For most applications, use the passkey methods on SignInManager<TUser> and UserManager<TUser> rather than implementing the protocol manually. The Identity APIs manage the temporary state required for the two-request process.
A complete browser registration flow also requires JavaScript to call navigator.credentials.create() and send the credential response back to the server. A server endpoint alone is not enough to register a working passkey.
4. Register a Passkey With ASP.NET Core Identity
In a custom Identity application, the server needs to generate creation options, return them to the browser, validate the submitted credential, and save the verified passkey.
The core registration operations look like this:
var optionsJson =
await signInManager.MakePasskeyCreationOptionsAsync(user);
// Return optionsJson to the browser.
// The browser calls navigator.credentials.create().
var attestationResult =
await signInManager.PerformPasskeyAttestationAsync(credentialJson);
if (!attestationResult.Succeeded)
{
return Results.BadRequest(
new { error = "Passkey registration failed." });
}
await userManager.AddOrUpdatePasskeyAsync(
user,
attestationResult.Passkey);
return Results.Ok();
Here, user is the Identity user whose account is being updated, and credentialJson is the serialized response returned by the browser.
This snippet illustrates the main server operations, not a complete endpoint. The application must implement the browser request, protect the attestation state between requests, validate the authenticated user's permissions, and handle errors appropriately.
Do not call the low-level passkey handler directly unless you understand its state-management requirements. If you bypass the Identity sign-in manager, your application becomes responsible for safely storing, validating, and consuming the temporary registration state.
5. Implement Passkey Sign-In
Passkey sign-in follows a similar process, but the authenticator proves possession of an existing private key.
The server generates authentication options and a fresh challenge. The browser passes those options to navigator.credentials.get(), and the authenticator returns a signed assertion.
ASP.NET Core Identity provides PasskeySignInAsync to verify the assertion and sign the user in.
var result =
await signInManager.PasskeySignInAsync(credentialJson);
if (result.Succeeded)
{
return Results.Ok(
new { message = "Sign-in successful." });
}
return Results.Unauthorized();
The credentialJson value comes from the browser's WebAuthn response. The server verifies it against the stored credential and the expected challenge before establishing the authenticated session.
This example assumes the authentication options were generated earlier through the corresponding Identity API. The application must also handle expired challenges, failed verification, rate limits, and other sign-in conditions.
Do not accept a credential response simply because the browser returned JSON. The server must perform the cryptographic and protocol checks.
6. Protect Passkeys in Production
Passkeys reduce reliance on passwords, but the surrounding application still needs careful security configuration.
Require HTTPS
Use HTTPS for all production passkey operations. Configure HTTP Strict Transport Security where appropriate and ensure that reverse proxies preserve the correct scheme and host information.
Validate the origin and domain
Set ServerDomain explicitly when practical. If the application serves untrusted content on subdomains within the relying-party scope, review the origin-validation requirements carefully.
Plan account recovery
Users can lose devices or access to a password manager. Provide a secure recovery method, such as recovery codes, a verified backup authentication method, or a process for registering another passkey.
Recovery must not become an easier route for an attacker to take over an account. Apply appropriate identity verification and notification controls.
Limit credential registration
Set a reasonable limit on the number of passkeys associated with each account. Validate display-name length and treat user-provided labels as untrusted input.
Protect authentication state
Use the built-in Identity flow where possible. Registration and authentication challenges must be tied to the correct user and operation, protected against modification, and consumed only once.
Common Mistakes to Avoid
Assuming passkeys require no account recovery
A user can lose access to a device or credential provider. Without a recovery plan, legitimate users may lose access to their accounts.
Using the wrong relying-party domain
The domain used during registration must match the one expected during authentication. Test the actual production hostname and any intended subdomains.
Implementing WebAuthn verification manually
WebAuthn involves challenge validation, origin checks, credential verification, and other protocol requirements. Use the built-in Identity APIs unless your application has a specific need for a separate WebAuthn library.
Treating passkeys as a second factor automatically
ASP.NET Core Identity treats passkeys as a primary authentication method in its built-in implementation. If the application requires a separate multi-factor authentication policy, design and test that policy explicitly.
Best Practices
Use ASP.NET Core Identity's built-in passkey support when it fits the application.
Configure the relying-party domain explicitly.
Use HTTPS and validate host and origin information.
Keep registration and authentication challenges short-lived and single-use.
Provide secure account recovery and encourage users to register backup passkeys.
Limit the number of credentials associated with an account.
Test registration, sign-in, cancellation, expiration, and recovery across supported browsers and devices.
Monitor failed authentication attempts without logging sensitive credential material.
Advantages and Disadvantages
Advantages | Disadvantages |
|---|
Resistant to common phishing attacks | Requires compatible browsers and authenticators |
The server stores public keys instead of passwords | Account recovery needs careful design |
Reduces password-reset and password-reuse problems | Domain changes can affect existing credentials |
Can provide a simple sign-in experience | Custom authentication systems may need additional integration work |
Troubleshooting Common Problems
The browser cannot create a passkey
Check that the application uses HTTPS, the browser supports WebAuthn, and the registration options contain the correct relying-party identifier.
Registration succeeds but sign-in fails
Verify that the credential was saved for the correct Identity user and that authentication uses the same domain. Check challenge-state handling and the server's validation results.
Passkeys stop working after a domain change
Review the relying-party identifier and the hostname used during registration. A domain migration may require users to register new credentials under the new domain.
Users lose access to their accounts
Review the recovery process and backup authentication methods. Test recovery before making passkeys the only supported sign-in method.
Summary
Passkeys provide a passwordless authentication option for ASP.NET Core applications through the WebAuthn standard. ASP.NET Core Identity in .NET 10 and later includes APIs for registration and authentication, which can reduce the amount of protocol code an application needs to maintain.
The main implementation work involves configuring the relying-party domain, securely managing registration and authentication challenges, storing verified public credentials, and establishing a normal authenticated session.
Before enabling passkeys for production users, test the full browser flow, HTTPS configuration, account recovery, and credential management. A passkey implementation is only as reliable as the security controls around it.
Join the conversation! Your thoughts help the community grow.