Why Go Passwordless in 2026?
Passwords are the weakest link in consumer authentication. Over 80% of data breaches involve stolen or weak credentials. In 2026, users expect seamless, frictionless login — and passwordless auth delivers exactly that while simultaneously raising your security baseline.
The case for passwordless B2C authentication is stronger than ever:
- No credential stuffing attacks. There are no passwords to steal, reuse, or crack across breached databases.
- Zero password reset tickets. Forgotten passwords are the #1 driver of auth-related support volume — eliminate the problem entirely.
- Higher conversion rates. Frictionless login reduces sign-up and sign-in drop-off — particularly on mobile where typing passwords is painful.
- Phishing resistance. Passkeys are origin-bound and cryptographically phishing-resistant. Magic links are single-use and link-scoped.
- Universal platform support. In 2026, passkeys are supported natively on all major browsers, iOS 16+, Android 9+, macOS Ventura+, and Windows 11.
Magic Links
A magic link is a one-time, time-limited URL sent to the user's email address. Clicking it proves possession of that inbox and signs the user in immediately — no password field, no code to copy.
How Magic Links Work
- User enters their email address on your login page.
- Your server generates a cryptographically random single-use token and stores a hash of it (with expiry) in the database.
- The signed URL (containing the raw token) is emailed to the user.
- User clicks the link; your server validates the token hash, checks expiry, marks it as used, and issues a session.
- Token is deleted after first use — replay attacks are impossible.
Magic Link Security Requirements
- RequiredToken must be at least 128 bits of cryptographic randomness
- RequiredStore only the SHA-256 hash of the token — never the raw token
- RequiredToken expiry must be short: 15–30 minutes maximum
- RequiredSingle-use: delete the record immediately after first successful use
- RecommendedBind token to the requesting IP address or user-agent for extra replay protection
- RecommendedRate-limit magic link generation: max 5 requests per email address per hour
Magic Link Implementation (.NET)
// Generate a time-limited, single-use magic link token
public async Task<string> GenerateMagicLinkAsync(string email)
{
var rawToken = Convert.ToBase64String(
System.Security.Cryptography.RandomNumberGenerator.GetBytes(32));
var tokenHash = Convert.ToBase64String(
System.Security.Cryptography.SHA256.HashData(
System.Text.Encoding.UTF8.GetBytes(rawToken)));
await _db.MagicLinkTokens.AddAsync(new MagicLinkToken
{
Email = email,
TokenHash = tokenHash,
ExpiresAt = DateTime.UtcNow.AddMinutes(20),
Used = false
});
await _db.SaveChangesAsync();
return $"https://auth.ailacs.com/magic?token={Uri.EscapeDataString(rawToken)}"
+ $"&email={Uri.EscapeDataString(email)}";
}
// Validate the token when the user clicks the link
public async Task<bool> ValidateMagicLinkAsync(string email, string rawToken)
{
var tokenHash = Convert.ToBase64String(
System.Security.Cryptography.SHA256.HashData(
System.Text.Encoding.UTF8.GetBytes(rawToken)));
var record = await _db.MagicLinkTokens.FirstOrDefaultAsync(t =>
t.Email == email
&& t.TokenHash == tokenHash
&& !t.Used
&& t.ExpiresAt > DateTime.UtcNow);
if (record is null) return false;
record.Used = true;
await _db.SaveChangesAsync();
return true;
}
Magic links depend entirely on email deliverability. Always provide a fallback (TOTP or a one-time code from the same email) for users who have deliverability issues.
Passkeys & WebAuthn / FIDO2
Passkeys are the gold standard for passwordless authentication in 2026. Built on the WebAuthn (W3C) and FIDO2 standards, passkeys use public-key cryptography tied to a user's device — and optionally synced across iCloud Keychain, Google Password Manager, or a hardware security key.
How Passkeys Work
Registration:
- Your server sends a challenge (random bytes) to the browser.
- The browser calls
navigator.credentials.create(), invoking the device authenticator (Face ID, Touch ID, Windows Hello, or a hardware key). - The authenticator generates a new public/private key pair scoped to your origin (
auth.ailacs.com). The private key never leaves the device. - The browser returns the public key + attestation to your server, which stores it against the user account.
Authentication:
- Server sends a new random challenge.
- Browser calls
navigator.credentials.get()— device prompts for biometric/PIN verification. - Authenticator signs the challenge with the stored private key.
- Server verifies the signature against the stored public key — identity confirmed, no password required.
Passkey Platform Support in 2026
| Platform | Sync Support | Hardware Key | Notes |
|---|---|---|---|
| Chrome / Edge (Desktop) | ✅ Google Password Manager or iCloud | ✅ FIDO2 USB/NFC | Full WebAuthn Level 3 |
| Safari (macOS / iOS 16+) | ✅ iCloud Keychain | ✅ | Best UX on Apple ecosystem |
| Firefox | ⚠️ Limited sync (no native store) | ✅ | Works, but no built-in passkey sync |
| Android (Chrome) | ✅ Google Password Manager | ✅ NFC keys | Full support since Android 9+ |
| Windows 11 (Windows Hello) | ✅ Windows Credential Manager | ✅ | Fingerprint / PIN / Face Recognition |
WebAuthn Registration (JavaScript)
// Step 1: Fetch challenge and options from your server
const response = await fetch('/auth/passkey/register/begin', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: userEmail })
});
const options = await response.json();
// Step 2: Decode base64url fields
options.challenge = base64urlToUint8Array(options.challenge);
options.user.id = base64urlToUint8Array(options.user.id);
// Step 3: Create credential — triggers device biometric/PIN prompt
const credential = await navigator.credentials.create({ publicKey: options });
// Step 4: Send attestation back to server for verification and storage
await fetch('/auth/passkey/register/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: credential.id,
rawId: uint8ArrayToBase64url(new Uint8Array(credential.rawId)),
response: {
clientDataJSON: uint8ArrayToBase64url(
new Uint8Array(credential.response.clientDataJSON)),
attestationObject: uint8ArrayToBase64url(
new Uint8Array(credential.response.attestationObject))
},
type: credential.type
})
});
For production, use a battle-tested WebAuthn server library such as Fido2NetLib (.NET), SimpleWebAuthn (Node.js), or py_webauthn (Python) rather than hand-rolling the attestation verification logic.
TOTP Authenticator Apps (RFC 6238)
TOTP (Time-based One-Time Password, RFC 6238) is the technology behind authenticator apps like Google Authenticator, Microsoft Authenticator, and Authy. A 6-digit code rotates every 30 seconds, derived from a shared secret and the current Unix timestamp.
TOTP is most commonly used as a second factor (MFA) rather than as a standalone credential, but in a passwordless flow it can serve as the primary authentication factor when combined with a previously verified email address at registration.
TOTP Setup (.NET / ASP.NET Core Identity)
ASP.NET Core Identity has built-in TOTP MFA support. The IUserTwoFactorTokenProvider<TUser> interface handles code generation and verification via GenerateTwoFactorTokenAsync and VerifyTwoFactorTokenAsync.
// 1. Enable TOTP in Program.cs
builder.Services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// 2. Display QR code to the user (setup page model)
public async Task OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
var key = await _userManager.GetAuthenticatorKeyAsync(user);
if (string.IsNullOrEmpty(key))
{
await _userManager.ResetAuthenticatorKeyAsync(user);
key = await _userManager.GetAuthenticatorKeyAsync(user);
}
SharedKey = FormatKey(key);
AuthenticatorUri = $"otpauth://totp/{Uri.EscapeDataString("Ailacs Identity")}"
+ $":{Uri.EscapeDataString(user.Email)}"
+ $"?secret={key}&issuer=AilacsIdentity&digits=6&period=30";
}
// 3. Verify the code the user enters
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
var valid = await _userManager.VerifyTwoFactorTokenAsync(
user,
_userManager.Options.Tokens.AuthenticatorTokenProvider,
Input.Code.Replace(" ", "").Replace("-", ""));
if (!valid)
{
ModelState.AddModelError("Input.Code", "Verification code is invalid.");
return Page();
}
await _userManager.SetTwoFactorEnabledAsync(user, true);
return RedirectToPage("./TwoFactorAuthentication");
}
Security Comparison: Passwordless Methods
Not all passwordless methods are equal. Here is how each compares across key security and UX dimensions:
| Method | Phishing Resistant | Replay Protection | Device Binding | UX Friction | Best For |
|---|---|---|---|---|---|
| Passkeys (WebAuthn/FIDO2) | ✅ Origin-bound | ✅ Cryptographic | ✅ Device-bound | ⭐ Very Low | Consumer apps, enterprise |
| TOTP (Authenticator App) | ⚠️ Partial (real-time phishing risk) | ✅ 30 s window | ⚠️ App-level only | Medium | MFA for B2C & B2B |
| Magic Links | ⚠️ Link-scoped only | ✅ Single-use token | ❌ None | Low | Low-risk consumer flows |
| SMS OTP | ❌ SIM-swap vulnerable | ⚠️ Short window only | ❌ None | Low | Legacy — avoid if possible |
Security ranking from strongest to weakest: Passkeys > TOTP > Magic Links > SMS OTP. Always prefer the highest-security method available on the user's device.
Enabling Passwordless with Ailacs Identity
Ailacs Identity provides built-in support for all three passwordless methods. Enable and configure them in the tenant dashboard under Authentication → Passwordless.
Magic Links
Enable under Passwordless → Email Magic Link. Configure expiry (1–60 min) and per-address rate limits per tenant.
Passkeys
Enable under Passwordless → Passkeys (WebAuthn). Set RP ID to your domain. Supports both platform and roaming (hardware) authenticators.
TOTP MFA
Enable under MFA → Authenticator App (TOTP). Works with Google Authenticator, Microsoft Authenticator, Authy, and any RFC 6238 app.
UX Best Practices for Passwordless B2C
- Always offer a fallback. If passkey creation fails (new device, unsupported browser), fall back to magic link or TOTP. Never leave users stranded on a dead-end screen.
- Progressive passkey enrollment. Don't force passkey setup at first login. Prompt after the user's second or third session — conversion is higher once users have invested in your product.
- Clear magic link messaging. Tell users exactly which address the link was sent to, how long it is valid, and that it is single-use. Proactive messaging eliminates the most common support tickets.
- Cross-device passkey flows. Support QR-code cross-device authentication (CTAP2) so users can authenticate on a desktop browser using their phone as an authenticator.
- Secure account recovery. Always provide a recovery flow (verified email + identity challenge) for users who lose their passkey device or delete their authenticator app. Without this, passwordless becomes an account lockout risk.
- Show the right option first. Detect passkey availability via
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()and surface that method first. Hide or deprioritise methods the device cannot support.