Registration & Login
Registration & Login
Complete flows for user registration, login, third-party auth, and 2FA verification. All flows are single-page, AJAX-driven with progressive screen reveal.
Architecture
Single-Page, Progressive Reveal
All registration and login flows are served as a single HTML page from
core.auth.medkronos.com. The page loads all screen components at once, but only the current screen is visible. Transitions between screens are handled by JavaScript — no page reloads, no framework. Just DOM manipulation.Page load (all screens rendered, only screen 1 visible)
│
▼
Screen 1 visible ──user action──→ AJAX call to core.auth API
│ │
▼ ▼
Screen 2 visible ←── response ── API validates/creates
│
▼
...progressive reveal until flow completesConnection Tokens
When a user is redirected from an app to core.auth for registration/login, the app issues a connection token and passes it as a URL parameter (
connection_ref). This token carries the app context through the entire flow:- Which app initiated the flow
- Where to redirect after completion
- What state/CSRF token the app expects back
-- connection_tokens table
-- See: /02-L2-architecture/02-auth/08-database-schemasIf there's no
connection_ref, the user came directly to core.auth.medkronos.com and there's no app to redirect back to.Frontend Technology
- HTML/CSS/JS served from core.auth domain
- Stylesheets from core.assets (core.backend HTML init)
- No framework — vanilla JS, DOM manipulation
- AJAX calls to core.auth API endpoints
- Mobile responsive
Registration Flow
Screen 1 — Info & Entry Point
┌─────────────────────────────────────────┐
│ │
│ Welcome to UNVRSL │
│ │
│ [ Let's Register ] │
│ │
│ ── or continue with ── │
│ │
│ [G] [A] [F] │
│ Google Apple Facebook │
│ │
│ Already have an account? Log in │
│ │
│ [Cancel] │
│ │
└─────────────────────────────────────────┘- "Let's Register" → Screen 2 (direct registration)
- Google/Apple/Facebook → redirect to provider OAuth flow
- "Log in instead" → switch to Login Flow (Screen 1)
- "Cancel" → redirect back to app (if connection_ref exists) or close
Third-Party Auth Redirect
User clicks "Continue with Google"
│
▼
JS redirects to: core.auth.medkronos.com/auth/google?connection_ref=CONN_TOKEN
│
▼
core.auth redirects to Google OAuth consent screen
│
▼
User authorizes with Google
│
▼
Google redirects back to: core.auth.medkronos.com/auth/google/callback
│
▼
core.auth:
1. Receives OAuth code from Google
2. Exchanges code for Google access token
3. Fetches user profile (name, email, avatar)
4. Checks: does a user_third_party_auths record exist for this provider+provider_user_id?
│
├── Yes → existing account found → skip to Login completion
│
└── No → new user:
├── Check: does a user exist with this email?
│ ├── Yes → prompt: "An account with this email exists. Log in to link Google."
│ └── No → create new user account:
│ ├── Generate random password (16 chars, alphanumeric)
│ ├── Hash with Argon2id
│ ├── Set email_verified = true (Google verified it)
│ ├── Store display_name, avatar_url from Google profile
│ ├── Create user_third_party_auths record
│ ├── Send welcome email with generated password
│ └── Continue to Screen 5 (success or app authorization)Welcome email content:
Subject: Your UNVRSL Account
Hi [Name],
Your account was created via Google Sign-In.
We generated a password for you in case you want to log in
without Google:
Password: [random-16-chars]
⚠️ If you plan to use password login, please:
1. Log in and change this password in account settings
2. Delete this email
If you only use Google Sign-In, you can ignore this.Screen 2 — Basic Info (Direct Registration)
┌─────────────────────────────────────────┐
│ │
│ Create Your Account │
│ │
│ First name: [ ] │
│ Last name: [ ] │
│ Email: [ ] │
│ Phone: [ ] │
│ Password: [ ] │
│ Confirm: [ ] │
│ │
│ [Continue] │
│ [Back] │
│ │
└─────────────────────────────────────────┘Password policy:
- Minimum 10 characters
- Hashed with Argon2id (
PASSWORD_ARGON2ID) - No breach database checking
Validation (AJAX):
POST /api/register/validate
{
"email": "user@example.com",
"phone": "+41...",
"password": "..."
}
Response:
{
"valid": true,
"errors": [] // or ["Email already registered", "Password too short"]
}If the email is already registered, the response includes a suggestion:
"An account with this email exists. [Log in instead?]"Screen 3 — Email + Code Verification
┌─────────────────────────────────────────┐
│ │
│ Verify Your Email │
│ │
│ We sent a code to user@example.com │
│ │
│ Code: [ - - - - ] │
│ │
│ [Verify] │
│ Resend code (55s) │
│ [Back] │
│ │
└─────────────────────────────────────────┘Flow:
POST /api/register/send-code
{ "email": "user@example.com" }
→ core.auth generates 6-digit code, stores hashed, sends via email
→ Code expires in 10 minutes
POST /api/register/verify-code
{ "email": "user@example.com", "code": "123456" }
→ core.auth verifies code hash
→ On success: mark email_verified = true, proceed to Screen 4
→ On failure: 401, up to 3 attempts before lockout (15 min)Phone verification (optional, if phone provided):
- Same flow: send SMS code, verify, mark phone_verified = true
- Can be done in parallel or sequentially after email
Screen 4 — Biometric Setup (Optional)
┌─────────────────────────────────────────┐
│ │
│ Quick Login Setup │
│ │
│ Use fingerprint or face recognition │
│ for faster login next time. │
│ │
│ [ Set Up Biometric ] │
│ │
│ Skip for now │
│ │
└─────────────────────────────────────────┘Only shown if the platform supports WebAuthn. Detected via:
if (window.PublicKeyCredential &&
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()) {
// Show biometric screen
}If the user taps "Set Up Biometric" → triggers WebAuthn registration flow (see 2FA & Passkeys).
If "Skip for now" → proceed to Screen 5.
Screen 5 — Completion
If registered directly (no connection_ref):
┌─────────────────────────────────────────┐
│ │
│ ✓ Account Created! │
│ │
│ Welcome to UNVRSL, [Name]. │
│ │
│ [Go to Dashboard] │
│ │
└─────────────────────────────────────────┘If registered via app (has connection_ref):
→ Switch to App Authorization Flow (see below).
Login Flow
Screen 1 — Login Entry Point
┌─────────────────────────────────────────┐
│ │
│ Log In to UNVRSL │
│ │
│ Email: [ ] │
│ Password: [ ] │
│ │
│ [ Log In ] │
│ │
│ ── or continue with ── │
│ │
│ [G] [A] [F] │
│ Google Apple Facebook │
│ │
│ [📱 Log in with QR code] │
│ │
│ Don't have an account? Register │
│ │
│ [Cancel] │
│ │
└─────────────────────────────────────────┘- Email + password → Screen 2
- Google/Apple/Facebook → third-party auth redirect (same as registration)
- QR code → QR Login Flow (see QR Login)
- "Register instead" → Registration Screen 1
- "Cancel" → redirect back to app or close
Screen 2 — Credential Verification
POST /api/login
{ "email": "user@example.com", "password": "..." }
Response (success):
{
"status": "ok",
"2fa_required": false
}
Response (2FA needed):
{
"status": "2fa_required",
"challenge_id": "abc-123",
"2fa_type": "totp" // or "webauthn"
}
Response (failure):
{
"status": "error",
"error": "Invalid credentials"
}Screen 3 — 2FA Verification (If Required)
┌─────────────────────────────────────────┐
│ │
│ Two-Factor Authentication │
│ │
│ Enter the code from your app: │
│ │
│ Code: [ - - - - ] │
│ │
│ [Verify] │
│ Use recovery code instead │
│ │
└─────────────────────────────────────────┘For TOTP:
POST /api/verify-2fa
{ "challenge_id": "abc-123", "code": "123456" }
→ Verify code against stored secret (±1 window tolerance)
→ On success: issue JWT + session_ref cookie
→ On failure: 401, up to 3 attempts before lockoutFor WebAuthn:
POST /api/verify-2fa/webauthn
{ "challenge_id": "abc-123" }
→ Returns PublicKeyCredentialRequestOptions
→ Browser shows biometric prompt
→ User authenticates
→ On success: issue JWT + session_ref cookieScreen 4 — Biometric Login (If Configured)
If the user has a passkey registered, offer biometric login as the primary option:
┌─────────────────────────────────────────┐
│ │
│ Welcome Back │
│ │
│ [ Use Fingerprint ] │
│ │
│ Use password instead │
│ │
└─────────────────────────────────────────┘This screen appears before the password prompt if the user has passkeys enabled. It's the path of least resistance for returning users.
Screen 5 — Login Completion
If logged in directly (no connection_ref):
→ User is logged in. Redirect to dashboard or the URL they originally requested.
If logged in via app (has connection_ref):
→ Check if user has authorized this app.
→ If yes: issue auth code, redirect back to app.
→ If no: show App Authorization screen.
Account Already Exists (Social Login)
When a user tries to register via social login and an account with the same email already exists:
User clicks "Continue with Google"
│
▼
Google returns email: user@example.com
│
▼
core.auth checks: user exists with this email?
│
└── Yes → check: already linked to this Google account?
│
├── Yes → log in directly (skip to completion)
└── No → prompt:
"An account with user@example.com already exists.
Log in with your password to link your Google account."
│
▼
User enters password → verified
│
▼
core.auth creates user_third_party_auths record (links Google)
│
▼
User is logged in, Google is now linkedThis prevents duplicate accounts while allowing users to link multiple social providers.
Password Policy
| Rule | Value |
| Minimum length | 10 characters |
| Hashing algorithm | Argon2id (PASSWORD_ARGON2ID) |
| Breach database checking | None (for now) |
| Salt | Automatic (PHP/Argon2id handles this) |
| Work factor | PHP defaults (configurable) |
Rate Limiting
| Endpoint | Limit | Window | Action |
/api/login | 5 attempts | 15 min | Lock account + notify |
/api/register/send-code | 3 codes | 10 min | Temporary block |
/api/register/verify-code | 3 attempts | 10 min | Lock verification + require new code |
/api/verify-2fa | 3 attempts | 5 min | Lock 2FA + require recovery code |
Rate limits are per-IP and per-user.
API Endpoints Summary
| Endpoint | Method | Purpose |
/api/register/validate | POST | Validate registration fields |
/api/register/send-code | POST | Send email/phone verification code |
/api/register/verify-code | POST | Verify the code |
/api/register/complete | POST | Create user account |
/api/login | POST | Authenticate credentials |
/api/verify-2fa | POST | Verify TOTP code |
/api/verify-2fa/webauthn | POST | Initiate/verify WebAuthn challenge |
/auth/google | GET | Redirect to Google OAuth |
/auth/google/callback | GET | Handle Google OAuth callback |
/auth/apple | GET | Redirect to Apple Sign-In |
/auth/apple/callback | GET | Handle Apple callback |
/auth/facebook | GET | Redirect to Facebook OAuth |
/auth/facebook/callback | GET | Handle Facebook callback |
/auth/qr/generate | POST | Generate QR login code |
/auth/qr/status | GET | Poll QR scan status |
/auth/qr/confirm | POST | Confirm QR login from scanning device |
Next: 2FA & Passkeys — TOTP, WebAuthn, biometric setup details.