Choosing an identity provider
Every product needs login, and the real decision is not which library but where the auth server runs. Three answers, and the trade is the same one every time: operational burden against control.
The three shapes
| Shape | Examples |
|---|---|
| SaaS | Auth0, Clerk, Cognito, Entra ID, Okta |
| Self-hosted server | Keycloak, Ory, Authentik, OpenAuth |
| Embedded library | Django allauth, Authlib, NextAuth |
SaaS is someone else’s uptime, someone else’s security patches, and a per-monthly-active-user bill that is trivial at 1,000 users and a line item at 1,000,000. The lock-in is real but bounded: users and credentials are exportable, though password hashes sometimes are not.
Self-hosted is a standalone server you run. You own the uptime and the upgrades, and you get data residency, unlimited users at infrastructure cost, and no vendor pricing changes.
Embedded means auth lives inside one application. Simplest until you have a second application, at which point you either duplicate it or promote one app into an accidental auth server. That promotion is the moment teams wish they had picked a centralized server.
The question that actually decides it
How many applications need to share a login? One means a library is fine. Two or more, or any third-party client, means you want a centralized server - SaaS or self-hosted - because a shared session across applications is what SSO means and it needs one issuer.
OpenAuth, and the self-hosted category
OpenAuth (openauth.js.org, from the SST maintainers) is a standards-based OAuth 2.0 / OIDC server designed to be easy to self-host - deployable to AWS Lambda or Cloudflare Workers rather than run as a long-lived JVM service. It is positioned against Auth0 and Clerk: the same centralized-server model without the SaaS bill or the data leaving your account.
It is written in TypeScript, which matters less than it sounds. A centralized auth server is language-agnostic over the wire - it speaks OAuth 2.0, so a Python backend integrates with it exactly as it would with Auth0 or Keycloak. If a job description lists OpenAuth for a Python role, what they need is an engineer who understands OIDC flows and token validation, not a TypeScript developer.
The category comparison worth knowing:
| Server | Its character |
|---|---|
| Keycloak | Full-featured, heavy, enterprise defaults |
| Ory | Composable services, API-first, no UI |
| Authentik | Modern UI, good for internal tools |
| OpenAuth | Minimal, serverless-friendly, code config |
What integration looks like from Python
The same regardless of which you picked, which is the point of the standards:
async def verify(token: str) -> dict:
jwks = await cache.get_jwks(ISSUER)
claims = jwt.decode(
token,
jwks,
audience=AUDIENCE,
issuer=ISSUER,
)
return claimsYour service is a resource server: it never sees a password, it validates a signed token against the provider’s published JWKS. Fetch that key set from the issuer’s discovery document rather than hard-coding it, and cache it with respect for key rotation - a provider rotating signing keys while you hold a stale cache is a self-inflicted outage.
# .well-known/openid-configuration gives you everything
DISCOVERY = f"{ISSUER}/.well-known/openid-configuration"This is why migrating between providers is mostly a configuration change. What does not migrate cleanly is anything you used outside the standard - custom claims, provider-specific rules engines, Auth0 Actions. Depth of use, not the provider, is what creates lock-in.
The build-it-yourself question
An interviewer may ask whether you would write your own. The answer is no, and the reason should be specific rather than superstitious: implementing OAuth 2.0 correctly means getting PKCE, state validation, token rotation, replay protection, and timing-safe comparison right, and the failure mode is a silent breach rather than a visible bug.
Storing a password hash correctly is the easy part. See SSO Attack Vectors for what the specification exists to prevent.
Related
Interview angle 6
- “How do you choose an identity provider?” - by where the server runs and how many applications share the login. One app can use an embedded library; two or more need a centralized issuer, either SaaS or self-hosted. The trade is operational burden against control and per-user cost.
- “What is OpenAuth?” - a standards-based, self-hosted OAuth 2.0 and OIDC server from the SST maintainers, designed to deploy to serverless infrastructure. It occupies the same slot as Keycloak or Ory: a centralized auth server on your own infrastructure instead of a SaaS bill.
- “It is TypeScript - does that matter for a Python backend?” - no. A centralized auth server is language-agnostic over the wire, so a Python service integrates the same way it would with Auth0: validate the token against the issuer’s JWKS. What is needed is OIDC understanding, not the server’s language.
- “How does your service validate a token?” - as a resource server. It never handles credentials; it fetches the issuer’s JWKS from the discovery document, verifies the signature, and checks issuer, audience and expiry. Cache the key set but honour rotation, or a provider key roll becomes your outage.
- “What creates lock-in?” - depth of use, not the vendor. Standard OIDC migrates as configuration; provider-specific extensions like custom rules engines or Actions have to be rewritten.
- “Would you build your own?” - no. Correct OAuth means PKCE, state validation, token rotation and replay protection, where mistakes fail silently as breaches rather than visibly as bugs.