or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

authentication-lifecycle.mddevice-management.mdindex.mdmulti-factor-authentication.mdoauth-social-authentication.mdpassword-management.mdserver-side-apis.mdsession-management.mduser-management.mdwebauthn-credentials.md
tile.json

webauthn-credentials.mddocs/

WebAuthn Credentials

Passwordless authentication using WebAuthn for biometric and security key authentication.

Associate WebAuthn Credential

Register a new WebAuthn credential (biometric or security key) for the current user.

function associateWebAuthnCredential(): Promise<void>;

Usage Example

import { associateWebAuthnCredential } from "@aws-amplify/auth";

try {
  await associateWebAuthnCredential();
  console.log("WebAuthn credential registered successfully");
} catch (error) {
  console.log("WebAuthn registration failed:", error.message);
}

List WebAuthn Credentials

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;
}

Usage Example

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
  });
}

Delete WebAuthn Credential

Remove a WebAuthn credential from the user's account.

function deleteWebAuthnCredential(input: DeleteWebAuthnCredentialInput): Promise<void>;

interface DeleteWebAuthnCredentialInput {
  credentialId: string;
}

Usage Example

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");
}

WebAuthn Registration Flow

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 Authentication Flow

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);
  }
}

Credential Management UI

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}`);
});

Browser Compatibility

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);
}

Security Best Practices

User Education

  • Explain what WebAuthn credentials are and their benefits
  • Guide users through the registration process
  • Provide clear instructions for different credential types
  • Explain backup and recovery options

Implementation Guidelines

  • Always check browser support before attempting WebAuthn operations
  • Provide fallback authentication methods
  • Handle user cancellation gracefully
  • Store minimal credential metadata
  • Implement proper error handling and user feedback

Error Handling

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);
    }
  }
}