In this article
August 25, 2026
August 25, 2026

Running your own OAuth and OIDC provider in 2026 is an operations problem

Better Auth 1.7 shipped DPoP, back-channel logout, and MCP alignment. Its own upgrade guide shows why the operational half of an OAuth provider costs more.

Explore with AI
Open in ChatGPT
Open in Claude
Open in Perplexity

On August 18 the Better Auth team published v1.7.0. The release adds DPoP sender-constrained access tokens under RFC 9449, OIDC back-channel logout, an MCP package aligned with the 2026-07-28 authorization profile, explicit protected resources with per-resource lifetimes and signing keys, and a long round of SAML and SCIM hardening. That is real protocol engineering, and most auth libraries never get near it.

It's also the part of the job with a finite scope: the spec list ends, but operating the provider doesn't. Running an OAuth and OIDC provider in 2026 is mostly an operations problem: key rotation, revocation fan-out, directory lifecycle, uptime, and owning the next security fix yourself. The sharpest evidence for that is 1.7's own upgrade guide.

Give the protocol work its due

The 1.7 feature list is the list a team writes when it actually runs a provider. Protected APIs get their own permissions, token lifetimes, claims, and signing policy, and the provider enforces which API a token was issued for under RFC 8707. DPoP binds a token to the client that requested it, so a copied token is not enough on its own. Back-channel logout notifies clients that registered a logout URL when the user's provider session ends, and an access token whose session has ended now introspects as { active: false } and is rejected at /oauth2/userinfo. Before 1.7 those tokens lived until they expired. SCIM gained first-class Groups with direct memberships, directory-group-to-role projection, and User fields like department, manager, and employee number. SAML providers can publish old and new signing certificates together so rotation needs no downtime, and IdP-initiated SAML is off by default with InResponseTo validated and Single Logout matched by SessionIndex.

The team also ran 1.7 against the official OpenID Conformance Suite and said plainly that the results are not a certification claim, with some optional OpenID features still unsupported. Engineers who write that sentence are worth taking seriously.

The upgrade guide is the invoice

Everything above ships as a library upgrade, which means the migration lands on your database and your on-call rotation. The 1.7 guide is unusually honest about what that costs.

External accounts are now keyed on the pair (issuer, accountId), with a required Account.issuer column and a unique compound index. Before you touch that schema, the guide tells you to open a maintenance window and stop authentication writes, including background jobs and admin APIs that insert into account directly. Then you find your collisions with the query the guide hands you:

  
SELECT issuer, accountId, COUNT(*) AS accountCount,
          COUNT(DISTINCT userId) AS userCount
FROM account
GROUP BY issuer, accountId
HAVING COUNT(*) > 1;
  

And then you resolve them by hand. The guide's instruction is blunt: "If duplicate rows belong to one user, choose the account record to keep and reconcile its provider configuration, tokens, scopes, and timestamps before deleting the others. If a key belongs to multiple users, stop the migration and establish the owner from trusted provider data. Never merge users by matching email alone."

There are sharper edges under that. If you ran auth migrate against MySQL before backfilling, MySQL's default sql_mode silently writes an empty string into every existing row's issuer instead of failing, and the database then needs repair rather than a backfill. The CLI never emits the statement that makes an existing nullable column non-nullable, so you write that DDL yourself, and on SQLite you rebuild the whole account table to add the constraint. Microsoft Entra accounts move from the app-specific sub claim to the stable directory oid; if you don't have stored Microsoft ID tokens to read oid from, you pause Microsoft sign-in and account linking during the cutover and get the mapping from a trusted Entra export. SCIM state can't migrate in place at all. The configuration, client APIs, schema, and Group model are all replaced, so you stop provisioning, issue new credentials, and ask every customer directory to send all Users and Groups again before resuming traffic. If you were running that on Cloudflare D1, you can't: D1 can't provide the transactions the SCIM-to-SSO bridge needs.

Better Auth's own 1.7 announcement puts the summary plainly: "Do not treat the generated migration as the full upgrade: account identity, OAuth clients, MCP, and SCIM need reviewed manual data steps." Registered OAuth clients are in the same category. Each 1.6 oauthApplication has to be copied or re-registered as an oauthClient, with redirectUrls remapped, old access tokens expired, and the legacy oauthAccessToken table dropped or renamed, and the CLI does none of it. The breaking-change list spans thirteen packages, and the auth CLI now needs Node.js 22.12 or newer.

This is the unavoidable shape of shipping identity infrastructure as a dependency, not sloppy engineering: correctness improvements arrive as data migrations you schedule, staff, and roll back yourself.

Each capability has an operational half

Take the 1.7 headline features one at a time and ask what it takes to keep them running.

Sender-constrained tokens. DPoP is a genuine upgrade over bearer tokens (we've written up RFC 9449 in detail). Operationally, it needs state: verifyAccessToken is renamed verifyBearerToken and now rejects DPoP tokens, DPoP-capable endpoints move to verifyAccessTokenRequest, and supporting DPoP at all requires configuring database-backed verification storage. There's a deployment trap waiting behind any TLS-terminating proxy: the provider computes its own URL for the proof's htu claim, which can come out as an internal address like 0.0.0.0:3000, so a perfectly valid proof gets rejected. In AuthKit, access tokens are JWTs verified against a per-client JWKS that WorkOS publishes at /sso/jwks/<clientId>, carrying sub, sid, org_id, role, and permissions claims, and key rotation and proof storage sit on our side of the API.

Revocation. Back-channel logout is the right mechanism and a fan-out problem in production. It requires the JWT plugin, every backchannel_logout_uri must be an absolute public HTTPS URL with no credentials or fragment, and on serverless platforms you set advanced.backgroundTasks.handler so delivering logout messages doesn't slow down sign-out. You own the delivery guarantees, the retries, and the audit trail. The managed equivalent is boring on purpose: AuthKit's sign-out reads the sid claim, drops your app session, and redirects to the WorkOS logout endpoint so the session also ends at WorkOS, with access-token duration, maximum session length, and inactivity timeout configured in the dashboard. Refresh tokens rotate on every exchange, and the concurrency case that bites everyone has a specified answer: a 30-second replay grace period returns the same rotated tokens instead of tearing down the session, after which a replay is invalid_grant. Our SDK integrations keep the session on a transient failure and only sign out on a terminal invalid_grant. That distinction is the difference between a brief upstream blip and a mass logout. Why revocation timing matters at all is a longer story we've already told.

MCP. 1.7's MCP support tracks the 2026-07-28 profile and version 2 of the official TypeScript SDK, client registration and metadata were realigned to that profile, and mcp() no longer enables unauthenticated Dynamic Client Registration. Endpoints also moved from /mcp/* to /oauth2/*, and the refresh-token reuse interval defaults to 30 seconds for every client. The spec will move again: Client ID Metadata Document only entered the MCP specification in November 2025, and each move is another coordinated upgrade. AuthKit is the authorization server for your MCP server instead, built on WorkOS Connect, with CIMD toggled on in the dashboard and DCR available for clients that haven't caught up, aud bound to Resource Indicators you register, and ID-JAG token exchange for Cross App Access already supported in early access.

Directory lifecycle. SCIM's hard part is the years after go-live: per-customer connection credentials, Entra and Okta quirks, reprovisioning without dropping access. 1.7 gets Groups and role projections, and charges a full reprovision for them. Directory Sync covers 12+ directory services and emits dsync.user.created, dsync.user.deleted, dsync.group.user_added and friends as real-time webhooks, with JIT and SCIM provisioning switched on per organization. Deprovisioning is where most apps quietly break their promises, which is its own post.

Someone owns the next security fix

1.7 shipped samlify 2.13.1 for a signed-assertion XML injection fix, after moving to 2.12.0 for XPath injection and XXE fixes. SAML parsers will keep producing findings like that, because XML signature validation is the most hostile code in enterprise auth. The question a self-hosted provider has to answer is how fast a patched dependency reaches production: who watches the advisory feed on a Friday, who cuts the release, who runs the regression suite against every customer IdP, and what the window looks like between publication and your deploy.

The same question applies to the endpoint changes in this release. IdPs must be repointed at /sso/saml2/sp/acs/:providerId because /sso/saml2/callback/:providerId is gone, and the deprecated oidcProvider plugin was removed entirely. That's an email to every IT admin who configured your ACS URL two years ago and hasn't thought about it since, not a config edit. With SAML and OIDC behind a single WorkOS API across 20+ identity providers and 99.99% guaranteed uptime SLAs for enterprise customers, that work is our on-call rotation and our conformance testing.

When running it yourself is the right call

The strongest argument for self-hosting is control, not cost. Your token issuance stays in your own database and process, inside your own network boundary, with no third party in the login path, which matters if auth is a product surface you differentiate on, if you're deploying into environments you don't control, or if you need to fork behavior the spec doesn't cover. Better Auth 1.7 makes that position more credible than it was a year ago, and the honest trade is that you also accept the maintenance window, the manual backfills, the fan-out delivery, and the advisory feed.

Cost is the half teams think they're optimizing, and it's the easy one to price. On the managed side of this comparison, WorkOS User Management is free to one million monthly active users and $2,500 per month per additional million, with SSO and Directory Sync connections at $125 each up to 15 connections, dropping to $65 each between 51 and 100. Set that against a maintenance window, a full directory reprovision, and an engineer's week per breaking release.

Most teams never make this choice deliberately. They adopt a library to add sign-in and end up operating an identity provider by accretion, discovering the second half of the job during a migration or a procurement review. Price both halves before you choose: the protocol features are in the release notes, the operational cost is only in the upgrade guide, and by then you've already agreed to pay it.