Blog

August 3, 2026

In 2012 the man holding the pen on OAuth 2.0 quit. His reason reads like a forecast.

Single sign-on has been a settled standard for over twenty years, yet SAML is still the line item that stalls a deal or triggers a call to sales. How enterprise auth became a premium upsell, who built that world and who warned us about it, how wide the moat really is and what it is made of, and the one force quietly draining half of it.

July 31, 2026

We proved every alert could fire. Most of them couldn't.

Monitoring is the code that only runs during a disaster, the one moment you cannot afford to find it broken. So we built a harness that drives a real signal for every alert rule and refuses to pass until the notification physically lands in a capture relay. The first run found our flagship error-rate alert stone dead, then found worse: a retention setting that had quietly stamped instant expiry on our logs, so a whole class of alerts evaluated over nothing while ingestion looked healthy.

July 30, 2026

We moved our images to a CDN and the origin kept serving them

A hundred megabytes of documentation screenshots moved to Cloudflare R2, behind a CDN base the app derives from its own hostname at runtime. It worked in the browser and did nothing to the origin's traffic, because the pages a crawler sees are prerendered on 127.0.0.1, where a hostname tells you nothing. The fix is a placeholder the HTML carries out of the build and the edge fills in per request, and the reason it has to work that way is a read-only filesystem.

July 29, 2026

We turned off a dangerous default without migrating a single row

Just-in-time provisioning was on by default, so a federated identity provider could create accounts in a customer's tenant simply by asserting that someone existed. Turning that default off is a one word change. Turning it off without silently changing the behaviour of every connection already stored, none of which ever wrote the column, is the actual problem, and the trick that solved it also explains why a default is API surface.

July 28, 2026

A BFF for many tenants, and the one request that arrives with no cookie

A backend-for-frontend takes OAuth tokens out of the browser and leaves it holding nothing but an opaque cookie. Serving many tenants from one BFF is mostly a plumbing problem, until back-channel logout arrives: a server-to-server POST with no cookie, no session, and no browser, where the only thing identifying the tenant is a claim inside a token you have not verified yet.

July 27, 2026

The admission webhook that said yes and took us down anyway

We added a signature-checking admission webhook to our Kubernetes cluster so unsigned images could never run. Hours later one Deployment had 2,242 ReplicaSets, a fresh one every three seconds, and no available pods. The webhook logs showed it admitting every single request. The damage came from the one word in "mutating admission webhook" that we had stopped reading.

July 23, 2026

Our login hung for exactly 10 seconds. Our own security headers did it.

A returning user watched "Loading…" for precisely ten seconds before login appeared, every time. The round number was the tell: our SSO library opened a hidden silent-renew iframe against an auth host serving X-Frame-Options: DENY and frame-ancestors 'none', so the frame never loaded, never errored, and the library waited out its full timeout. Clickjacking defense and OIDC silent renew, each correct, hung in the seam between them.

July 22, 2026

HMAC ate my autocomplete: type-ahead search over encrypted emails

We encrypted every email address at rest, and the admin search box quietly stopped autocompleting — no error, no log line. A blind index restores exact-match lookup over ciphertext, but HMAC's entire job is to destroy the ordering that prefix search needs. Here's the design that got type-ahead back — index every prefix as its own value — and the three traps that taught us searchable encryption is a systems problem, not a crypto problem.

July 20, 2026

One ES256 migration, three ways to mangle a valid signature

We moved JWT signing from RS256 to ES256 and every token we minted was cryptographically valid — and rejected. Three times, for three different reasons: DER where JWS wants raw R‖S, integer padding that fails one token in 128, and a base64 dialect that changed under our feet. Here's how to read the mangled bytes and name the guilty layer from the signature length alone.

July 20, 2026

Every token validation hit our origin. Now the JWKS lives on the edge.

The two documents a service reads to validate one of our JWTs — the discovery doc and the JWKS — are the most-requested, least-secret things we serve, and every fetch used to come all the way back to origin. Caching them on Cloudflare was the easy part. The hard part is that a stale public-key cache must never reject a valid token, and here's the rotation-safe ordering that makes an aggressively-cached JWKS safe.

July 18, 2026

Duende IdentityServer's cheapest license can't do SAML

Duende's LITE tier is $5,750/yr but caps at 2 client apps and ships no SAML at all. The moment you need enterprise SSO or a third app, you're on STANDARD at $12,500. A look at what Duende actually costs a real product, and the client-count tax nobody warns you about.

July 17, 2026

The three lives of my JWT signing key

One private key signs every token our auth server issues, and it has moved twice in its life: born inside the process, exiled to a vault it can't leave, then reincarnated as a smaller, faster kind of key entirely. Each move happened on a live issuer with tokens in flight. Here's what custody and algorithm changes actually take, and the quiet regression that rode in on a "more secure" commit.

July 16, 2026

The SAML signature was valid. That was never the problem.

Your SP validated the assertion signature, pinned the certificate, and logged the user in. Every check passed. I still replayed a login I captured an hour ago. The trick was not breaking the signature. It was deleting a field the signature never covered.

July 14, 2026

We required MFA. One query parameter turned it off.

We challenged the second factor on the login page and never re-checked it at the token endpoint. So a valid password plus a hand-edited return URL walked straight past MFA. The fix, and the rule it taught us: MFA is a property of the session you issue, not a step in the login screen.

July 14, 2026

The SCIM tax: provisioning is plumbing, not a premium

SCIM is a standardized CRUD API whose most important verb is the one that turns an ex-employee's account off. Vendors price it at $125 a month per connection, or park it in the enterprise tier next to SSO. Here is what a SCIM connection actually costs to serve, and why charging for deprovisioning is charging for security.

July 12, 2026

Your backup quietly resurrects the users you deleted

Azure Table Storage and DynamoDB have no delete markers, so a deleted row just vanishes, which means your incremental backup never sees the delete at all. Restore that backup and the user you offboarded is back, with the password you rotated away from. Here is why deletes have to be first-class, and the one table that makes them so.

July 11, 2026

Every per-IP control we shipped was bypassable with one header

Our rate limiter and lockout counter keyed off the client IP. Behind nginx that IP came from a header the client could set, so one rotating header handed every request a fresh IP and every per-IP control fell over. The one-line spoof, and the fix that is fiddlier than it looks on Kubernetes.

July 10, 2026

We run a whole auth system on stores that only know get and put

Azure Table Storage gives you a partition key, a row key, and almost nothing else. No joins, no secondary indexes worth the name, no increment. We run a multi-tenant auth system on it anyway. The patterns that make it work, and what you give up versus SQL.

July 9, 2026

Your password-reset form is a free email cannon

An unauthenticated password-reset endpoint with no per-email rate limit is not just your problem. Point it at someone else's inbox and you have turned their reset form into a spam cannon, billed to your sender reputation. The fix, without leaking which emails have accounts.

July 7, 2026

We gave away every feature on the free tier, including the ones rivals reserve for enterprise

Most auth vendors fence SSO, SCIM, MFA, and audit logs behind an Enterprise plan. We put all of them on the $0 tier. Here is the unit economics that makes giving features away survivable: the wall was never features, it is active users and support.

July 6, 2026

An identity provider told us who you were, and we believed it

Single sign-on means trusting the identity provider. We trusted it for one thing too many: we resolved returning federated users by the email in the assertion, so any connection could assert someone else's address and land on their account. The fix wasn't more validation. It was changing the identity join key from email to a per-provider subject the asserting party can't forge.

July 6, 2026

Scale-to-zero can't count to one

Azure Container Apps promised to stop us paying for an idle auth cluster. We costed the migration and killed it, not over price or cold starts, but because serverless is missing the only number a leader-elected auth backplane actually needs.

July 3, 2026

The SSO tax, in dollars: what SAML actually costs at Auth0, WorkOS, and Clerk

The 'feature tax' as a real invoice. Three buyer scenarios (first enterprise deal, a handful of customers, SSO as table stakes) priced against each vendor's public rates. Plus the quieter surcharge nobody warns you about: running out of OAuth clients mid-integration.

July 1, 2026

Our service discovery caught its own failure and switched itself off

A three-replica cluster kept disagreeing with itself. The cause was in our own code: the peer-discovery routine had a catch block that disabled discovery when multicast failed, and on managed Kubernetes multicast always fails. Here's why we deleted gossip entirely and elected a leader with a blob lease instead, and why the membership you can open in Storage Explorer beats the membership you have to infer.

June 30, 2026

Build vs buy auth: the bill you don't see coming

Login is a weekend. The bill arrives after: signature verification, SCIM deprovisioning, MFA, audit logs, key rotation, breach liability, and on-call for a tier-zero system. An honest build-vs-buy, including when building is right.

June 28, 2026

OIDC or SAML: which one you actually need

OIDC or SAML? You rarely get to choose. What each protocol is, when each wins, the XML-signature footguns that make SAML dangerous to hand-roll, and why a B2B product ends up needing both.

June 26, 2026

Support that speaks your language. Without making you ask.

Authagonal's support system: real-time messaging with no refresh, transparent two-way AI translation pinned from the customer's first message, and a thread that stays fully translated even over email. Simple, good-looking, and actually capable.

June 25, 2026

One hyphen, two tenants, one signing key

Two of our tenants were quietly the same tenant: same database, same token-signing key, different names. The cause was a one-line helper that sanitized slugs by stripping hyphens, turning a unique identifier into a non-unique one. Here's how a lossy string transform became a cross-tenant takeover, and why we found it before you did.

June 23, 2026

Green unit tests don't mean you can go live

We sell authentication, so our most important test is one that becomes a customer: a single Playwright run that signs up a tenant, configures every feature, points a real app at it, logs a real user in with the right claims, then backs up, restores, and deletes itself. How exhaustive end-to-end testing earned our go-live confidence.

June 22, 2026

We log into our own admin console with our own SAML. Here's what it caught.

Our staff console authenticates through Authagonal itself, with Entra SSO and SCIM doing the provisioning. Being your own customer surfaces the edge cases no test suite thinks to write. Here are the ones that mattered.

June 22, 2026

Take your Terraform state off the public internet (without standing up a VPN)

Your Terraform state is a full map of your cloud plus a pile of secrets, and by default it lives behind a public endpoint. Here's how we bootstrapped remote state from nothing, put it behind a private endpoint, and reached it through a zero-trust connector instead of paying the VPN gateway tax, without ever locking ourselves out.

June 21, 2026

Importing users without a password reset

"Everyone resets their password" is a migration choice, not a law of physics. How to carry your users' existing password hashes across an identity-provider move so the cutover is invisible, and the one case where you genuinely can't.

June 20, 2026

The feature tax: stop paying to flip switches that cost nothing

The auth industry meters permission, not cost. It gates SSO, MFA, and provisioning behind tiers and per-connection fees for features that cost effectively nothing to provide. Here's the distinction that pricing pages hope you never draw.

June 18, 2026

Leaving self-hosted Duende: what migrates, and what you stop operating

Moving off self-hosted Duende IdentityServer to managed auth — what actually migrates from the SQL config and ASP.NET Identity, and the operational cost you stop carrying.

June 16, 2026

Leaving Auth0: what actually moves — and the one thing they won't hand over

A practical look at what an Auth0 migration really involves: what's easy, what's fiddly, and the one thing Auth0 genuinely won't give you.