mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 17:51:13 -05:00
128 lines
4.0 KiB
Markdown
128 lines
4.0 KiB
Markdown
# 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})
|