Count something for me: how many times has your organization built a login? Not bought, not reused - built. A login form here, a token check there, a password reset flow in that legacy app, a slightly different session handling in the new one. If the answer is “more than once”, you are carrying more risk than you think - and I would bet the answer is more than once.
We preach DRY - don’t repeat yourself - for utility functions and date formatting. And then we cheerfully re-implement the most dangerous code in the entire system, project after project. Let’s talk about why that is exactly backwards.
Why we keep rebuilding the same door
Nobody re-implements auth out of malice. It happens for mundane reasons:
- “It’s just a form.” Login looks trivial from the outside - two fields and a button. The dangerous parts (session fixation, timing attacks, token expiry, redirect validation) are invisible until someone exploits them.
- Project pressure. The new app needs auth this sprint. Copying the old project’s code - or asking an AI to generate a fresh version - feels faster than doing it properly once.
- Ownership gaps. Auth belongs to everyone and no one. Without a designated home, every team solves it locally.
The result: five projects, five subtly different implementations of the same security-critical flows. Each one reviewed once, tested differently, patched on its own schedule - or not at all.
Repetition is uniquely dangerous in security code
Here is the asymmetry that makes this a special case. If you duplicate a date-formatting helper, the cost is aesthetic - two places to update, mild embarrassment. If you duplicate an authentication flow, the cost is an attack surface:
- Every copy is a fresh chance to get it wrong. The classes of bugs are well known - broken access control and authentication failures sit at the top of the OWASP Top 10 year after year. A rewrite does not dodge them; it re-rolls the dice on all of them.
- Fixes do not propagate. You patch the token validation in project A. Projects B through E keep the vulnerability - and nobody remembers they have it, because nobody has an inventory of five implementations.
- Review does not compound. A single shared component gets reviewed, pentested, and hardened again and again; its quality accumulates. Five scattered copies each get one hurried review at birth, then silence.
- AI multiplies the copies. Agents will happily generate you a “working” login in thirty seconds - a brand-new, never-reviewed implementation, with confident-looking handling of exactly the edge cases that bite. Generation got cheap; scrutiny did not.
This is why OWASP’s own guidance is blunt: define shared, reusable components for authentication, authorization, validation, and cryptography. When every team builds its own, the inconsistencies between them are where attackers live.
What belongs in the “build once” box
You do not need to centralize everything. You need to centralize the layers where a mistake is a breach:
- Login and session handling - credential flow, session creation/invalidation, cookie flags, logout that actually invalidates server-side.
- Token handling - issuing, validating, refreshing, and storing tokens; clock skew; audience and issuer checks.
- Access control - the guard components and role/permission checks your UIs and APIs use (one guard, not one per app).
- Password and secret flows - reset, rotation, storage. The flows attackers script against first.
- Input validation and output encoding helpers - the anti-injection toolbox.
- Crypto usage - never hand-rolled, always wrapped: one blessed way to hash, sign, encrypt.
Concretely, that can be as simple as one internal package that every project consumes:
// @yourorg/auth — the only place this logic exists
import { AuthClient } from "@yourorg/auth";
export const auth = new AuthClient({
provider: import.meta.env.PUBLIC_IDP_URL, // your IdP (OIDC/OAuth2)
onSessionExpired: () => location.assign("/login"),
});
// Every app consumes the same hardened pieces:
// <PermissionGuard required="reports:view"> ... </PermissionGuard>
// await auth.fetch("/api/reports") — token attach + refresh handled once
The point is not this exact shape. The point is that session expiry, token refresh, and permission checks exist once - versioned, tested, and owned - instead of five times, differently.
How to get there without a big bang
- Prefer boring, vetted foundations. An established identity provider and protocol (OIDC/OAuth2) at the boundary beats anything you will write. Your shared package then wraps configuration and conventions - it does not reinvent the protocol.
- Extract from the best existing implementation. Do not design a grand framework in a vacuum. Take the auth code from your most battle-tested project, harden it, and make it the package.
- Give it an owner and a version. A shared security component without an owner is just a sixth copy. Someone maintains it, someone reviews changes, releases are versioned so apps upgrade deliberately.
- Concentrate your paranoia. Point your code reviews, your tests, and your pentests at this one component. That is the entire payoff: scrutiny compounds when it has a single target - and a fix ships to every app with one version bump.
- Wrap, don’t fork. When a project needs something special, extend the component behind an option or a hook. The moment someone copies it “just for this one project”, you are back to five doors.
One honest caveat, because DRY has a failure mode too: do not turn this into abstraction theater. The goal is not a clever framework that does everything - it is a small, boring, well-guarded box of the flows that can hurt you. Business logic can stay repetitive and local; nobody breaches you through a duplicated date formatter.
The vault door
No bank welds its own vault door. Not because their engineers could not bolt steel plates together, but because a vault door is a category of thing where “pretty good” is worthless. So a handful of specialist manufacturers build doors that are certified, attacked by professionals, improved after every attempted break-in - and every branch installs the same proven door.
Now imagine a bank chain where every branch manager welded their own: five branches, five doors, each with its own quirks, each tested only by whoever eventually attacks it. That is precisely what re-implemented login flows are - homemade vault doors, guarding your most valuable room.
Build the door once. Let professionals attack it. Improve it every time. And when a weakness is found, fix one door - and know that every branch is safe by morning. That is DRY where it actually matters.