Everyone Is Already on the List Link to heading

Go and put your email address into Have I Been Pwned. I will wait.

For almost everyone reading this, the answer is yes. Not once, either. Your email address, your phone number, your date of birth, your residential address, your mother’s maiden name, the name of your first pet, the last four digits of a card you cancelled in 2019 - all of it has leaked from somewhere, been aggregated with everything else that leaked, and is sitting in a list that costs less than a coffee to buy.

That is the actual state of play, and it has a consequence that most systems still have not internalised. Every authentication method built on the assumption that you know something nobody else knows is now broken. Not weakened. Broken. Knowledge-based authentication is dead, security questions are a joke, and if your account recovery flow asks for a date of birth and a postcode then your account recovery flow is a public API for taking over accounts.

The genie is out of the bottle and it is not going back in. So the honest starting position for anyone building anything in 2026 is this: assume the attacker already has everything a legacy system would have asked the user to prove.

We Worked Out How to Fix This a Long Time Ago Link to heading

Here is the part that should be embarrassing for the industry.

This is not an unsolved problem. It is not an open research question. The patterns that fix it have been written down, standardised, implemented, interoperability tested and given away for free for well over a decade.

OpenID Connect landed in February 2014. It sits on top of OAuth 2.0 and does the boring, unglamorous work of telling a relying party who the user is, with signed assertions, key rotation via a published JWKS endpoint, and a discovery document so the client can work most of it out on its own. It is not elegant in every corner, but it is well specified, widely deployed and thoroughly attacked, which is worth more than elegance.

PKCE followed in 2015 and closed the authorisation code interception hole for public clients, and is now simply mandatory practice. Sender-constrained tokens via mTLS and later DPoP closed the “possession equals authority” hole for bearer tokens. The backend-for-frontend pattern, where the tokens never touch the browser at all and the client holds nothing but an opaque session cookie, has been the documented answer for high-assurance applications for years. If your threat model says a token must never be readable by client-side JavaScript, that is a solved architectural problem with published guidance and reference implementations.

WebAuthn and passkeys then did something genuinely new: they moved the secret into hardware and made it non-exportable, which means phishing a passkey is not a matter of tricking the user into typing it, because there is nothing to type. The credential is bound to an origin and lives in a secure enclave. You cannot read it out and you cannot post it to an attacker’s server, no matter how convincing the email is.

So the toolkit is complete, and it has been complete for a while.

And It Is Free Link to heading

The second embarrassment is cost, because there almost is not any.

If you want a commercial product with a support contract and someone to shout at, Ping and Okta have been doing this for a very long time and they are good at it. If you want open source you can run yourself, Keycloak and Ory will both do the job, and Zitadel if you want something more recent. None of these are research projects. They are mature, deployed in anger, and boring in the way infrastructure should be.

If you do not want to run anything at all, Firebase Authentication will handle 50,000 monthly active users for no money whatsoever. Fifty thousand. That is not a trial, it is the free tier. For a very large proportion of the applications being built right now, the entire authentication bill is zero dollars, forever.

And social login costs even less than that. Sign in with Google, Apple, Microsoft or GitHub requires no cryptographic expertise, no password storage, no reset flow, no breach exposure on your side, and no ongoing cost. The technical requirement is registering a client with the provider and handling a redirect. That is it. You are done in an afternoon, and the hardest security problem in your application has been handed to an organisation with a security team larger than your entire company.

There is no serious argument that doing this properly is expensive or difficult. It is neither.

So Why Is It Still a Hot Mess? Link to heading

I have thought about this a lot, because it is genuinely strange. The vendors are large and well credentialed. The standards are open. The open source options are good. The free tier is generous. And yet the web is still full of forms that take a username and a password over a connection you have to hope is doing its job, applications that email you your existing password when you click “forgot”, and services storing PII they had no reason to collect and no ability to protect.

The first reason is legacy, and it is the honest one. There is an enormous amount of software still running very large corporate services where the authentication layer was designed twenty years ago and everything since has been built on top of it. Replacing it is not a sprint. It is a multi-year programme touching every integration, every batch job, every partner connection and every internal tool nobody remembers exists. It costs real money, sometimes ten million dollars of real money, and someone has to sign that cheque.

That someone is looking at a spreadsheet. And from where they are sitting, the old system still works. Nobody is complaining. Revenue is fine. There is no incident on the board report. Spending eight figures to arrive at exactly the same user-visible outcome is a very hard business case to make, right up until the morning it becomes the only thing anybody wants to talk about. The economics of prevention are terrible and always have been.

The second reason is familiarity, and this one is on us.

Username and password is what everybody knows. It is what the tutorial shows. It is what the framework scaffolds. Ask any AI coding agent to add authentication to a project and watch what it reaches for by default: a users table, an email column, a bcrypt hash, a session cookie. It does that because that is what the overwhelming weight of its training data does, which is another way of saying that is what we have collectively written down a million times. You do it. I do it. The agent does it because we did it.

Familiar and easy beats correct, every single time, unless someone senior enough makes correct the default. That is the whole mechanism. There is no deeper mystery.

The third reason is organisational. Authentication is usually treated as a feature of an application rather than as infrastructure, which means it gets built by whichever team happens to be building the application, to whatever standard that team happens to hold, on whatever timeline the product manager negotiated. Nobody owns it across the organisation. The security team finds out at the penetration test, which is to say after it has shipped, at the point where the only available options are “accept the risk” and “delay the launch”. You can guess which one wins.

Zero Trust Was Not Enough Either Link to heading

The industry’s answer to all this for the last several years has been Zero Trust, and Zero Trust is right as far as it goes. Stop trusting the network. Stop assuming the corporate VPN means anything. Verify explicitly, every time, for every resource.

But notice the load-bearing assumption underneath it: that when you verify, the verification means something. Zero Trust tells you to check the credential rather than the network location. It does not help you when the credential material itself is public.

And that is where we are. If the second factor is an SMS to a number that is on a list, and the number can be moved to a new SIM with a phone call to a call centre that will verify the caller using a date of birth that is also on a list, then you have not built a second factor. You have built a longer first factor. The chain is only as strong as the weakest identity proofing step in it, and for most organisations that step is a human on a phone with a script and a queue length target.

So the rule I would write on the wall is short: stop trusting anything a user can tell you. If it can be typed, spoken, emailed or read off a screen, treat it as public, because it probably is. What is left is possession of something that cannot be copied and cannot be exported - a key in hardware, bound to an origin, that signs a challenge and never leaves the device it was born on. That is the only category of proof that survives contact with the current threat environment.

Everything else is theatre with a compliance tick next to it.

What Actually Needs to Happen Link to heading

None of this requires new invention. It requires doing the things that already exist.

Stop building your own. Whatever you are about to write, one of the four or five identity providers named above already does it, has already had it attacked by better people than either of us, and will run it for free or nearly free. There is no competitive advantage in your login form. There is only downside.

Stop collecting what you cannot protect. If you do not need a date of birth, do not ask for one. Every field you collect is a field you can leak, and the aggregate of all those leaks is precisely the problem described at the top of this post. The safest PII is the PII you never had.

Move to possession-based authentication, properly. Passkeys as the primary factor, not as an optional extra buried in account settings that three percent of users will ever find. If you are running a consumer service, the default enrolment path should be a passkey, and password should be the fallback you are actively trying to retire.

Sender-constrain your tokens. A bearer token is a bearer instrument; whoever holds it is the owner, and that is a terrible property for something that gets logged, cached, copied into a support ticket and pasted into a chat window. Bind it to a key. Make theft insufficient.

And keep tokens away from the browser when the assurance requirement is high. The pattern exists, it is documented, and it works.

The Part That Actually Worries Me Link to heading

All of the above is a decade-old argument that I should not still be making, and I would not have bothered writing it down except for one thing.

We are now, at some speed, handing this stack to software that acts on our behalf. Agents are already logging in, already clicking through consent screens, already holding credentials, already making purchases. They are doing it through interfaces designed on the assumption that there is a human at the other end who will read the warning, notice the anomaly and hesitate before approving something strange.

There is not a human at the other end anymore. And we have not agreed on how to tell.

That is the scary part. And it’s also the next post.


This post is part of a series on identity and agents. Next: Who Authorised the Bot? on what identity looks like when the user is not a person, and Let the Good Bots In on what to do about it.