Identity is the first thing you integrate and the last thing you can change.

Auth looks like a checkbox on the plan. Pick a provider, wire it up, move on to the real work. It is actually the one decision that sets who can ever use the thing you built — and it is the decision you are least able to revisit later, because by then it is welded to every page, every service and every integration you added afterwards.

Standing up something new and unsure where the auth should live? It might be worth a quick chat.

Che ShivaRoughly a four minute read
Che Shiva
Che ShivaSystems Builder

You do not choose your auth. You inherit everyone else’s.

This week I deployed a piece of open-source software onto a server I run. It went well, in the way these things go well. The container came up, the health check passed, the certificate issued, the reverse proxy took traffic on the first reload. Everything I could point at was green.

Then I read the front end, and found that every page in the product is gated behind one identity provider. Not configurably. One import, at the top of one file, and a middleware that redirects anything without a session. There was no username-and-password option, no trusted-header bypass, no flag to turn it off for a private network. I checked for all three, because I did not believe it the first time.

That is not a bug and nobody did anything wrong. It is a decision somebody made in the first week of the project, when it was the smallest decision on the board, and it quietly became the shape of the product. My options were to adopt their choice or to fork the thing and own the fork forever.

This is the part people get wrong about identity. You do not choose your auth once, at the start, on your own terms. You inherit somebody else’s choice every time you adopt anything — and each one of those inherited choices has to be reconciled with the ones you already live with.

Four failures, none of them the password.

01

The redirect URI.

The single most demoralising failure in software: the login works. The user authenticates, the provider says yes, and then the browser lands somewhere that does not exist. Everything succeeded except the last hop, and the error the user sees describes none of it.

02

The app does not know its own address.

Behind a reverse proxy, a service builds its callback URL from the address it bound to, which is usually nothing a human could type. Telling it to trust the host header is necessary and not sufficient. You have to name the public origin explicitly, or sign-in succeeds and then dies on a URL only the container believes in.

03

The gate that is off by default.

I have now met more than one system that serves every route unauthenticated unless two environment variables are set, and announces this in a log line at startup. It is honest. It is also invisible, because nobody reads a healthy service’s logs.

04

The allowlist that lives in two places.

An environment variable and a database roster, one layered additively on the other. Remove somebody from one and they are still admitted by the other. That is not a bug you find by reading the code; you find it when the wrong person is still getting in a week after you offboarded them.

Being locked out is loud. Being let in is quiet.

The failure everyone plans for is being locked out. It is loud, it is immediate, and somebody complains within a minute. In practice it is the cheap one.

The expensive failure is being let in. A missing secret usually fails hard and gets fixed the same hour. A permissive default fails silently, and from the outside it looks exactly like everything working — which it is, for the wrong people.

The hardest incident I have had to explain to a room was not a service that was down. It was a service that was up, healthy, fast, and answering honestly to anyone who found it. Every dashboard was green. Every dashboard was measuring the wrong thing.

You cannot get to confidence here by clicking around your own product, because you are always allowed. Your session is warm, your email is on the list, your browser has the cookie. The only test that means anything is the one you run as a stranger.

Decide it first, while it is still cheap.

Identity is the one layer where the cost of changing your mind rises fastest. Spend the hour now.

01

Decide identity before architecture

Who is allowed inFrom whereRemoved howProven to whom
02

Name the public origin

Explicit callback URLNever infer from bind addressSame value in every env
03

Fail closed on purpose

Refuse to start unsecuredLoud, not a log lineDefault denies
04

Test as a stranger

Private windowUnlisted accountAfter offboardingFrom outside the network

Before you wire the second service to the first, decide who is allowed in.