Authentication is a solved problem. The number of things that can go wrong when you implement it yourself is long, and they're not just the obvious mistakes. Storing passwords in plain text is obvious. The subtle ones are what get you: JWTs that don't get invalidated on logout, password reset flows vulnerable to timing attacks, login endpoints with no rate limiting, refresh tokens that never rotate.
These show up in security post-mortems for companies that knew what they were doing and still got it wrong. The surface area is larger than it looks.
The default: use a provider
On every project where there's no specific reason not to, we use an auth provider. Clerk, Auth0, Supabase Auth, and WorkOS are all reasonable depending on scale and requirements. They handle session management, MFA, social logins, token rotation, and the compliance requirements that come with storing credentials. They're also actively maintained, which means security patches happen without us making a calendar event to remember.
The cost is a monthly subscription and some degree of lock-in. For most projects, that's a good trade. The subscription cost is trivial relative to the engineering time of doing it correctly. The lock-in risk is real but smaller than it sounds: the surface area of auth in a typical application is well-defined and swappable if you design for it.
Why your auth is not a competitive advantage
Clients sometimes ask why we're "not building it ourselves." The answer is that their auth is infrastructure, not a feature. The thing their software does is the competitive advantage. The login screen is not.
We've seen projects where significant engineering time went into building a custom auth system that did roughly what Clerk does, minus the edge cases Clerk has already handled. That time could have gone into the actual product. It didn't.
When we have to roll our own
It happens. Regulated industries with compliance requirements that existing providers don't meet. Systems that need to integrate with an existing identity provider in a way that off-the-shelf products don't support. Legacy environments where adding an external dependency isn't possible.
In those cases, the floor is:
- Passwords hashed with bcrypt, not MD5, not SHA-1, not plain text
- JWTs with short expiry, 15 minutes or less for access tokens
- Refresh tokens with rotation on use
- Rate limiting on login, registration, and password reset endpoints
- Explicit session invalidation when a password changes
- No JWTs stored in localStorage; use httpOnly cookies
That's the floor. The ceiling involves a lot more: brute-force protection, anomaly detection, device tracking, audit logs. How much of the ceiling you need depends on what the application does and who the users are.
The important thing is that this list is a starting point, not a comprehensive guide. Auth is a domain where reading a checklist and feeling confident is its own kind of risk. When we have to build it, we build it carefully and we get it reviewed before it ships.