# Passkey/WebAuthn Implementation Plan **Status:** 🟡 In Progress **Priority:** CRITICAL (Required for Phase 2 Authentication) **Estimated Time:** 12-16 hours --- ## Overview Implementing passkey/WebAuthn support to provide modern, passwordless authentication as required by Phase 2 of the authentication migration. This will work alongside existing JWT/password authentication. --- ## Architecture ### Backend (Django) - **WebAuthn Library:** `webauthn==2.1.0` (already added to requirements) - **Storage:** PostgreSQL models for storing passkey credentials - **Integration:** Works with existing JWT authentication system ### Frontend (Next.js) - **Browser API:** Native WebAuthn API (navigator.credentials) - **Fallback:** Graceful degradation for unsupported browsers - **Integration:** Seamless integration with AuthContext --- ## Phase 1: Django Backend Implementation ### 1.1: Database Models **File:** `django/apps/users/models.py` ```python class PasskeyCredential(models.Model): """ Stores WebAuthn/Passkey credentials for users. """ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='passkey_credentials') # WebAuthn credential data credential_id = models.TextField(unique=True, db_index=True) credential_public_key = models.TextField() sign_count = models.PositiveIntegerField(default=0) # Metadata name = models.CharField(max_length=255, help_text="User-friendly name (e.g., 'iPhone 15', 'YubiKey')") aaguid = models.CharField(max_length=36, blank=True) transports = models.JSONField(default=list, help_text="Supported transports: ['usb', 'nfc', 'ble', 'internal']") # Attestation attestation_object = models.TextField(blank=True) attestation_client_data = models.TextField(blank=True) # Tracking created_at = models.DateTimeField(auto_now_add=True) last_used_at = models.DateTimeField(null=True, blank=True) is_active = models.BooleanField(default=True) class Meta: db_table = 'users_passkey_credentials' ordering = ['-created_at'] def __str__(self): return f"{self.user.email} - {self.name}" ``` ### 1.2: Service Layer **File:** `django/apps/users/services/passkey_service.py` ```python from webauthn import ( generate_registration_options, verify_registration_response, generate_authentication_options, verify_authentication_response, options_to_json, ) from webauthn.helpers.structs import ( AuthenticatorSelectionCriteria, UserVerificationRequirement, AuthenticatorAttachment, ResidentKeyRequirement, ) class PasskeyService: """Service for handling WebAuthn/Passkey operations.""" RP_ID = settings.PASSKEY_RP_ID # e.g., "thrillwiki.com" RP_NAME = "ThrillWiki" ORIGIN = settings.PASSKEY_ORIGIN # e.g., "https://thrillwiki.com" @staticmethod def generate_registration_options(user: User) -> dict: """Generate options for passkey registration.""" @staticmethod def verify_registration(user: User, credential_data: dict, name: str) -> PasskeyCredential: """Verify and store a new passkey credential.""" @staticmethod def generate_authentication_options(user: User = None) -> dict: """Generate options for passkey authentication.""" @staticmethod def verify_authentication(credential_data: dict) -> User: """Verify passkey authentication and return user.""" @staticmethod def list_credentials(user: User) -> List[PasskeyCredential]: """List all passkey credentials for a user.""" @staticmethod def remove_credential(user: User, credential_id: str) -> bool: """Remove a passkey credential.""" ``` ### 1.3: API Endpoints **File:** `django/api/v1/endpoints/auth.py` (additions) ```python # Passkey Registration @router.post("/passkey/register/options", auth=jwt_auth, response={200: dict})