Multi-Modal Identity & Authentication
Status: PLANNED (V1.0–V1.3) Effort: XL (6-8 weeks, spread across releases) Dependencies: Auth (done), SSO Login (done) Priority: HIGH — Enables families with kids, accessibility
The Story: Sophie Logs In
Sophie is 8 years old. She has no email address, no password, no phone. She walks up to the family tablet and says:
"Morphee, it's me!"
Her voice is recognized. She's in. Her Space opens with her tasks, her conversations, her canvas — exactly where she left off. She creates a task ("finish math homework"), asks Morphee a question, and logs out by walking away.
Her dad Sebastien has email + 2FA. Her grandmother has face recognition because she forgets passwords. Her au-pair has a temporary voice profile that expires in 3 months.
Everyone uses the same Morphee. Everyone authenticates differently. That's multi-modal identity.
Core Principle: Identity =/= Email
Traditional:
User = Email + Password → Access
Morphee:
Identity (one person)
├── Auth Method 1: Voice ("Morphee, it's me")
├── Auth Method 2: Face (smile at camera)
├── Auth Method 3: Email + Password
├── Auth Method 4: GitHub OAuth
├── Auth Method 5: Passkey (YubiKey)
└── Auth Level → ACL decisions
One identity, many ways to prove you're you. ACL actions can require specific methods or combinations:
| Action | Required | Why |
|---|---|---|
| Create a task | Any method (level 1+) | Low risk |
| Change group settings | 2FA or biometric (level 2+) | Medium risk |
| Delete the group | Parent/admin + 2FA (level 3) | High risk |
| Kid invites a friend | Parent approval | Parental control |
Architecture
Authentication Levels
| Level | Meaning | Example |
|---|---|---|
| 1 | Single factor | Email/password, OAuth |
| 2 | Biometric or 2FA | Face, voice, TOTP |
| 3 | Multi-factor + biometric | Face + TOTP, voice + hardware key |
Step-Up Authentication
When an action requires a higher level than the current session:
Sophie (level 2, voice) tries to delete group
→ ACL: "delete_group requires level 3 + parent approval"
→ Step-up challenge issued
→ Dad gets notification: "Sophie wants to delete the group. Approve?"
→ Dad authenticates (face/2FA) and approves
→ Action completes
This works for any escalation — a developer using email+password who tries to access billing gets a TOTP challenge, not a rejection.
Identity Model
class Identity(BaseModel):
id: str # UUID
display_name: str # "Sophie" — no email required
avatar_url: str | None
date_of_birth: date | None # For age-based ACL
is_minor: bool # Jurisdiction-aware (EU: 16, US: 13)
parent_identity_id: str | None # Link to parent for kids
groups: list[GroupMembership]
class AuthenticationMethod(BaseModel):
id: str
identity_id: str
method_type: Literal[
"email_password", "github_oauth", "google_oauth", "apple_oauth",
"face_recognition", "voice_recognition", "passkey",
"totp_2fa", "sms_2fa", "hardware_key"
]
primary: bool
verified: bool
biometric_template_id: str | None # Vault reference, NEVER raw data
last_used_at: datetime | None
class AuthenticationSession(BaseModel):
session_id: str
identity_id: str
methods_used: list[str] # ["voice_recognition"]
authentication_level: int # 1, 2, or 3
expires_at: datetime
Biometric Processing — Local Only
All biometric processing happens on-device via Tauri Rust. No face or voice data ever leaves the device.
Face recognition: candle (FaceNet/ArcFace) → 512-dim embedding → cosine similarity > 0.85 Voice recognition: candle (ECAPA-TDNN) → speaker embedding → cosine similarity > 0.90 Liveness detection: eye blinks + head movement + texture analysis (face), speech naturalness + environment noise (voice)
Templates are stored in the VaultProvider (OS keychain on desktop, encrypted SQLite on Android). Only a template_id reference is stored in the database.
Connection to the Runtime Hierarchy
Biometric models are excellent candidates for the compilation chain (see ROADMAP.md — Knowledge Pipeline):
| Component | Runtime | Rationale |
|---|---|---|
| Face encoder (FaceNet) | WasmRuntime (Level 3) | Fixed model, performance-critical, runs on all platforms |
| Voice encoder (ECAPA) | WasmRuntime (Level 3) | Same — compiled, sandboxed, portable |
| Liveness detection | WasmRuntime (Level 3) | Security-critical, must be tamper-resistant |
| Age estimation from face | PythonRuntime (Level 1) | Experimental, needs iteration |
| Custom passphrase logic | LLMRuntime (Level 0) | Flexible — "say something about your favorite animal" |
Biometric WASM modules are not marketplace-shareable (they contain personal templates). But the model architecture could be shared — a community could publish better face encoders as WASM extensions.
Use Cases
1. Kid Without Email (Primary Use Case)
Parent creates Sophie's identity (no email needed):
→ Display name: "Sophie"
→ Date of birth: 2018-05-15
→ is_minor: true, parent_identity_id: <dad_id>
Parent enrolls Sophie's voice:
→ Sophie says "Morphee, it's me" 5 times
→ Voice template stored in vault (encrypted)
Daily use:
→ Sophie: "Morphee, it's me" → authenticated (level 2)
→ Creates tasks, chats, uses canvas → all allowed (level 1+ required)
→ Tries to change settings → parent approval required → dad notified
2. Multi-Method Adult
Dad has: email+password (primary), GitHub OAuth, TOTP 2FA, face recognition
Quick morning login: face recognition → level 2 → full access
Sensitive action (delete group): level 3 required → step-up → enters TOTP → level 3
New device: no face enrolled → email+password → level 1 → adds TOTP → level 2
3. Accessibility: Voice-Only
Maria (visually impaired) uses only voice:
→ "Morphee, it's me" → authenticated
→ All interactions via voice commands
→ Sensitive actions: voice + SMS code ("Morphee, my code is 123456")
4. Temporary Access (Au-pair, Contractor)
Au-pair enrolled with voice, expiry date set (3 months)
→ Full access to family Space during contract
→ Auto-revoked on expiry
→ No residual biometric data (vault cleanup)
Security & Privacy
Biometric Data Rules
- NEVER store raw biometric data (images, audio) in the database
- ALWAYS store templates in VaultProvider (encrypted at rest)
- ALWAYS process biometrics locally (Tauri Rust, never cloud)
- GDPR: full biometric deletion on request (templates + auth methods)
- Consent: explicit opt-in for each biometric method, revocable anytime
- Liveness: required for all biometric auth (prevents photo/recording attacks)
Parent Controls for Minors
- Parent must authenticate to create/modify a minor's identity
- Minor's authentication methods can only be managed by parent
- Age-based ACL policies enforced automatically (jurisdiction-aware)
- Parent approval requests for sensitive actions (with notification)
- Parent can see activity log, revoke access instantly
Rollout Plan
V1.0: Multi-Method Login (2 weeks)
- Identity + AuthenticationMethod models
- Support email, GitHub/Google/Apple OAuth (leveraging existing SSO)
- JWT with
authentication_levelclaim - Frontend: multi-method login screen
- Database migration: extend users → identities
- Tests: multi-method authentication flows
V1.0: Parent-Kid Identity (2 weeks)
- Create minor identity (no email required)
- Parent-child identity linking
- Age-based ACL policies
- Parent approval request system
- Kid-friendly UI (larger buttons, simpler language)
- Tests: parent-kid workflows
V1.3: Biometric Authentication (2 weeks)
- Tauri Rust: face encoding via candle (FaceNet/ArcFace)
- Tauri Rust: voice encoding via candle (ECAPA-TDNN)
- Vault integration for template storage
- Frontend: FaceRecognitionAuth + VoiceRecognitionAuth components
- Liveness detection (face + voice)
- Tests: biometric enrollment + authentication
V1.3: Step-Up Authentication (1 week)
- Step-up challenge system
- Authentication requirements per action/resource
- Frontend: StepUpAuthDialog component
- ACL integration
- Tests: step-up flows
Future
- Passkeys (WebAuthn) — hardware security keys, OS biometric
- Behavioral biometrics — typing patterns, usage patterns
- Continuous authentication — re-verify periodically during session
- Biometric fusion — combine face + voice for higher confidence
- Age estimation — auto-detect minor status from face (PythonRuntime → WasmRuntime)
Database Schema
-- Identities (extends existing users concept)
CREATE TABLE identities (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
display_name TEXT NOT NULL,
avatar_url TEXT,
date_of_birth DATE,
is_minor BOOLEAN DEFAULT FALSE,
parent_identity_id UUID REFERENCES identities(id) ON DELETE SET NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Authentication methods (multiple per identity)
CREATE TABLE authentication_methods (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
identity_id UUID NOT NULL REFERENCES identities(id) ON DELETE CASCADE,
method_type TEXT NOT NULL,
primary_method BOOLEAN DEFAULT FALSE,
verified BOOLEAN DEFAULT FALSE,
last_used_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
biometric_template_id TEXT, -- Vault reference only
oauth_provider_user_id TEXT,
oauth_email TEXT,
email TEXT,
email_verified BOOLEAN DEFAULT FALSE,
totp_secret_id TEXT, -- Vault reference
expires_at TIMESTAMP WITH TIME ZONE, -- For temporary access
UNIQUE(identity_id, method_type, email)
);
-- Step-up authentication challenges
CREATE TABLE step_up_challenges (
challenge_id UUID PRIMARY KEY,
identity_id UUID NOT NULL REFERENCES identities(id) ON DELETE CASCADE,
required_methods TEXT[] NOT NULL,
reason TEXT NOT NULL,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
completed BOOLEAN DEFAULT FALSE
);
-- Parent approval requests
CREATE TABLE parent_approval_requests (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
child_identity_id UUID NOT NULL REFERENCES identities(id) ON DELETE CASCADE,
parent_identity_id UUID NOT NULL REFERENCES identities(id) ON DELETE CASCADE,
action TEXT NOT NULL,
resource_type TEXT NOT NULL,
resource_id UUID,
status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'denied')),
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
decided_at TIMESTAMP WITH TIME ZONE
);
This is the foundation for truly family-friendly, accessible, secure AI. A platform where a grandmother, a dad, an 8-year-old, and an au-pair can all share the same Morphee — each authenticating in the way that works best for them.