- Created SelfFreezoneClient in Self components
- Wraps SDK FreezoneScriptClient for Self-specific operations
- Implements send_verification_email method
- Uses Rhai script template for email verification
- Includes template variable substitution
- Added serde-wasm-bindgen dependency
Usage:
let client = SelfFreezoneClient::builder()
.supervisor_url("http://localhost:8080")
.secret("my-secret")
.build()?;
client.send_verification_email(
"user@example.com",
"123456",
"https://verify.com/abc"
).await?;
8.6 KiB
8.6 KiB
Authentication Flows
Overview
Self implements a cryptographic challenge-response authentication system that eliminates the need for passwords while providing strong security guarantees. The system supports both new user registration and existing user authentication flows.
Registration Flow
Step-by-Step Process
sequenceDiagram
participant U as User
participant C as Client (Browser)
participant S as Server
participant E as Email System
Note over U,E: Phase 1: Email Verification
U->>C: Enter name and email
C->>S: POST /api/send-verification
S->>E: Generate verification link
S->>C: Verification sent response
S-->>U: Display verification link (dev mode)
Note over U,E: Phase 2: Email Confirmation
U->>S: Click verification link
S->>S: Mark email as verified
S->>C: SSE notification (verified)
C->>C: Update UI to show verified status
Note over U,E: Phase 3: Key Generation
U->>C: Click "Generate Keys"
C->>C: Generate secp256k1 key pair
C->>C: Display private key for backup
U->>C: Copy private key (mandatory)
U->>C: Enter encryption password
C->>C: Encrypt private key with AES-256-GCM
C->>C: Store encrypted key in localStorage
Note over U,E: Phase 4: Key Confirmation
U->>C: Paste private key for confirmation
C->>C: Verify pasted key matches generated key
U->>C: Confirm registration
C->>S: POST /api/register {email, name, public_key}
S->>S: Verify email is confirmed
S->>S: Store user record
S->>C: Registration success response
C->>C: Complete registration flow
Registration Data Flow
-
Email Collection
struct EmailVerificationRequest { email: String, } -
Verification Status
struct VerificationStatus { email: String, verified: bool, verification_token: String, } -
Key Generation (Client-side)
let keypair = generate_keypair()?; let encrypted_key = encrypt_private_key(&keypair.private_key, &password)?; -
Registration Completion
struct RegistrationRequest { email: String, name: String, public_key: String, }
Security Measures
- Email Verification: Prevents unauthorized registrations
- Key Backup Confirmation: Ensures user has saved private key
- Client-side Encryption: Private key never transmitted unencrypted
- Secure Random Generation: Cryptographically secure key generation
- Password Validation: Minimum 8 character password requirement
Login Flow
Step-by-Step Process
sequenceDiagram
participant U as User
participant C as Client (Browser)
participant S as Server
participant V as Vault System
Note over U,V: Phase 1: Identity Input
U->>C: Enter public key or select identity
C->>C: Validate public key format
Note over U,V: Phase 2: Challenge Generation
C->>S: Request authentication challenge
S->>S: Generate random challenge
S->>C: Return challenge + session info
Note over U,V: Phase 3: Key Decryption
U->>C: Enter password
C->>V: Retrieve encrypted private key
V->>C: Return encrypted key data
C->>C: Decrypt private key with password
Note over U,V: Phase 4: Challenge Response
C->>C: Sign challenge with private key
C->>S: POST /oauth/token {signature, public_key, challenge}
S->>S: Verify signature against public key
S->>S: Generate JWT token
S->>C: Return access token
Note over U,V: Phase 5: Session Establishment
C->>C: Store JWT token
C->>S: GET /oauth/userinfo (with Bearer token)
S->>C: Return user profile
C->>C: Complete login flow
OAuth 2.0 Token Request
The login process follows OAuth 2.0 client credentials flow with cryptographic assertions:
struct LoginRequest {
grant_type: String, // "client_credentials"
client_assertion_type: String, // "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
client_assertion: String, // Signed JWT containing challenge response
public_key: String, // User's public key (client identifier)
challenge: String, // Server-provided challenge
scope: String, // Requested permissions
}
JWT Token Structure
struct Claims {
sub: String, // Subject (public key)
iss: String, // Issuer ("self-sovereign-identity")
aud: String, // Audience ("identity-server")
exp: usize, // Expiration time (1 hour)
iat: usize, // Issued at time
scope: String, // Granted scopes
}
Challenge-Response Mechanism
-
Challenge Generation
let challenge = Uuid::new_v4().to_string(); -
Signature Creation (Client-side)
let signature = keypair.sign(&challenge)?; -
Signature Verification (Server-side)
fn verify_signature(public_key: &str, challenge: &str, signature: &str) -> bool { // Verify signature matches challenge using public key }
Vault-Based Authentication
Multi-Key Management
The vault system allows users to store multiple encrypted keys and authenticate with any of them:
sequenceDiagram
participant U as User
participant C as Client
participant V as Vault Manager
participant S as Server
U->>C: Select identity from vault
C->>V: List available identities
V->>C: Return identity list
U->>C: Choose identity and enter password
C->>V: Decrypt selected identity key
V->>C: Return decrypted private key
C->>S: Authenticate with selected identity
S->>C: Return session for selected identity
Vault Data Structure
struct VaultEntry {
id: String,
name: String,
email: String,
public_key: String,
encrypted_private_key: EncryptedPrivateKey,
created_at: String,
}
struct EncryptedPrivateKey {
encrypted_data: String, // Base64 encoded ciphertext
nonce: String, // Base64 encoded nonce
salt: String, // Base64 encoded salt
}
Session Management
JWT Token Lifecycle
-
Token Issuance
- Generated after successful authentication
- Contains user's public key as subject
- 1-hour expiration time
- Signed with server secret
-
Token Usage
- Included in Authorization header as Bearer token
- Required for accessing protected endpoints
- Validated on each request
-
Token Refresh
- Currently requires re-authentication
- Future: Refresh token mechanism
Session Storage
// Client-side session storage
localStorage.setItem('jwt_token', access_token);
localStorage.setItem('current_identity', public_key);
localStorage.setItem('session_expires', expiration_time);
Error Handling
Registration Errors
- Email Not Verified: User must complete email verification
- Invalid Email Format: Client-side validation prevents submission
- Key Generation Failed: Retry with new random seed
- Encryption Failed: Check password strength and retry
- Server Unavailable: Retry with exponential backoff
Authentication Errors
- Invalid Public Key: Key format validation and user feedback
- Wrong Password: Decryption failure, prompt for correct password
- Signature Verification Failed: Invalid key or challenge tampering
- Token Expired: Automatic re-authentication flow
- User Not Found: Public key not registered, redirect to registration
Error Response Format
struct ErrorResponse {
error: String, // OAuth 2.0 error code
error_description: String, // Human-readable description
error_uri: Option<String>, // Optional documentation link
}
Security Considerations
Attack Mitigation
- Replay Attacks: Challenges are single-use and time-limited
- Man-in-the-Middle: HTTPS encryption for all communications
- Key Theft: Private keys encrypted with user passwords
- Brute Force: Rate limiting on authentication attempts
- Session Hijacking: JWT tokens with short expiration times
Privacy Protection
- No Password Storage: Server never sees user passwords
- Minimal Data Collection: Only email and public key stored
- Local Key Storage: Private keys never leave user's device
- Anonymous Usage: Public keys don't reveal personal information
Compliance Considerations
- GDPR: Users control their own data, right to deletion
- OAuth 2.0: Standard token-based authentication
- OpenID Connect: Compatible user info endpoint
- WebAuthn: Future integration for hardware token support