Skip to content

Authentication is a Developer Nightmare

Authentication is conceptually simple — verify the user is who they say they are. In practice it's a convoluted mess of vulnerabilities, dependencies, and buggy edge cases. This is an honest look at all three approaches: passwords, OAuth, and passkeys.


Why It's Hard

The web is an open, trustless protocol.
Anyone can send any request pretending to be anyone.

And the distrust goes BOTH directions:
  You    →  wary of bad actors trying to access your database
  Users  →  wary of your website misusing their data

There is no perfect answer. Every mechanism has trade-offs.

Authentication vs Authorization — don't mix them:

Authentication  →  "Who are you?"    Verify identity
Authorization   →  "What can you do?" Verify permissions

The Three Factors of Authentication

mindmap
  root((Proving Identity))
    Know Something
      Password
      PIN
      Security questions
      Most common
      Can be forgotten or leaked
    Have Something
      Physical key or keycard
      Hardware security key YubiKey
      TOTP authenticator app codes
      Requires physical possession
    Be Something
      Fingerprint
      Face scan
      Biometrics
      Device-level passkeys
      Hardest to steal

Multi-factor authentication (MFA) combines two or more of these. A stolen password alone isn't enough if the attacker also needs your phone.


Approach 1: Password Authentication

The most boring, most understood, and most universally supported approach. A user proves identity by knowing a secret string associated with their account.

The Core Flow

Registration:
  1. User provides username + password
  2. Server generates a random salt (unique per user)
  3. Server hashes:  SHA-256(password + salt)  →  hashed_password
  4. Server stores:  { user_id, username, hashed_password, salt }
  5. Password itself is NEVER stored

Sign-in:
  1. User provides username + password
  2. Server fetches user record → retrieves stored salt
  3. Server hashes:  SHA-256(provided_password + salt)
  4. Compare with stored hash:
       match     →  create session token → set HttpOnly cookie ✅
       no match  →  401 Unauthorized ❌

Why Hashing + Salting?

Plain text storage (NEVER do this):
  DB leak → attacker has every user's password directly

Hashed only (bad):
  SHA-256("password123") is always the same hash
  Attacker uses rainbow tables: precomputed hash → password lookups
  Every user with "password123" has the SAME hash → one lookup cracks all of them

Hashed + salted (correct):
  salt = random UUID generated per user
  SHA-256("password123" + "a7f3c9d2-...") is unique for every user
  Rainbow tables are useless — attacker must brute-force each user individually
# Registration (Kotlin-style pseudocode → applies to any language)
val salt = UUID.randomUUID().toString()
val hashedPassword = SHA256(password + salt)
db.save(userId, username, hashedPassword, salt)

# Sign-in
val user = db.findByUsername(username)
val attemptHash = SHA256(providedPassword + user.salt)
if (attemptHash == user.hashedPassword) createSession(user.id)

The Password Reset Problem

The moment you add passwords, you inherit the reset problem. Users forget passwords. Users leak passwords. You can't avoid building this.

Reset flow:
  1. User clicks "Forgot password" → enters email
  2. Server generates a one-time code → stores {code, email, expiry}
  3. Server sends email with link: https://yourapp.com/reset?code=abc123
  4. User follows link → enters new password
  5. Server: fetch user by code → validate expiry → re-salt + re-hash new password
  6. Invalidate the one-time code

Complexity added:
  ✅ SMTP client (sending emails)
  ✅ One-time code storage and expiry
  ✅ Secure code generation
  ✅ Reset page UI

The insight this reveals

Once you're sending a reset link to an email, you're already using email as an authentication method. Anyone who can access the inbox can access the account. This begs the question: why make users remember an extra password at all?

Pros, Cons, When

✅ Pros ❌ Cons
Simple to understand and implement Password reset is non-trivial infrastructure
No third-party dependency Users forget, reuse, and leak passwords
Works everywhere Plain text storage risk if you make mistakes
Universal fallback Brute-force and credential stuffing attacks
Full control over the system Phishing — users tricked into entering on fake sites

Approach 2: OAuth 2.0 (Social Sign-In)

OAuth 2.0 lets users authenticate via a third-party they already trust (Google, GitHub, Apple). Your app never sees their password — only a verified token from the provider.

The Flow

1. User clicks "Sign in with GitHub"
2. Your app redirects to GitHub with:
   - client_id   (your public app ID)
   - redirect_uri (where GitHub sends the code back)
   - state       (random unguessable value — prevents CSRF / man-in-the-middle)

3. User logs into GitHub, approves requested permissions (e.g. read email)

4. GitHub redirects back to your redirect_uri with:
   ?code=one_time_code&state=your_state_value

5. Your SERVER (not client) sends to GitHub:
   POST /login/oauth/access_token
   { client_id, client_secret, code }
   → GitHub returns an access_token

6. Your server uses access_token to fetch user details from GitHub API
   GET /user  →  { id, email, name, avatar_url }

7. Create your own session for the user → set HttpOnly cookie
Why the `state` parameter matters:
  Without it:  attacker crafts a link with their own OAuth code
               → victim clicks → their account is linked to attacker's identity
  With state:  code is only accepted if state matches what YOUR server generated
               Man-in-the-middle attacks blocked ✅

Setup Required (GitHub Example)

GitHub App Settings:
  Client ID:      public — safe to expose in frontend
  Client Secret:  private — only on your server, never in client code
  Origin:         https://yourapp.com  (where requests come from)
  Redirect URL:   https://yourapp.com/auth/callback  (where code is sent)

The Big Provider Problem

Google, Facebook, and Apple are a nightmare to work with

Google: - Requires a full privacy policy and terms of service before submission - App goes through a review process that can take days - Can revoke your access at any time with no recourse

Facebook: - Similar review process PLUS requires HTTPS on localhost (you need tunnels for local dev) - True story from the transcript: Facebook sent unclear requirements with a short deadline, didn't respond to support tickets, then shut down authentication for a multi-billion dollar company. The only resolution was their CTO threatening to pull millions in Facebook ad spend.

GitHub: - What you'd expect OAuth to be. Straightforward, no political hurdles, works as documented.

"If all providers were as pleasant as GitHub I would have a much easier time praising OAuth. The problem is that you are now at the mercy of these other companies."

The Supabase / Firebase / Auth0 Question

These services abstract OAuth, password auth, and session management. Are they worth it?

What they solve:
  ✅ OAuth plumbing (code → token exchange)
  ✅ User database
  ✅ Email templates
  ✅ Some security best practices baked in

What they don't solve:
  ❌ The provider approval process (you still deal with Google/Facebook review)
  ❌ The dependency risk (they can deprecate features, change pricing, go down)
  ❌ Complexity (deprecation notices forwarded from Auth0 originally from Google — who are you even talking to?)
  ❌ Added communication layer that can fail

The honest assessment:
  Implementing OAuth directly is NOT significantly more work than integrating with these services.
  But using them adds a dependency that can fail or cut you off.

  Recommendation: implement OAuth and password auth yourself.
                  It's not that much code. You own it. Nobody can pull the rug.

OAuth Pros, Cons, When

✅ Pros ❌ Cons
No password management for you Provider approval process (Google/Facebook)
Leverages user's existing secure account You depend on third-party availability
Most people already use these platforms Provider can revoke access at any time
No password reset infrastructure needed HTTPS required for some providers (Facebook)
GitHub/Apple are reasonable to work with State/CSRF implementation required

Approach 3: Passkeys / WebAuthn

Passkeys use a public/private key pair stored on the user's device or password manager. No password is ever created, stored, or transmitted. The server never sees private data.

The Key Pair Concept

Asymmetric cryptography:
  Private key:  stays on the device / password manager. Never shared. Never transmitted.
  Public key:   sent to and stored by your server.

Sign with private key → anyone with the public key can verify the signature.
But you can't derive the private key from the public key (one-way).

Registration Flow

1. User clicks "Register with passkey"
2. Browser / device / password manager generates a new key pair
3. Device prompts user: biometric (fingerprint/face) or PIN to approve

4. Client sends to server:
   { publicKey, algorithm, challenge_response }
   The challenge was sent by the server — proves this is a fresh registration (not a replay)

5. Server verifies the challenge → stores { user_id, publicKey, algorithm }
   Private key never leaves the device ✅

Sign-In Flow

1. Server sends a fresh challenge (random bytes)
2. User's device signs the challenge with their private key
   (biometric or PIN prompts the device to use the key)
3. Client sends signed challenge to server

4. Server:
   fetch stored publicKey for this user
   verify(signedChallenge, publicKey)  →  match? ✅ or ❌

5. No password. No secret transmitted. Nothing to phish.

The Device Problem

Passkeys tied to a device:
  ✅ Incredibly secure — private key never leaves hardware
  ❌ What if the device is lost, broken, or stolen?
  ❌ What if you need to sign in on someone else's computer?
  ❌ Users need a recovery path — back to email or another device

Passkeys in a password manager (the better approach):
  ✅ Syncs across your devices
  ✅ Works anywhere you have access to your password manager
  ✅ Not tied to one physical device
  ✅ Password managers handle backup and recovery

  "Using a password manager lets you avoid the headaches of
   passwords being logged to a device while still getting all the benefits of passkeys."

Why Passkeys Are the Ideal Endgame

Your app stores:  public key only — no secrets, no passwords
Phishing:         useless — the key is domain-bound, won't work on a fake site
Password reset:   doesn't exist — nothing to forget or reset
Database leak:    no passwords to expose
MFA built-in:     biometric prompt IS the second factor

"This is the way authentication should be done, in my opinion."

The Adoption Problem

Current reality (2024):
  Most users use a browser's built-in password manager whether they know it or not.
  Passkey-style UX is appearing in Chrome, Safari, iOS, Android — growing fast.
  But not everyone understands or trusts "sign in with passkey" yet.

Recommendation:
  High-risk or privacy-focused product? Passkeys are a great fit.
  Excellent as a second factor (MFA) alongside passwords.
  Still too early to use as the ONLY auth method for a general audience.

Passkey Pros, Cons, When

✅ Pros ❌ Cons
No password to forget, leak, or phish Unfamiliar to many users
Server stores no secret data Device loss recovery is complex
Domain-bound — immune to phishing Requires JavaScript on the client
Biometric / PIN UX is familiar now More complex server implementation
Works with password managers Adoption still growing

The Full Comparison

┌──────────────────────┬──────────────────┬──────────────────┬──────────────────┐
│                      │ Password         │ OAuth 2.0        │ Passkeys         │
├──────────────────────┼──────────────────┼──────────────────┼──────────────────┤
│ Complexity           │ Low–Medium       │ Medium           │ Medium–High      │
│ Third-party dep      │ None             │ Provider         │ Browser/OS API   │
│ Password to remember │ Yes              │ No (use theirs)  │ No               │
│ Phishable            │ Yes              │ Partially        │ No (domain-bound)│
│ Reset flow needed    │ Yes              │ No               │ No               │
│ Works everywhere     │ Yes              │ Most places      │ Modern browsers  │
│ Data stored server   │ Hash + salt      │ User ID + token  │ Public key only  │
│ Provider risk        │ None             │ High (Google etc)│ None             │
│ User familiarity     │ Universal        │ High             │ Growing          │
└──────────────────────┴──────────────────┴──────────────────┴──────────────────┘

Decision Framework

mindmap
  root((Which Auth Method?))
    Always include password fallback
      Universal compatibility
      Required for accessibility
    Natural OAuth provider exists
      GitHub for dev tools
      Google for productivity apps
      Use OAuth as primary
      Implement directly skip Firebase
    High-risk or privacy-focused product
      Add passkeys as MFA
      Or as primary if audience is technical
    General consumer app
      Password + optional OAuth
      Add passkeys as opt-in
    Enterprise
      SSO SAML or OIDC
      Out of scope for this video
Simple rule of thumb:
  Start with password auth.          Simple, boring, works everywhere.
  Add OAuth if there's a clear fit.  GitHub for devtools, Google for productivity.
  Add passkeys as MFA or future-proofing. The right direction.
  Skip Supabase/Firebase for OAuth.  Direct integration isn't harder.
  Never use Facebook OAuth.          The horror story speaks for itself.

Quick Reference

Password auth:
  Store:   SHA-256(password + random_salt) + the salt
  Never:   plain text passwords, unsalted hashes
  Add:     reset flow via email one-time code (adds SMTP dependency)
  Session: create opaque session token on successful login

OAuth 2.0:
  Flow:    redirect → code → server exchanges code for token → fetch user → create session
  State:   always include state param (CSRF/man-in-the-middle protection)
  Avoid:   Facebook. Be cautious with Google. GitHub is great.
  Skip:    Supabase/Firebase for OAuth — direct integration is equally simple

Passkeys:
  Keys:    device generates key pair; public key stored on server; private key never leaves device
  Flow:    server sends challenge → device signs with private key → server verifies with public key
  Best:    with a password manager for cross-device portability
  Use for: high security, phishing resistance, MFA, future default

All three:
  Always use HTTPS
  Session token = HttpOnly + Secure + SameSite cookie
  Never store passwords in plain text
  Never roll your own hashing — use established libraries