This afternoon I clicked Continue with LinkedIn on my dashboard's login screen, watched it bounce through LinkedIn's consent page, and land back signed in. Third new provider of the day. When the session started, Magic knew exactly one way to sign somebody in over OpenID Connect — Google. When it ended, it knew eight, speaking two different OAuth flows.
Let me get the honesty out of the way first, because it is half the point of this article: Claude wrote essentially all of it. The Hyperlambda, the TypeScript, the settings dialog, the migration script. My contribution was reviewing everything, clicking through every flow against my own backend, and catching the one bug the model could not see — more on that one below, because it is the best part.
The other half of the point is why one afternoon was enough. It was not the model. It was what the model was building on.
The ledger
| Before | After | |
|---|---|---|
| Sign-in providers | 1 (Google) | 8 — + GitHub, LinkedIn, Microsoft Entra, Okta, Auth0, Keycloak, Slack |
| OAuth flows | implicit only | implicit + authorization-code with PKCE |
| New backend files | 11 Hyperlambda files + 1 SQL migration | |
| Recompiles | 0 | |
| Backend restarts | 1 (and I can tell you exactly why) | |
| Wall clock | one afternoon, inside a longer dashboard session |
A provider is a file
Here is the architectural decision doing most of the work. Magic's backend does not have a provider list anywhere. It has a naming convention: any dynamic slot named magic.openid.providers.is a sign-in provider. The endpoint that feeds the login screen enumerates the vocabulary, executes whatever it finds, and returns the union.
So "adding Okta support" means dropping a roughly seventy-line declarative file into a folder. The file knows Okta's issuer, which configuration keys hold the client ID and secret, and which flow to speak. That is the entire integration. The new OpenID settings dialog in the dashboard discovers configurable providers the same way — from the backend's slot list, not from a hardcoded array in the frontend — so the day somebody writes magic.openid.providers.gitlab.hl, the dropdown already knows about it without a frontend release.
Seven providers stopped being seven integrations. They became seven files that differ in about ten lines each.
The second flow
The pre-existing Google support used the implicit flow — the provider hands an id_token straight to the browser. Fine for Google, but the modern providers are authorization-code shops, and the industry has deprecated implicit for good reasons. So the real work of the afternoon was teaching the backend the code + PKCE flow: a state cache binding each sign-in to a one-shot transaction, S256 challenges, discovery-document caching per issuer, and a server-side token exchange.
Two properties of that exchange are worth calling out. The PKCE verifier is generated server-side and never enters the browser — most single-page apps keep it in session storage, which is exactly where malicious extensions go looking. And the client secret lives in the server's configuration and travels only from backend to provider. All of it is plain Hyperlambda — including base64url(sha256(verifier)), which turned out to be an eight-line pipeline of hash, encode and character-swapping.
GitHub got to be the awkward guest at the party: no OIDC, no id_tokens, no discovery document. Its provider file declares its token and user endpoints outright, and the exchange asks GitHub's API who the access token belongs to — accepting only email addresses GitHub has actually verified. That flag matters more than it looks: anyone can add someone else's address to their GitHub account without proving ownership, and an unverified email would have walked straight into the account matching it.
Zero recompiles is not a figure of speech
Hyperlambda endpoint files are read when they are hit. Save the file, and the next HTTP request runs the new code. No build, no deploy, no restart, no container image. Which means the debug loop this afternoon was: click the sign-in button, read what actually happened, fix the file, click again. Seconds per iteration, against the real provider, with real tokens.
Two war stories to make that concrete.
LinkedIn accepted the authorize request, sent back a code, and then answered the token exchange with 401 invalid_client — "Client authentication failed" — for a client ID it had just accepted and a secret I had triple-checked. Here is what the failure looked like from the login screen:

And here is what it looked like from the server — the actual execution state of the exchange endpoint, LinkedIn's response headers and all:

The diagnosis: LinkedIn does not support PKCE, and its token endpoint maps "you sent me a parameter I do not like" onto an error that blames your credentials. The fix was an argument — the shared sign-in machinery grew a pkce:bool option, LinkedIn's file passes false, and every other provider kept its challenge. File saved, button clicked, signed in. Total elapsed time from diagnosis to working login: about two minutes, none of them spent compiling.
The one restart of the afternoon? Provider files register themselves at startup — slots.create runs when the backend boots — so new providers are the single boot-time thing in this whole story. Everything downstream of the redirect is live per request.
The bug my reviewer caught
Every article I write about AI-built software gets a bug ledger, and this one has a centerpiece. GitHub sign-in kept failing with "Not a recognized OpenID provider" — while the trace showed the GitHub provider entry sitting right there, issuer matching perfectly:

Claude had copied @for-each/@.dp/# — a Hyperlambda iterator idiom for reaching an outer loop's data pointer from inside a nested loop — into a plain single loop, where it silently resolves to nothing. The comparison matched. The copy copied zero nodes. The emptiness check then threw an error that was technically true and completely misleading. The model stared at its own code and at working code with the same expression and could not see the difference, because in the file it had cargo-culted from, the idiom was correct.
I read the execution trace and pointed at the exact expression. One iterator deleted, sign-in worked. The model wrote a thousand lines that ran on the first try and one expression that could not — and the reason the fix cost ninety seconds instead of a build cycle is, again, the platform, not the model.
Emails are not identities
Instant testing does not just find bugs — it finds design mistakes while they are still cheap. My first successful GitHub sign-in landed me in the dashboard as root. Convenient. Horrifying. The legacy behaviour keyed OIDC users on their bare email address, so any configured provider vouching for [email protected] signed you into whatever account carried that name — including one holding the root role. Change your email at one provider, become somebody else on the platform.
Ten minutes later, OIDC usernames were provider-scoped:

The prefix is prepended server-side from the platform's own provider registration — no user-controlled value can ever forge or shed it — so github:[email protected] can only ever be reached through GitHub verifying that email, and can never collide with root, or with any other provider's idea of me.
Which created a versioning problem: existing installations have OIDC users under bare emails. The Magic answer is a four-statement SQL file dropped into the migrations folder, executed automatically at startup, idempotent by construction — it renames every legacy OIDC user under the google: prefix (Google being the only provider that existed before), roles and profile fields following along. The platform got a breaking identity change and its own upgrade path, and the compiler still was not invited.
The fine print
Big claims, precise edges. Apple is deliberately absent — Sign in with Apple requires a form-post response mode the browser flow does not speak, and mints client secrets as signed ES256 JWTs; it needs a dedicated callback endpoint, and it can wait. Slack's PKCE tolerance is untested — if it objects the way LinkedIn did, the fix is the same one-line argument, but I am not claiming a test I have not run. Microsoft Entra's trust boundary is the tenant admin — the email claim there is only as verified as the directory you point at, which is why the provider file demands a concrete tenant ID rather than accepting common. And the implicit flow stays for Google, but the code flow is the future, and now it is the default for everything new.
What this actually demonstrates
The 46,000-line dashboard rewrite made the argument that AI compresses the reading and parity-checking of a big port. This afternoon makes a different one: AI plus a dynamic backend compresses the iteration loop itself. Seven external services, each with its own quirks, each requiring real round-trips against real consent screens — and every diagnosis-to-fix cycle took seconds, because the unit of deployment is a file and the unit of testing is a click.
The model wrote the code. I supplied judgment, distrust, and one pair of eyes on an execution trace. The platform supplied the property that made the whole loop fast enough to finish before dinner. All three were necessary. Only one of them is new.
Magic is MIT-licensed and open source — the repository is at github.com/polterguy/magic, with documentation at docs.ainiro.io. If you want a cloudlet of your own to hang seven sign-in buttons on, it is one copy-paste on a $6 DigitalOcean droplet.