4 min read

Authentication Options for Indie Apps in 2026

  • #comparison
  • #setup

Auth is one of those side-project tasks that feels like it should take an afternoon and somehow eats a week. The good news is you almost never need to build it from scratch anymore.

Roll your own with a library

Lucia and Auth.js give you session handling and OAuth integration without a hosted service attached. You wire up a users/sessions table, configure the providers you want, and the library handles token exchange and cookies.

You get full control over your data model and no per-user pricing. You also get stuck owning the parts that aren’t glamorous: password resets, email verification, rate-limiting login attempts. This is the right call if you already have a database and want auth data living next to the rest of your app’s data instead of in someone else’s system. It’s the wrong call if you’re trying to ship fast and don’t yet care about owning that data.

Managed auth-as-a-service

Clerk, Auth0, and Supabase Auth handle the hosted login UI, MFA, and social providers behind an SDK. Clerk’s drop-in <SignIn /> component gets you production-grade auth, including organization support, in under an hour.

It’s free at side-project scale, but check the pricing page before assuming it stays that way. Per-monthly-active-user billing means going from “side project” to “a few thousand users” can turn a $0 line item into a real bill overnight, and the threshold where that happens is different for every provider. Clerk’s free tier currently covers a few thousand MAUs before billing kicks in; Auth0’s free allowance is smaller and its pricing tiers jump in bigger steps. Neither number is worth memorizing exactly, since both change, but it’s worth actually checking rather than assuming “free tier” means “free at any scale you’ll realistically hit.”

Supabase Auth, specifically

This one’s worth pulling out on its own, because it’s bundled with Postgres and row-level security policies that key off the authenticated user’s ID directly. Every query gets scoped to “rows this user can see” by Postgres itself, not by application code you have to remember to write correctly on every single endpoint.

If Supabase is already your database, the auth is basically free to add. No second vendor, no separate mental model for who’s logged in versus what they can touch.

RLS specifically has a real learning curve though, and it’s easy to lock yourself out or leave a hole open the first few times you write a policy. I’d start without RLS turned on while the data model is still moving, then add policies once things settle, rather than trying to get security rules right on day one.

Passkeys

Most of the above now support passkeys as a provider option, and @simplewebauthn/server exists if you want to roll your own. A user signs in with a fingerprint or face unlock instead of a password, which kills an entire category of phishing risk outright.

Adoption is still a coin flip depending on your audience. Device support has caught up everywhere, but plenty of people still expect “sign in with Google” as a fallback, so offer passkeys as an option, not the only door in. The other wrinkle is account recovery: a passkey tied to a lost phone with no backup provider configured is a support ticket you can’t easily resolve, so pair it with at least one other sign-in method rather than shipping it alone.

The part everyone forgets: session revocation

Whichever option you pick, check how it handles killing a session before you need it in production. “User clicks logout” is the easy case, every option here handles that fine. The harder case is “this account got compromised, kill every active session everywhere right now.” Auth.js and Lucia leave this to you, since you own the sessions table; deleting the row does it, but you have to build the admin action yourself. Clerk and Auth0 give you a dashboard button. Supabase gives you a supabase.auth.admin.signOut() call. None of this matters until the day it does, and that’s exactly the kind of gap that’s expensive to discover for the first time during an actual incident instead of while you’re still setting things up.

What I’d actually pick

If you’re already on Supabase for data, use Supabase Auth. There’s no reason to add a second vendor for something your existing one does well, and this isn’t close. If you’re not on Supabase and just want the fastest path to a polished login flow, Clerk is the better pick over Auth0 for a solo project specifically because of how little friction the drop-in components add. Auth.js is the honest answer for “I don’t want a hosted dependency at all,” but be clear-eyed that it costs you a weekend, not an afternoon, and you’ll own every bug in that flow forever.