Stop Building Password Authentication by Default_
Why most product teams should delegate authentication to an identity provider and use OpenID Connect instead of owning passwords themselves.
Most teams underestimate what it means to "own auth".
It is not just a login form and a hashed password column. It is account recovery, bot protection, MFA enrollment, email verification, breached-password handling, device trust, suspicious-login detection, lockout policy, session management, and incident response when something eventually goes wrong.
If authentication is not your product, you should be suspicious of any plan that starts with "we will just store bcrypt hashes ourselves."
The Problem Is Not Hashing
Password hashing is necessary. It is not the whole problem.
A serious authentication system also needs:
- a safe reset flow
- resistance to credential stuffing
- protection against phishing and session fixation
- auditability around sign-in events
- support for MFA or passkeys
- operational processes for support and recovery
Teams often implement the first 20 percent and inherit responsibility for the other 80 percent without noticing.
What OIDC Actually Gives You
OpenID Connect adds an identity layer on top of OAuth 2.0. In practice, it lets your application rely on an identity provider such as Google, Microsoft Entra ID, Okta, Auth0, WorkOS, or Clerk to authenticate the user and issue standardized tokens.
That changes your application from:
- collecting passwords directly
- validating credentials
- running recovery flows
to:
- redirecting the user to an identity provider
- validating the returned tokens
- creating an application session
That is a much better place to be for most SaaS teams.
The Flow You Usually Want
For web apps, the default should usually be Authorization Code Flow. If the client is a browser-based app, use PKCE as well.
At a high level:
- The user clicks "Sign in".
- Your app redirects them to the identity provider.
- The identity provider authenticates the user.
- Your backend exchanges the authorization code for tokens.
- Your application validates the token claims and creates its own session.
The important detail is that your product does not need to handle the password directly.
What You Still Need to Implement
Delegating authentication does not eliminate all responsibility. It narrows it.
You still need to:
- validate issuer, audience, expiry, and signature correctly
- use
stateandnoncewhere required - create and rotate your own application session safely
- separate authentication from authorization
- model users in your database with stable identifiers
A common mistake is to store only the email address as identity. That is not stable enough on its own. In OIDC systems, the durable identifier is usually the provider plus the sub claim.
type Identity = {
provider: "google" | "microsoft" | "okta";
subject: string;
email: string;
emailVerified: boolean;
};
function getIdentityKey(identity: Identity) {
return `${identity.provider}:${identity.subject}`;
}
That avoids subtle account-linking bugs later.
A Better Mental Model
Do not think of OIDC as "social login".
Think of it as moving a high-risk capability to software that is built, audited, and staffed specifically for identity.
That does not mean every provider is automatically configured well. It means the default building blocks are materially stronger than what most product teams create under time pressure.
When Building It Yourself Makes Sense
There are cases where owning authentication is justified:
- you are building an identity product
- you have hard regulatory constraints that rule out external providers
- you need offline or air-gapped authentication models
- you have the in-house security maturity to operate it properly
Those cases exist. They are just less common than engineers like to think.
For a typical SaaS product, the real question is not "can we build auth ourselves?" It is "why would we choose to own this liability?"
Further Reading
Related Writing.
Continue with closely related articles on software engineering, architecture, and implementation trade-offs.
WebAuthn Improves Authentication. JWTs Only Describe the Session Shape.
WebAuthn and JWTs are often compared incorrectly. WebAuthn secures how a user proves identity, while JWTs are one way to carry session state afterward.
HttpOnly Cookies vs `localStorage` for Browser Session Security
Tokens in `localStorage` are easy to implement but easier for XSS to reach. HttpOnly cookies reduce that exposure, though they still require good CSRF and session design.