Authentication category of AWS Amplify providing APIs and building blocks for creating authentication experiences with Amazon Cognito
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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);
}
}
}