Core Docs

QR Login & Multi-Device

QR Login & Multi-Device



Cross-device login via QR code — log into a new device by scanning with a device you're already logged into.

QR Login Flow



The QR login lets a user who is already authenticated on one device (phone, tablet) log into a new device (desktop, laptop) by scanning a QR code. No password entry needed on the new device.

The Flow



Device A (new, not logged in)              Device B (already logged in)
┌──────────────────────────┐ ┌──────────────────────────┐
│ │ │ │
│ Login Screen │ │ (user's phone/tablet) │
│ │ │ │
│ [📱 Log in with QR] │ │ │
│ │ │ │
└──────────────────────────┘ └──────────────────────────┘
│ │
▼ │
┌──────────────────────────┐ ┌──────────────────────────┐
│ │ │ │
│ ┌────────────────────┐ │ │ │
│ │ ▄▄▄ ▄▄▄ ▄▄▄ │ │ scanned │ Browser opens: │
│ │ ▄▄▄ ▄▄▄ ▄▄▄ │ │ ◄────────── │ core.auth/qr/confirm │
│ │ ▄▄▄ ▄▄▄ ▄▄▄ │ │ by user │ │
│ └────────────────────┘ │ │ "Log in on new device?" │
│ │ │ │
│ Scan this code with │ │ Device: Chrome/Windows │
│ a device you're │ │ IP: 192.168.1.42 │
│ already logged into │ │ Location: Lausanne, CH │
│ │ │ │
│ Waiting... │ │ [Approve] [Deny] │
│ │ │ │
└──────────────────────────┘ └──────────────────────────┘
│ │
│ ▼
│ ┌──────────────────────────┐
│ │ ✓ Login approved │
│ │ │
│ │ You can close this page │
│ └──────────────────────────┘

┌──────────────────────────┐
│ │
│ ✓ Logged in! │
│ │
│ [Continue] │
│ │
└──────────────────────────┘


Step-by-Step



1. Device A requests QR code

When the user clicks "Log in with QR code" on the login screen:

POST /auth/qr/generate
→ Returns:
{
"qr_token": "abc-123-uuid",
"qr_data": "https://core.auth.medkronos.com/qr/scan?token=abc-123-uuid",
"expires_in": 300
}


  • qr_token is a one-time token stored in a temporary table (Redis or DB)

  • Expires in 5 minutes

  • The QR code encodes the qr_data URL

2. Device A polls for status

GET /auth/qr/status?token=abc-123-uuid
→ Returns:
{ "status": "pending" } // waiting for scan
{ "status": "scanned" } // scanned, waiting for approval
{ "status": "approved" } // user approved — login complete
{ "status": "denied" } // user denied
{ "status": "expired" } // 5 min timeout


Device A polls every 2 seconds. Stops when status is approved, denied, or expired.

3. User scans QR on Device B

The QR code URL opens core.auth/qr/scan?token=abc-123-uuid on Device B's browser. If Device B has an active session (session_ref cookie), core.auth:

  1. Validates the qr_token

  2. Reads the session from the cookie

  3. Updates qr_token status to scanned

  4. Shows the confirmation screen

4. Confirmation screen on Device B

┌─────────────────────────────────────────┐
│ │
│ Log in on new device? │
│ │
│ Device: Chrome 125 on Windows │
│ IP: 192.168.1.42 │
│ Location: Lausanne, Switzerland │
│ Time: June 25, 2026 at 19:45 │
│ │
│ [ Approve ] │
│ [ Deny ] │
│ │
└─────────────────────────────────────────┘


5. User approves

POST /auth/qr/confirm
{ "token": "abc-123-uuid", "action": "approve" }


core.auth:
  1. Validates qr_token

  2. Gets the user from Device B's session

  3. Creates a new session for Device A

  4. Updates qr_token status to approved

  5. Returns the session info (session_ref, user info)

6. Device A completes login

Device A's poll receives status: approved with session data. It:
  1. Stores the session_ref

  2. Redirects to the app or dashboard

  3. User is logged in on Device A

Security Considerations



ConcernMitigation






QR code interceptionOne-time token, 5-min expiry, HTTPS only
Replay attacksToken consumed on first approval
Phishing (fake QR)QR encodes core.auth domain — user sees the real domain in browser
Session leakageNew session is created specifically for Device A, not a copy of Device B's session
Unauthorized approvalDevice B must be authenticated; confirmation shows device details for user verification

QR Token Storage



CREATE TABLE qr_login_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
token VARCHAR(64) UNIQUE NOT NULL,
status VARCHAR(20) DEFAULT 'pending', -- pending, scanned, approved, denied, expired
user_id UUID, -- set when scanned (from Device B's session)
device_info JSON, -- Device A's user agent, IP
created_at TIMESTAMP DEFAULT NOW(),
expires_at TIMESTAMP NOT NULL, -- NOW() + 5 min
approved_at TIMESTAMP
);

CREATE INDEX idx_qr_token ON qr_login_tokens(token);
CREATE INDEX idx_qr_expires ON qr_login_tokens(expires_at);


Cleanup job removes expired tokens every 5 minutes.

Multi-Device Session Management



Users can view and manage all their active sessions from the Account Management frontend (see Frontend Architecture).

Session List



┌─────────────────────────────────────────┐
│ │
│ Active Sessions │
│ │
│ 📱 This device │
│ iPhone 15, Safari │
│ Lausanne, Switzerland │
│ Last active: now │
│ │
│ 💻 Desktop │
│ Chrome 125, Windows │
│ Lausanne, Switzerland │
│ Last active: 2 hours ago │
│ │
│ [Revoke] each session │
│ [Log out everywhere] │
│ │
└─────────────────────────────────────────┘


Device Trust (Future)



Planned extension: mark devices as "trusted" to skip 2FA on known devices.

  • User logs in with 2FA on a device → "Trust this device?" prompt

  • If trusted → skip 2FA for 30 days on this device

  • Device identified by fingerprint (browser + OS + hardware characteristics)

  • Trust expires after 30 days, requiring re-verification

This is a Phase 2+ feature. For now, 2FA is required every time if enabled.




See also: Registration & Login for the main login flow, Session Architecture for session mechanics.