Passwordless authentication using WebAuthn for biometric and security key authentication.
Register a new WebAuthn credential (biometric or security key) for the current user.
function associateWebAuthnCredential(): Promise<void>;import { associateWebAuthnCredential } from "@aws-amplify/auth";
try {
await associateWebAuthnCredential();
console.log("WebAuthn credential registered successfully");
} catch (error) {
console.log("WebAuthn registration failed:", error.message);
}Get all WebAuthn credentials associated with the current user.
function listWebAuthnCredentials(input?: ListWebAuthnCredentialsInput): Promise<ListWebAuthnCredentialsOutput>;
interface ListWebAuthnCredentialsInput {
pageSize?: number;
nextToken?: string;
}
interface ListWebAuthnCredentialsOutput {
credentials: AuthWebAuthnCredential[];
nextToken?: string;
}
interface AuthWebAuthnCredential {
credentialId: string;
friendlyCredentialName: string;
relyingPartyId: string;
authenticatorAttachment?: 'platform' | 'cross-platform';
authenticatorTransports?: ('ble' | 'hybrid' | 'internal' | 'nfc' | 'usb')[];
createdAt: Date;
}import { listWebAuthnCredentials } from "@aws-amplify/auth";
const { credentials, nextToken } = await listWebAuthnCredentials({
pageSize: 10
});
credentials.forEach(credential => {
console.log(`Credential: ${credential.friendlyCredentialName}`);
console.log(`ID: ${credential.credentialId}`);
console.log(`Type: ${credential.authenticatorAttachment || 'unknown'}`);
console.log(`Created: ${credential.createdAt.toLocaleDateString()}`);
console.log(`Transports: ${credential.authenticatorTransports?.join(', ') || 'none'}`);
console.log('---');
});
// Handle pagination
if (nextToken) {
const nextPage = await listWebAuthnCredentials({
pageSize: 10,
nextToken
});
}Remove a WebAuthn credential from the user's account.
function deleteWebAuthnCredential(input: DeleteWebAuthnCredentialInput): Promise<void>;
interface DeleteWebAuthnCredentialInput {
credentialId: string;
}import { deleteWebAuthnCredential, listWebAuthnCredentials } from "@aws-amplify/auth";
// Get user's credentials
const { credentials } = await listWebAuthnCredentials();
// Find credential to delete
const credentialToDelete = credentials.find(c =>
c.friendlyCredentialName === 'Old Security Key'
);
if (credentialToDelete) {
await deleteWebAuthnCredential({
credentialId: credentialToDelete.credentialId
});
console.log("WebAuthn credential deleted successfully");
}Complete flow for registering a new WebAuthn credential:
import { associateWebAuthnCredential } from "@aws-amplify/auth";
class WebAuthnManager {
async registerCredential(friendlyName?: string) {
// Check browser support
if (!this.isWebAuthnSupported()) {
throw new Error('WebAuthn not supported in this browser');
}
try {
// The associateWebAuthnCredential function handles the WebAuthn ceremony
await associateWebAuthnCredential();
console.log('WebAuthn credential registered successfully');
return true;
} catch (error: any) {
this.handleWebAuthnError(error);
return false;
}
}
private isWebAuthnSupported(): boolean {
return !!(navigator.credentials && window.PublicKeyCredential);
}
private handleWebAuthnError(error: any) {
// Handle common WebAuthn errors
if (error.name === 'NotSupportedError') {
console.log('WebAuthn not supported on this device');
} else if (error.name === 'InvalidStateError') {
console.log('Credential already registered');
} else if (error.name === 'NotAllowedError') {
console.log('User cancelled WebAuthn registration');
} else if (error.name === 'AbortError') {
console.log('WebAuthn registration timed out');
} else {
console.log('WebAuthn registration failed:', error.message);
}
}
}
// Usage
const webAuthnManager = new WebAuthnManager();
const handleRegisterWebAuthn = async () => {
const success = await webAuthnManager.registerCredential('My Security Key');
if (success) {
// Update UI to show new credential
window.location.reload();
}
};WebAuthn credentials are used during the sign-in process:
import { signIn, confirmSignIn } from "@aws-amplify/auth";
// Sign in process with WebAuthn
const { isSignedIn, nextStep } = await signIn({
username: "user@example.com",
// No password needed for WebAuthn
});
if (!isSignedIn && nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_WEBAUTHN_CREDENTIAL') {
// User needs to provide WebAuthn credential
console.log("Please use your security key or biometric");
try {
// The confirmSignIn will trigger WebAuthn ceremony
// No challengeResponse needed for WebAuthn
const { isSignedIn: webAuthnComplete } = await confirmSignIn({
challengeResponse: '' // Empty for WebAuthn
});
if (webAuthnComplete) {
console.log("Signed in with WebAuthn successfully");
}
} catch (error) {
console.log("WebAuthn authentication failed:", error);
}
}Building a credential management interface:
import {
listWebAuthnCredentials,
deleteWebAuthnCredential,
associateWebAuthnCredential
} from "@aws-amplify/auth";
class CredentialManager {
async buildCredentialList() {
const { credentials } = await listWebAuthnCredentials();
return credentials.map(credential => ({
id: credential.credentialId,
name: credential.friendlyCredentialName,
type: this.getCredentialTypeDescription(credential),
created: credential.createdAt.toLocaleDateString(),
async delete() {
const confirm = window.confirm(`Remove ${this.name}?`);
if (confirm) {
await deleteWebAuthnCredential({ credentialId: credential.credentialId });
console.log(`Credential ${this.name} removed`);
}
}
}));
}
private getCredentialTypeDescription(credential: AuthWebAuthnCredential): string {
const attachment = credential.authenticatorAttachment;
const transports = credential.authenticatorTransports || [];
if (attachment === 'platform') {
return 'Built-in (Face ID, Touch ID, Windows Hello)';
} else if (attachment === 'cross-platform') {
if (transports.includes('usb')) {
return 'USB Security Key';
} else if (transports.includes('nfc')) {
return 'NFC Security Key';
} else if (transports.includes('ble')) {
return 'Bluetooth Security Key';
} else {
return 'External Security Key';
}
}
return 'Security Key';
}
async addNewCredential() {
try {
await associateWebAuthnCredential();
console.log('New credential added successfully');
return true;
} catch (error) {
console.log('Failed to add credential:', error);
return false;
}
}
}
// Usage in React component or similar
const credentialManager = new CredentialManager();
const credentials = await credentialManager.buildCredentialList();
credentials.forEach(credential => {
console.log(`${credential.name} (${credential.type}) - Added: ${credential.created}`);
});WebAuthn support varies across browsers and platforms:
class WebAuthnSupport {
static check(): {
isSupported: boolean;
canCreateCredentials: boolean;
canUseCredentials: boolean;
platformSupport: string[];
} {
const isSupported = !!(
window.PublicKeyCredential &&
navigator.credentials &&
navigator.credentials.create
);
const platformSupport: string[] = [];
// Check platform-specific support
if (typeof window !== 'undefined') {
if ('ontouchstart' in window) {
platformSupport.push('touch');
}
if (navigator.userAgent.includes('iPhone') || navigator.userAgent.includes('iPad')) {
platformSupport.push('faceid', 'touchid');
} else if (navigator.userAgent.includes('Mac')) {
platformSupport.push('touchid');
} else if (navigator.userAgent.includes('Windows')) {
platformSupport.push('windows-hello');
}
}
return {
isSupported,
canCreateCredentials: isSupported,
canUseCredentials: isSupported,
platformSupport
};
}
static getUnavailabilityReason(): string | null {
if (!window.PublicKeyCredential) {
return 'WebAuthn not supported in this browser';
}
if (!navigator.credentials) {
return 'Credentials API not available';
}
if (window.location.protocol !== 'https:' && window.location.hostname !== 'localhost') {
return 'WebAuthn requires HTTPS';
}
return null;
}
}
// Usage
const support = WebAuthnSupport.check();
if (!support.isSupported) {
const reason = WebAuthnSupport.getUnavailabilityReason();
console.log('WebAuthn unavailable:', reason);
} else {
console.log('WebAuthn supported with:', support.platformSupport);
}import { associateWebAuthnCredential, AuthError } from "@aws-amplify/auth";
try {
await associateWebAuthnCredential();
} catch (error) {
if (error instanceof AuthError) {
switch (error.name) {
case 'NotAuthorizedException':
console.log('User not signed in');
break;
case 'InvalidParameterException':
console.log('Invalid WebAuthn parameters');
break;
case 'ResourceNotFoundException':
console.log('WebAuthn not configured for this user pool');
break;
default:
console.log('WebAuthn operation failed:', error.message);
}
} else {
// Handle WebAuthn-specific errors
switch (error.name) {
case 'NotSupportedError':
console.log('WebAuthn not supported');
break;
case 'InvalidStateError':
console.log('Credential already exists');
break;
case 'NotAllowedError':
console.log('User cancelled or timeout');
break;
case 'AbortError':
console.log('Operation aborted');
break;
default:
console.log('WebAuthn error:', error.message);
}
}
}