porchlight/docs/plans/2026-02-13-auth-services-design.md

3.1 KiB

Authentication Services — Design Document

For Claude: This document captures design decisions for the authentication service layer. The implementation plan is at docs/plans/2026-02-13-auth-services-plan.md.

Scope

Backend-only authentication services. No HTTP routes or templates in this phase.

Three independent services:

  1. PasswordService — Argon2 hash/verify
  2. WebAuthnService — FIDO2 registration/authentication via python-fido2
  3. MagicLinkService — Invite token create/validate

Design Decisions

1. PasswordService (stateless, pure crypto)

  • Wraps argon2-cffi PasswordHasher
  • Two methods: hash(password) -> str, verify(hash, password) -> bool
  • PasswordHasher injected via constructor for testability
  • No repository dependency — caller reads/writes credentials
  • Uses argon2-cffi OWASP-recommended defaults
  • verify() returns bool (catches VerifyMismatchError internally)

2. WebAuthnService (class wrapping Fido2Server)

  • Constructor takes rp_id, rp_name, origin — creates Fido2Server internally
  • Four methods: begin_registration, complete_registration, begin_authentication, complete_authentication
  • begin_* methods return (options, state) — caller stores state (e.g., in HTTP session)
  • complete_* methods accept state back from caller
  • No repository dependency — caller handles credential CRUD
  • Uses python-fido2 v2.1 API (Fido2Server, PublicKeyCredentialRpEntity, etc.)

3. MagicLinkService (token lifecycle)

  • Depends on MagicLinkRepository (injected)
  • create(username, created_by?, note?) -> MagicLink — generates token via secrets.token_urlsafe(32)
  • validate(token) -> MagicLink | None — checks exists, not used, not expired
  • mark_used(token) -> bool — delegates to repo
  • cleanup_expired() -> int — delegates to repo
  • TTL configurable via constructor (from Settings.invite_ttl)

4. Testing Strategy

  • Password: Roundtrip hash/verify, invalid password returns False, fast hasher params for tests
  • WebAuthn: Build registration/authentication responses manually using fido2's create() factory methods (AttestedCredentialData.create, AuthenticatorData.create, CollectedClientData.create, etc.) with real ES256 keys from cryptography library. No external test utility package needed.
  • MagicLink: In-memory SQLite fixtures, test create/validate/expired/used states

5. Dependencies

All already in pyproject.toml:

  • fido2>=2.1 (python-fido2)
  • argon2-cffi>=25.1

No new dependencies required.

File Structure

src/fastapi_oidc_op/
├── authn/
│   ├── __init__.py          (exists, empty)
│   ├── password.py          (new)
│   └── webauthn.py          (new)
├── invite/
│   ├── __init__.py          (exists, empty)
│   └── service.py           (new)

tests/
├── test_authn/
│   ├── __init__.py          (new)
│   ├── test_password.py     (new)
│   └── test_webauthn.py     (new)
├── test_invite/
│   ├── __init__.py          (new)
│   └── test_service.py      (new)