Introduction: The Problem with "Convenience"
Let's be honest: traditional multi-device authentication is a security nightmare dressed up as a user experience feature. You sign in on your phone, your tablet, your laptop, that sketchy computer at the hotel business center you swore you'd never use again—and they all stay logged in forever.
For a personal finance app like Lumo, that's unacceptable. I didn't spend months building a secure transaction layer just to have someone's ex-girlfriend still accessing their account because they forgot to log out of their iPad six months ago.
So I did what any sane engineer would do: I stopped reading theoretical security books (boring) and built a practical, slightly paranoid device sync system I'm calling the Guardian Model.
The Guardian Model: One Ring to Rule Them All
The concept is simple, bordering on brutal:
- One device has full access at any given time. This is the Guardian.
- Want to switch to a new device? You become a Petitioner and politely wait outside.
- The Guardian gets a modal: "Let this new device in?"
- Approve: The Guardian is immediately evicted (kill switch), the Petitioner becomes the new Guardian.
- Deny: The Petitioner stays in the digital cold.
No simultaneous access. No "maybe I can just check this real quick on my laptop while my phone is the Guardian." One device, full control, explicit handoffs.
How It Works (The Technical Tea)
The Data Model
We track device state in Convex with surgical precision:
// The critical fields in our users table
{
activeDeviceId: "iphone-15-pro-abc123", // Who's the boss right now
activeDeviceDisplayName: "Abdul's iPhone", // Pretty name for modals
lastActiveDeviceTimestamp: 1707832800000, // "Last seen" for stale detection
pendingRequest: { // Someone's knocking
deviceId: "pixel-8-xyz789",
deviceName: "Abdul's Pixel",
timestamp: 1707836400000
}
}The Happy Path (Sequence Diagram)
Here's exactly what happens when you try to sign in on your new phone while your old phone is the Guardian:
State Machine: What Mode Is My Device In?
Security Analysis: Where Are The Bodies Buried?
I'm not going to pretend this is perfect. Here is an honest threat model:
The Good (What We Mitigated)
| Threat | Mitigation |
|---|---|
| Lost device retains access | Single active device policy. Old device is evicted immediately on new approval. |
| Session hijacking via stolen refresh tokens | Device ID binding. Even with valid Firebase token, wrong device ID = blocked. |
| Race condition: two Guardians | Backend re-fetches user state before updates. Last write wins, but Guardian check ensures only active device can approve. |
| Stale device lockout | 7-day auto-expiry. Lost your phone in the ocean? Wait a week or use recovery code. |
The "Acceptable Risks" (What We Acknowledge)
| Risk | Why We Accept It |
|---|---|
| Device ID spoofing | Device ID is client-generated. An attacker with valid Firebase credentials could spoof the active device ID. Mitigation: Device ID includes hardware-bound components (Android ID, iOS Keychain) making it non-trivial. |
| Recovery code brute force | 6-digit codes have 1M combinations. Mitigation: Rate limiting, max attempts, codes are single-use and generated with crypto-secure random. |
| Social engineering | User could be tricked into approving attacker's device. Mitigation: Clear device naming, timestamp of request shown in modal. |
The Architecture (C4 Context)
Edge Cases: Because Users Are Creative
The "Fell in the Toilet" Scenario
User drops Guardian phone in water. It's dead. They buy a new phone.
- Day 1-6: User must use recovery code or wait
- Day 7+: Stale device threshold reached, new device auto-approved
The "Approve Spam" Scenario
User keeps clicking approve on Guardian while Petitioner keeps retrying.
- Mitigation: Each
ensureUsercall updates the timestamp. Old pending requests (>10 min) are treated as stale and overwritten.
The "Airplane Mode" Scenario
Guardian approves, but Petitioner is offline.
- Handling: Petitioner queues the approval. When online,
getUsersubscription updates and grants access. Guardian is already evicted (kill switch is reactive, not proactive).
Closing: Why This Matters
I built this because I was tired of security theater. Most apps pretend that "sign out everywhere" buttons work instantly, or that session revocation is real-time. It's not. It's eventual consistency with a side of hope.
The Guardian model is pessimistic security: assume every other device is compromised until proven otherwise. It's slightly less convenient than "sign in everywhere," but infinitely more secure than "oops I left my account logged in at the Apple Store."
If you're a security researcher and you see a hole in this logic, please tell me. If you're a developer and want to implement this, steal this code—it's why I open-sourced the logic. If you're just here for the diagrams, I hope they were pretty.
Stay paranoid. Build secure.
Abdul Rafay
Founder, Syntax Lab TechnologySharing the journey at rafay99.com & lumo.rafay99.com
P.S. Yes, I know "Guardian" sounds like a Destiny reference. No, I don't play Destiny. Yes, the naming is staying anyway.