September 27, 2026
Forget Email, Passwords, and OAuth: A Stateless Deterministic Authentication Pattern for Indie Apps
If youโve built side projects, lightweight SaaS platforms, or local-first web applications, youโve likely hit the Authentication Wall.

By Hoa Nghi Trinh
6 min read
What should be a simple feature โ letting users bookmark a post, save their preferences, or sync state across devices โ often turns into a multi-week infrastructure nightmare. For years, developers have been trapped between two bad options: forcing traditional credentials or relying on Big Tech OAuth.
There is a third way. By shifting from centralized identity verification to a Server-Side Derived Credential system, we can achieve cross-device sync with zero email collection, zero password management, and zero third-party dependencies.
The Authentication Nightmare for Indie Developers
Pain Point 1: The Administrative Wall of Big Tech OAuth
We're told that "Sign in with Google" or "Sign in with Apple" solves auth. But for an indie hacker, hobbyist, or early-stage startup, setting this up is a massive administrative hurdle:
- Apple Developer Tax: "Sign in with Apple" requires a paid Apple Developer Account ($99/year) and strict domain association.
- Facebook & Microsoft Business Verification: Want to let users log in with Facebook or Microsoft? Prepare to submit business registration documents, utility bills, and wait weeks for manual app reviews.
- Verification Fear: Without full business verification, platforms flag your app as "Unverified," scaring users away with glowing red security warnings.
Pain Point 2: The Infrastructure Cost of "Simple" Email/Password
Deciding to roll your own traditional auth sounds simple until you hit the infrastructure requirements:
- Domain & DNS Overhead: You need a dedicated domain with configured SPF, DKIM, and DMARC records.
- Third-Party Email APIs: Sending magic links or password reset emails requires services like Resend, SendGrid, or Postmark.
- Data Liability: Storing emails and password hashes turns your database into a target for credential stuffing attacks and GDPR compliance requirements.
Pain Point 3: The Flaws of Device Fingerprinting & Pure Local Storage
Many developers try to bypass auth entirely using localStorage or browser fingerprinting. However:
localStorageis Ephemeral: Users clear browser caches, switch to Incognito mode, or use privacy extensions that wipe local data.- Fingerprinting is Unreliable: Fingerprints shift with browser updates, system theme toggles, or VPN usage. More importantly, two identical hardware setups can produce identical hashes, causing collision bugs.
Pain Point 4: Modern User Fatigue
Users no longer want to sign up for every tool they try. Requiring an email, phone number, or password for a lightweight utility creates immediate bounce rates. Users fear spam, data leaks, and password fatigue.
The Philosophy: Zero Identity Footprint
Instead of holding onto real-world identities, give users a self-managed key to their session state.
Imagine an app where:
- A user visits your site and instantly uses it as if they are logged in.
- No popups, no forms, no "Confirm your email" friction.
- When they want to sync to a second device, they scan a One-Time Login QR code.
- For long-term emergency access, they save a Master Recovery QR Code.
- If they lose all active devices and forgot to back up their Recovery Key, the account is lost โ an explicit architectural trade-off in favor of absolute privacy and zero identity tracking.
Technical Architecture: Dual-Key Deterministic Pairing
How do we balance quick cross-device pairing, long-term backup, and bulletproof session security without storing plain passwords or complex database states?
We use a Two-Key System combining a single-use pairing_epoch with a permanent, user-salted HMAC derivation.
+-------------------------------------------------------+
| SERVER DATABASE |
| |
| User Row: |
| +---------------+---------------+---------------+ |
| | user_id | pairing_epoch | user_salt | |
| +---------------+---------------+---------------+ |
| | usr_987654321 | 0 | e4a82b9c... | |
| +---------------+---------------+---------------+ |
+-------------------------------------------------------+
/ \
/ \
[ONE-TIME LOGIN QR CODE] [MASTER RECOVERY KEY]
HMAC(user_id + ":" + pairing_epoch, HMAC(user_id,
PAIRING_SECRET + user_salt) PAIRING_SECRET + user_salt)
| |
v v
Tied to pairing_epoch Ignores pairing_epoch
Single-use: Auto-increments Permanent backup key.
epoch upon successful login. Rotating user_salt revokes
all previous recovery keys.+-------------------------------------------------------+
| SERVER DATABASE |
| |
| User Row: |
| +---------------+---------------+---------------+ |
| | user_id | pairing_epoch | user_salt | |
| +---------------+---------------+---------------+ |
| | usr_987654321 | 0 | e4a82b9c... | |
| +---------------+---------------+---------------+ |
+-------------------------------------------------------+
/ \
/ \
[ONE-TIME LOGIN QR CODE] [MASTER RECOVERY KEY]
HMAC(user_id + ":" + pairing_epoch, HMAC(user_id,
PAIRING_SECRET + user_salt) PAIRING_SECRET + user_salt)
| |
v v
Tied to pairing_epoch Ignores pairing_epoch
Single-use: Auto-increments Permanent backup key.
epoch upon successful login. Rotating user_salt revokes
all previous recovery keys.1. Dual Passphrase Derivation Schemes
Option A: One-Time Login QR Code (Daily Sync)
Calculated using the current pairing_epoch:
- Single-Use Guarantee: As soon as Device B successfully pair-logs in via
POST /api/pair, the server immediately executes
UPDATE users SET pairing_epoch = pairing_epoch + 1 WHERE id = ?UPDATE users SET pairing_epoch = pairing_epoch + 1 WHERE id = ?- Zero Window of Exposure: The QR code displayed on Device A becomes invalid the instant it is used. Anyone standing behind you who photographed your screen holds an instantly expired token.
Option B: Master Recovery QR Code (Permanent Backup)
Calculated without pairing_epoch:
- Permanent Emergency Key: Users can download this as an image or save the 6-word phrase into a password manager. It does not expire when you sync new devices.
- Revocation via Salt Rotation: If a user suspects their physical recovery key was compromised, tapping "Re-generate Master Recovery Key" generates a new random
user_saltin the database. This instantly renders all previously exported Recovery Keys completely useless.
Secure Session Tokens & Architecture Isolation
Setting a bare cookie like gl_id=user_id is a severe authentication flaw: HttpOnly prevents client-side JavaScript access, but users can easily edit their cookie in DevTools to impersonate any user ID.
HMAC-Signed Session Cookies
All active device sessions must be HMAC-signed using a separate server-side secret (AUTH_SECRET):
Crucial Architectural Boundary: Session vs. Pairing Isolation
Notice that pairing_epoch is deliberately excluded from the active Session_Cookie.
- Why? If active sessions were bound to
pairing_epoch, an attacker scanning a leaked Login QR code would causepairing_epochto increment upon login, instantly logging out the legitimate owner and locking them out of their own account. - The Solution: Isolate
pairing_epochstrictly to the Login QR flow. Daily device syncs auto-increment the epoch to destroy used codes, while existing, authenticated devices maintain their signed session uninterrupted.
URL Hash Fragments for Client-Side Privacy
When Device A renders a QR code for Device B to scan:
https://yourapp.com/pair#n=username&p=word1.word2.word3.word4.word5.word6https://yourapp.com/pair#n=username&p=word1.word2.word3.word4.word5.word6Per the HTTP specification, data following a # fragment is processed strictly client-side and is never sent to the server in GET request headers. This guarantees that pairing passphrases never leak into server access logs, edge CDN caches, or browser history headers.
1. Instant Credential Revocation (pairing_epoch)
What if a user accidentally leaks their QR code or passphrase? Instead of writing complex session-invalidation logic or rotating global keys, the server simply executes one light query:
sql
UPDATE users SET pairing_epoch = pairing_epoch + 1 WHERE id = ?;UPDATE users SET pairing_epoch = pairing_epoch + 1 WHERE id = ?;Incrementing pairing_epoch changes the HMAC input instantly. Every previously issued Login QR code or passphrase for that profile becomes invalid immediately โ including any currently displayed, unused code. Active, already-authenticated HTTP-Only session cookies remain unaffected, since sessions are deliberately isolated from the pairing epoch (see architecture section above).
2. Rate Limiting & Brute-Force Protection
Because 6 words from the BIP-39 list represent 66โ72 bits of entropy, that entropy only matters if the verification endpoint is protected from repeated guessing:
- IP- and account-level rate limits โ e.g., a maximum of 5 failed pairing attempts per 15 minutes, enforced at both the IP and account level so an attacker can't simply rotate IPs to bypass a per-account limit.
- Automatic epoch lockout โ if failed attempts against a given account exceed the threshold, the server automatically increments
pairing_epochfor that user, invalidating the passphrase under attack before it can be brute-forced.
3. Account Enumeration Protection
To prevent attackers from discovering which usernames exist by measuring API response times: if the submitted username does not exist in the database, the server must still run the full HMAC derivation against a dummy record before returning a generic error. This ensures a request for a non-existent account takes exactly as long as a request for a real account with an incorrect passphrase โ closing the timing side-channel.
4. Clear UX Expectations Around Data Loss
This architecture makes a conscious trade-off: privacy and simplicity in exchange for self-sovereign responsibility. Without an email or phone number on file, there is no "Forgot Password" link. If a user loses all active devices and did not save their Recovery Key, the account cannot be recovered by support. This must be communicated clearly in the UI โ not as a missing feature, but as an explicit security boundary.
Out of Scope: Future Production Hardening
To avoid over-complicating this core pattern, the following advanced security measures are recommended for production readiness but are kept out of scope for this foundational architecture:
- Short Time-To-Live (TTL) on Login QR Codes: Adding a short timestamp window (e.g., 3โ5 minutes) to the Login QR code payload to force automatic UI refresh if unused.
- Global "Log Out All Devices" Revocation: Implementing a dedicated session-version counter on the user record if you need the ability to forcibly terminate all active signed cookies simultaneously. Not implemented here; note the current cookie format has no session-version field, so adding this later requires a schema/cookie-format change โ flagged now so it's a deliberate deferral, not a surprise refactor.
- Recovery Cooldown & Push Decline Window: If a Master Recovery Key is used on a new device, active sessions could enter a 24-hour "cool-down" state, triggering a Web Push notification to existing devices allowing the owner to Decline & Intercept the recovery attempt (invalidating the old salt and kicking out the attacker).
By separating short-term login tokens from permanent recovery keys, indie developers can eliminate administrative hurdles, protect user privacy, and ship resilient software that runs anywhere without third-party gatekeepers.