41

Better Auth as an OIDC authorization server: consolidate trust without duplicating identity

The enterprise problem and today’s slice

An enterprise application can accidentally create two issuers, two token lifecycles, and two equally plausible answers to “who authenticated this user?”. That makes browser administration, command-line tools, public MCP clients, backend APIs, and corporate identity providers harder to secure and operate.

Enterprise problem: Atlas has a browser admin, a CLI/public MCP client, a backend API, and a corporate IdP, but needs one understandable downstream trust model per trust domain.

Whole-course context: Day 40 established trusted tenant context; this day carries that immutable tenant context into token validation and authorization decisions.

Today’s slice: Decide when Better Auth should be an OAuth authorization server and OpenID Provider, and design one issuer, client, token, and session lifecycle where it is justified.

End-of-day evidence: An issuer map and ADR, client inventory, positive discovery/issuer/audience checks, denial checks for wrong issuer/audience/redirect URI, and revocation/logout/key-rotation probes exist under immutable evidence IDs.

Still unsolved: Fine-grained tenant authorization policy, entitlement administration, and resource-server domain policy remain separate work; an upstream login never grants an Atlas application role.

Better Auth describes this provider as OAuth 2.1, a tightened profile for delegated authorization that uses authorization code with PKCE; the finalized security baseline is the OAuth 2.0 Security Best Current Practice in RFC 9700. Neither one defines user identity by itself. OpenID Connect (OIDC) adds identity claims to OAuth: an authorization server (AS) is the service that authenticates a user and issues authorization artifacts, while an OpenID Provider (OP) is an AS that speaks OIDC. A relying party (RP), also called an OAuth client, asks the OP to authenticate a user; a resource server is the API that accepts an access token. This terminology matters because Better Auth can be an RP to the corporate IdP and, separately, an OP to Atlas clients.

The first decision is not “can the plugin issue tokens?” but “should this trust domain have Better Auth as its downstream issuer?” Consolidate to one downstream issuer, client inventory, token/session lifecycle, and revocation surface when Better Auth already owns application sessions and the team can operate an OP. Keep corporate and social IdPs upstream and federated. If an external enterprise IdP already satisfies the issuer, discovery, availability, audience, and lifecycle requirements, Better Auth should remain an RP/client: adding a second issuer only adds translation, incidents, and ambiguity.

Customer use cases

These use cases make the issuer decision testable. A use case is useful only if its success and denial evidence can be retained without substituting a mutable email address for a durable identifier.

Use case IDActorCustomer jobSuccess outcomeDenial or recovery evidence
D41-UC-01Atlas tenant administratorSign in through the corporate IdP and use the Atlas browser admin under the trusted Day 40 tenant contextBetter Auth creates or resumes the Atlas session and issues tokens only for registered Atlas browser client atlas-admin-webev_d41_001 records upstream subject, issuer, tenant context ID, session ID, and a denied cross-tenant attempt if one occurs
D41-UC-02Automation engineerObtain a scoped access token for the Atlas CLI/public MCP client without exposing a client secretBetter Auth completes authorization-code plus PKCE for public client atlas-cli-mcp and the API accepts an access token for atlas-apiev_d41_002 records code challenge method, client ID, token ID, expected audience, and wrong-audience denial
D41-UC-03Security operatorEnd access after compromise or a signing-key rollover and prove old artifacts no longer workRevocation, RP-initiated logout, and JWKS key rotation converge while valid replacement credentials continue workingev_d41_003 records grant/session/key IDs, revocation time, logout correlation, key ID, and before/after validation probes

The three records are contiguous because they describe one product: human login, public-client delegation, and lifecycle control. The denial column is not an implementation footnote; it is the observable reason an auditor can distinguish a correct refusal from a broken path.

Actor-centred user stories

The stories translate customer jobs into conditions a beginner can observe. They deliberately separate authentication at an issuer from authorization at the resource server.

Story IDUse case IDsUser storyObservable acceptance conditions
D41-US-01D41-UC-01As an Atlas tenant administrator, I want corporate sign-in to create an Atlas session for my trusted tenant so I can administer only that tenant.Discovery document resolves to the chosen issuer; the browser RP receives an ID token for itself and an access token for atlas-api; API logs show the tenant context ID and a tenant-policy decision.
D41-US-02D41-UC-02As an automation engineer, I want a public CLI/MCP client to use PKCE so a stolen authorization code cannot be exchanged by another client.Authorization request contains code_challenge and S256; exchange with the matching verifier succeeds; missing or mismatched verifier, redirect URI, issuer, or audience is denied and correlated to ev_d41_002.
D41-US-03D41-UC-03As a security operator, I want revocation, logout, and key rotation to invalidate the intended access without taking down every valid session.Revoked grant is rejected; logout ends the browser session; API refreshes JWKS and accepts a new key ID while rejecting an invalid signature or retired-key artifact according to the published retention policy.

An ID token proves an authentication event to the client/RP that requested it; it is not an API credential. The API, as resource server, verifies an access token’s issuer, audience, signature, expiry, and scope, then applies tenant and domain policy from authoritative application data. A corporate IdP assertion proves upstream authentication, not an Atlas role.

End-to-end product flows

The flows show where artifacts change hands and where refusal belongs. They also prevent a common mistake: treating a browser cookie, an ID token, and an API access token as interchangeable.

Flow IDUse case IDsPathTriggerNumbered stepsTerminal evidence
D41-FLOW-01D41-UC-01HappyAdministrator opens tenant administration1. Browser requests authorization. 2. Better Auth redirects to corporate IdP as RP. 3. Corporate IdP returns a verified identity. 4. Better Auth binds the upstream subject to an internal user/session and trusted tenant context. 5. Better Auth returns code and tokens to the registered browser client. 6. Browser sends access token to API. 7. API validates token then tenant/domain policy.Actor: Atlas tenant administrator; resource: Atlas API tenant admin route; scope: atlas.admin; precondition: registered browser client and trusted tenant context; expected: allow; observed: allow; environment: staging; timestamp: ISO-8601 UTC; immutable ID: ev_d41_001.
D41-FLOW-02D41-UC-02HappyEngineer runs a command requiring a tenant-scoped API operation1. Client creates verifier and S256 challenge. 2. Client opens authorization request. 3. Better Auth authenticates through existing session or corporate IdP. 4. Consent is recorded if required. 5. Authorization code is bound to client, redirect URI, and challenge. 6. Client exchanges code with verifier. 7. API validates access token and applies tenant/domain policy.Actor: automation engineer; resource: Atlas API MCP command route; scope: atlas.read; precondition: public client has exact redirect and matching verifier; expected: allow; observed: allow; environment: staging; timestamp: ISO-8601 UTC; immutable ID: ev_d41_002.
D41-FLOW-03D41-UC-02DeniedEngineer presents valid token with a non-Atlas API audience1. Test client obtains a selected-issuer token for another audience. 2. Client calls Atlas API. 3. API verifies signature. 4. API compares audience before tenant policy. 5. API emits stable denial.Actor: automation engineer; resource: Atlas API MCP command route; scope: atlas.read; precondition: token signature valid but aud differs from atlas-api; expected: deny audience_invalid; observed: deny audience_invalid; environment: staging; timestamp: ISO-8601 UTC; immutable ID: ev_d41_002.
D41-FLOW-04D41-UC-03RecoveryOperator revokes a grant, initiates logout, and rotates a signing key1. Operator selects immutable grant/session/key record. 2. Better Auth writes revocation or key state. 3. Logout clears browser session and notifies RP where configured. 4. JWKS publishes active verification keys. 5. API refreshes keys on unknown key ID and revalidates. 6. Probe presents revoked, stale, or wrong-key artifact. 7. Probe presents valid replacement artifact.Actor: security operator; resource: Atlas API protected route; scope: atlas.read; precondition: target grant/session/key IDs are active; expected: revoked artifact denied and replacement accepted; observed: recorded by probes; environment: staging; timestamp: ISO-8601 UTC; immutable ID: ev_d41_003.

The normal browser flow is intentionally a chain of two roles: Better Auth is an RP to the corporate IdP and an OP to Atlas clients. The resource server never trusts an unverified upstream assertion directly.

The public-client flow adds PKCE. PKCE binds the authorization code to a high-entropy verifier held by the client; the S256 challenge is derived from that verifier and prevents an intercepted code from being redeemed by a different party.

The recovery flow proves that control-plane changes have data-plane effect. Logout does not retroactively change a signed token’s contents, so token lifetime, revocation checks, and session policy must be designed together.

System design derived from the flows

The system design assigns authority to each boundary. The consequence is that no client can promote an upstream identity, an ID token, or a tenant hint into API permission by itself.

Use case IDEntry pointResponsible servicesAuthoritative storeFailure evidence
D41-UC-01Atlas browser admin authorization routeBrowser client; Better Auth as RP to corporate IdP and OP to Atlas; Atlas API resource serverBetter Auth user/session/link records; Day 40 tenant-context record; Atlas policy storeev_d41_001 stores discovery issuer, client ID, upstream issuer/subject reference, session ID, token validation result, and policy denial code.
D41-UC-02Atlas CLI/public MCP authorization routePublic client; Better Auth authorization/token endpoints; Atlas API resource serverBetter Auth client, consent, code, grant, and token records; Atlas policy storeev_d41_002 stores immutable transaction/code/grant/token IDs and reasons pkce_invalid, redirect_uri_invalid, issuer_invalid, or audience_invalid.
D41-UC-03Security lifecycle console and validation probesSecurity console; Better Auth session/revocation/key services; JWKS endpoint; Atlas API verifierBetter Auth session/grant/key metadata and append-only audit storeev_d41_003 stores revocation, logout, rotation, JWKS fetch, old/new key ID, and validation probe IDs.

An issuer is the stable HTTPS identifier naming the token authority. An audience is the intended recipient of a token, often an API identifier such as atlas-api. Scope is a bounded capability label, for example atlas.read; it narrows what an access token may request but does not replace tenant policy. The resource server must reject a token if any of issuer, audience, signature, expiry, or required scope is wrong before applying the tenant/domain policy.

This diagram reconnects the flow participants into operational boundaries, including discovery/JWKS, policy, and evidence. Better Auth owns downstream authentication artifacts; Atlas owns resource authorization decisions; the corporate IdP owns upstream credentials and its own session.

Data model and ownership

The data model makes lifecycle ownership explicit. If records cannot name their owner, tenant relationship, retention rule, and immutable evidence reference, incident recovery turns into guesswork.

Generated-application database: Not created in this slice — the records below are the logical contract for the existing Better Auth persistence adapter and Atlas-owned policy/evidence stores; do not invent a parallel token database merely to document the design.

Record or entityStore and ownerPrimary keyForeign key or opaque referenceTenant keyMaterial constraintLifecycle and deletionUse case IDs
OAuth clientBetter Auth store, owned by identity platformclient_idregistered redirect URI set; software inventory IDNone — platform-level client registration; tenant binding is enforced by issued grant and Atlas policyexact redirect URI matching; public clients have no secret; allowed grants/scopes explicitversioned; disable before deletion; retain inventory/audit referenceD41-UC-01, D41-UC-02, D41-UC-03
Consent decisionBetter Auth store, owned by identity platformconsent_idclient_id, internal user_id, policy versiontrusted tenant-context reference when consent is tenant-boundscopes are explicit and least-privilege; consent cannot widen client registrationexpires or is withdrawn; audit tombstone retainedD41-UC-01, D41-UC-02
Authorization codeBetter Auth store, owned by identity platformauthorization_code_idclient_id, consent_id, session_id, hashed PKCE challengetrusted tenant-context IDone-time, short-lived, exact redirect URI and PKCE bindingconsumed or expired; purge secret value, preserve event referenceD41-UC-02
OAuth grantBetter Auth store, owned by identity platformgrant_idclient_id, user_id, consent and session referencestrusted tenant-context IDscopes, audience, issue time, expiry, and revocation state immutable per issuanceactive, revoked, expired; retain state transition auditD41-UC-01, D41-UC-02, D41-UC-03
Access token metadataBetter Auth store or token log, owned by identity platformaccess_token_idgrant_id, signing key_id, token hash or opaque referencetrusted tenant-context ID if claim is issuednever persist raw bearer token; issuer/audience/scopes must equal grantexpires naturally; revocation link retainedD41-UC-01, D41-UC-02, D41-UC-03
Refresh token metadataBetter Auth store, owned by identity platformrefresh_token_idgrant_id, token-family referencetrusted tenant-context IDhashed at rest; rotation and reuse detection required where issuedrotate, revoke family on reuse, retain security eventD41-UC-01, D41-UC-02, D41-UC-03
Signing key and JWKS metadataKey-management store, owned by identity platformkey_idpublic JWK thumbprint and publication versionNone — key material serves the selected platform issuer, not a tenantprivate key is non-exportable where possible; key ID unique; published set contains valid verification keysstaged active/retiring/retired; publish overlap then retire per token TTLD41-UC-03
Upstream identity linkBetter Auth store, owned by identity platformidentity_link_idcorporate issuer plus stable upstream sub; internal user_idtenant eligibility reference only, not role grantunique on upstream issuer plus subject; email is non-authoritativeunlink only after recovery path; preserve audit referenceD41-UC-01, D41-UC-02
Better Auth sessionBetter Auth store, owned by identity platformsession_idinternal user_id, upstream identity linktrusted tenant-context IDcookie/session binding, expiry, and logout state explicitexpire or revoke; audit session terminationD41-UC-01, D41-UC-03
Atlas authorization policy decisionAtlas policy store, owned by resource servicepolicy_decision_idaccess_token_id or token hash, principal ID, Day 40 tenant-context IDauthoritative tenant IDverifies token before policy; upstream login is never a roleretain decision evidence under policy retentionD41-UC-01, D41-UC-02, D41-UC-03
Immutable evidence eventAppend-only audit store, owned by security operationsevidence_idopaque IDs for client, session, grant, key, and policy decisiontenant ID when applicableappend-only, time-stamped, actor/action/outcome correlationretention-controlled; no in-place mutationD41-UC-01, D41-UC-02, D41-UC-03

JWKS, the JSON Web Key Set, is the published set of public verification keys used by clients and APIs to validate signed tokens. It is metadata, not a shared private-key store. The API should cache it with bounded refresh behavior and refresh on an unfamiliar key ID; operators should overlap publishing old and new verification keys long enough for valid tokens to age out.

This data diagram names the lifecycles that support the system boundaries: clients, sessions, upstream identity, artifacts, keys, API authorization, and evidence. Tenant context is referenced by trusted ID throughout, rather than reconstructed from token display claims or email domains.

Smallest complete model

The smallest complete model prevents premature platform building. Its consequence is a decision that can be implemented and audited before adding optional protocols or a bespoke identity database.

One trust domain has one selected downstream issuer. Better Auth is that issuer only when it already owns the Atlas application session and the team accepts OP responsibilities: client registration, redirect URI control, discovery, token issuance, revocation, logout behavior, signing keys, JWKS publishing, monitoring, and incident response. The browser admin and CLI/MCP are registered clients. The Atlas API is the resource server with a unique audience. Corporate/social providers remain upstream federation sources.

The minimum claims and state are deliberately boring: immutable internal subject, issuer, audience, expiry, scopes, client identity, trusted tenant-context reference or lookup key, and a correlation ID. Do not put mutable role lists or every tenant entitlement into an ID token. The API uses access-token validation first and Atlas policy second.

Provider-vs-client roles

The provider-versus-client distinction answers the architecture question. Confusing these roles creates a second issuer merely because Better Auth is already present in the application.

Better Auth as an RP/client redirects an Atlas user to the corporate IdP, validates the upstream response, and creates an Atlas session. Better Auth as an OP then serves downstream Atlas clients under its own issuer. This dual role is valid only when the downstream issuer has a concrete product need.

If the corporate IdP already provides a stable issuer, OIDC discovery, appropriate audiences and scopes, application client management, logout/revocation behavior, and operational ownership for every Atlas client, choose it as the sole downstream issuer. Better Auth remains an RP/client for Atlas session convenience or may use the enterprise session directly. Do not add Better Auth as a token-minting proxy just to normalize claims.

Better Auth’s current implementation path is oauthProvider from @better-auth/oauth-provider. Treat legacy oidcProvider references as a migration warning: inventory them, follow the Better Auth 1.7 upgrade guidance, and remove them behind a tested cutover rather than carrying two provider plugins indefinitely. The official plugin documentation and upgrade guide are the source of truth for exact supported configuration and migration details (OAuth provider plugin, 1.7 upgrade guide).

Trust boundaries

Trust boundaries explain who may assert what. The consequence is a design where a token is accepted only by the service it was minted for and where federation never becomes authorization.

The corporate IdP may assert an upstream subject after authenticating the person. Better Auth may bind that subject to an Atlas internal user and session, but it must validate upstream issuer, signature, nonce/state, redirect handling, and applicable claims. Better Auth may issue its own downstream tokens only under its selected issuer. A client may present an access token but may not choose its audience, invent a redirect URI, or claim a tenant role.

The Atlas API is the final resource boundary. It verifies the access token’s issuer, audience, scope, expiry, and signature against discovery/JWKS, then retrieves or evaluates trusted tenant/domain policy. It rejects ID tokens as API credentials. Atlas policy may grant a role only through its own authoritative workflow; successful upstream login is not a role assignment.

Authorization-server metadata discovery is standardized by RFC 8414, and the issuer identifier in authorization responses has specific mix-up-attack protections in RFC 9207 (RFC 8414, RFC 9207). OIDC Core defines the ID token’s RP-facing validation expectations (OpenID Connect Core).

Decision matrix and costs

The matrix turns “consolidate auth” into an accountable decision. The consequence is that teams can choose a single issuer intentionally rather than migrating because a plugin exists.

DecisionChoose it whenBenefitsCosts and obligationsReject it when
Better Auth is downstream OPBetter Auth owns Atlas sessions and all Atlas clients need a common issuer/token lifecycleOne client inventory, consistent API audience, unified evidence and logout surfaceOperate discovery, keys/JWKS, client registration, redirect governance, revocation, monitoring, and incident responseCorporate IdP already meets every downstream issuer requirement or team cannot own OP operations
Corporate IdP is sole downstream OPEnterprise IdP can register browser and CLI clients and issue suitable API tokensFewer issuers, fewer translations, enterprise lifecycle controls already existAtlas must align with IdP client/audience/scope model and release cadenceAtlas needs a distinct trust domain or capabilities unavailable from the IdP
Hybrid federation with Better Auth as RP onlyAtlas needs an app session but not token mintingSimple application session plus upstream enterprise authorityAPI trust stays at external issuer; no Better Auth access-token lifecycleAtlas clients require a domain-specific issuer Better Auth can safely operate

OAuth security best current practice emphasizes exact redirect URI handling, PKCE, sender-constrained thinking where appropriate, and protection against code/token leakage (RFC 9700). The security update for the deployed Better Auth version must be reviewed before enabling provider behavior, including the remediation guidance published by the project (Better Auth security update, June 2026).

Better Auth TypeScript config

Configuration must express the selected topology, not merely compile. The consequence is a reviewable starting point that forces exact issuer and client decisions into environment-controlled configuration.

Use the current plugin configuration below, and create clients through the documented API. Do not revive the legacy oidcProvider plugin.

import { betterAuth } from "better-auth";
import { jwt } from "better-auth/plugins";
import { oauthProvider } from "@better-auth/oauth-provider";

export const auth = betterAuth({
  disabledPaths: ["/token"],
  plugins: [
    jwt(),
    oauthProvider({
      loginPage: "/sign-in",
      consentPage: "/consent",
      scopes: ["openid", "profile", "atlas.read", "atlas.admin"],
    }),
  ],
});

Create the public CLI/MCP client through the Better Auth API. Its token_endpoint_auth_method is none because a public client cannot safely keep a client secret; PKCE remains mandatory for the authorization-code exchange.

const oauthClient = await auth.api.createOAuthClient({
  headers,
  body: {
    client_name: "atlas-cli-mcp",
    redirect_uris: ["http://127.0.0.1:39817/callback"],
    token_endpoint_auth_method: "none",
    grant_types: ["authorization_code", "refresh_token"],
    response_types: ["code"],
    scope: "openid profile atlas.read",
  },
});

The conceptual controls are fixed even if the package’s exact fields evolve: derive issuer from controlled configuration, register exact redirect URIs, label public clients correctly, require PKCE for public clients, define permitted grants/scopes/audiences explicitly, and use managed signing-key storage. Keep client inventory in configuration or controlled administration with review history, not in client-supplied request parameters.

Staged migration, cutover, and rollback

Migration needs a reversible sequence because issuer changes are trust changes. The consequence is that existing users can continue working while verification behavior is measured before it becomes authoritative.

  1. Write the issuer map and ADR: name every current issuer, RP, client, API audience, corporate IdP, session owner, and trust domain. Decide either Better Auth OP or external IdP sole OP.
  2. Inventory legacy oidcProvider usage, clients, redirect URIs, scopes, signing keys, token validators, and ID-token-as-API misuse. Classify each client as browser confidential, native/CLI public, service, or unsupported.
  3. Upgrade to the supported oauthProvider implementation in a non-production environment following the official migration guidance. Register shadow clients with non-production exact redirects.
  4. Publish discovery and JWKS. Run positive issuer/audience checks and intentional wrong-issuer, wrong-audience, and wrong-redirect probes before enabling real traffic.
  5. Enable one low-risk browser client, then the CLI/public MCP client with PKCE. Keep the prior issuer accepted only for a documented bounded overlap and only where API policy allows it.
  6. Cut over validators to the selected issuer/audience contract. Monitor denials by reason, discovery/JWKS freshness, token exchange failures, and session/logout success.
  7. Retire old clients, legacy provider configuration, and old keys only after their maximum token/session overlap has elapsed and immutable evidence confirms no live dependency.

Rollback means disable the new client registrations or route selection, restore the previously approved issuer validation configuration within the documented overlap window, and revoke newly issued grants where incident policy requires it. It does not mean accepting arbitrary issuers “temporarily.” Preserve evidence IDs and configuration versions so rollback can be explained later.

Normal, denied, and recovery evidence plan

Evidence is part of the feature because identity failures are often indistinguishable from misconfiguration without correlation. The consequence is a probe suite that demonstrates both correct access and correct refusal.

Probe IDModeProcedureExpected terminal evidence
D41-P-01NormalFetch authorization-server metadata; complete browser login through corporate IdP; call API with issued access tokenDiscovery issuer equals configured issuer; ID token aud equals browser client; access token aud equals atlas-api; ev_d41_001 links session and policy decision
D41-P-02NormalComplete CLI/public MCP authorization-code flow using S256 PKCE; call API with required scopeCode exchange succeeds only with matching verifier; API accepts issuer/audience/scope and emits ev_d41_002
D41-P-03DeniedPresent token from a non-selected issuer to Atlas APIAPI returns stable issuer_invalid denial and records token hash/reference plus policy correlation ID
D41-P-04DeniedPresent a correctly signed token with audience other than atlas-apiAPI returns stable audience_invalid denial before tenant policy and records ev_d41_002
D41-P-05DeniedStart authorization using an unregistered or near-match redirect URI, or exchange code with wrong PKCE verifierAuthorization or token endpoint denies exact mismatch; event records redirect_uri_invalid or pkce_invalid without logging secrets
D41-P-06RecoveryRevoke a selected grant and retry API/refresh behavior; initiate RP logoutRevoked grant/session fails as designed, valid unrelated session remains valid, and ev_d41_003 links command to result
D41-P-07RecoveryStage a new signing key, publish JWKS, validate new token, then test stale/invalid signature according to retirement policyAPI observes new key ID after bounded refresh, accepts new valid signature, rejects invalid signature, and records JWKS/version probes

Use immutable IDs such as evidence_id, client_id, session_id, grant_id, authorization_code_id, access_token_id, key_id, and policy_decision_id. Redact bearer values, code verifiers, client secrets, cookies, and full assertion bodies. Store token hashes or opaque references when correlation is required.

Operations

Operating an OP is a product commitment, not a library import. The consequence is a named runbook for availability, discovery, keys, client changes, and security response.

Monitor authorization and token endpoint success/error rates by client, redirect mismatch denials, PKCE failures, issuer/audience validation denials at the API, session creation/logout outcomes, grant revocations, JWKS refresh failures, and key-ID distribution. Alert on unexpected issuer values, a sudden audience-denial spike, signing-key publication drift, refresh-token reuse, and a client using a retired redirect URI.

Run key rotation with a staged active/retiring state: publish the new public JWK, issue with the new key ID, retain old verification material for the maximum allowed artifact lifetime plus cache allowance, then retire it. Test the whole sequence against independent API validation. Never replace a key in place under the same key ID.

Use OIDC RP-initiated logout where client/browser semantics require it, but document what it ends: RP session, OP session, or upstream corporate session. A local Atlas logout does not necessarily end a corporate IdP session, and front-channel behavior is not proof that an already-issued access token has been revoked (OIDC RP-Initiated Logout).

Key takeaways

The key takeaway is a decision rule, not a mandate to introduce another layer. The consequence is that the issuer topology remains legible as Atlas gains clients and tenants.

  • Consolidate to one Better Auth downstream issuer/client/token/session lifecycle per trust domain when Better Auth already owns app sessions and the team can operate an OP.
  • Keep corporate and social IdPs federated upstream; Better Auth is their RP/client even when it is also an OP to Atlas clients.
  • If an external enterprise IdP already meets the issuer requirements, keep Better Auth as an RP/client and do not create a second issuer.
  • Use oauthProvider from @better-auth/oauth-provider; treat oidcProvider as legacy migration work, not a parallel implementation.
  • An ID token is for the RP/client. The API accepts access tokens only after issuer, audience, signature, expiry, and scope checks, then applies tenant/domain policy.
  • Upstream login authenticates a subject. It never grants an Atlas role.

Checklist

This checklist is the handoff boundary for implementation. The consequence is that a team can refuse launch until both normal behavior and security refusals are observable.

  • [ ] Issuer map and ADR name one selected downstream issuer per trust domain.
  • [ ] Client inventory includes atlas-admin-web, atlas-cli-mcp, redirect URIs, type, grants, scopes, audiences, owner, and lifecycle status.
  • [ ] Better Auth uses supported oauthProvider; all legacy oidcProvider references have migration owners.
  • [ ] Corporate/social identity remains upstream federation with stable issuer-plus-subject links.
  • [ ] Discovery and JWKS endpoints are reachable and their issuer/key IDs match the documented configuration.
  • [ ] Browser ID token validation is performed by its RP; the API does not accept ID tokens.
  • [ ] API validates access-token issuer, audience, signature, expiry, and scope before tenant/domain policy.
  • [ ] Public CLI/public MCP client uses authorization code plus S256 PKCE and exact registered redirect URIs.
  • [ ] Wrong issuer, audience, redirect URI, and PKCE verifier are denied with redacted immutable evidence.
  • [ ] Revocation, logout, and key-rotation probes have immutable IDs and expected convergence windows.
  • [ ] Key overlap and retirement schedule covers maximum token lifetime and verifier cache behavior.
  • [ ] Security update and version-specific Better Auth documentation have been reviewed before production enablement.

Validation walkthrough

This walkthrough turns the checklist into an ordered review. The consequence is that an implementer can diagnose the layer that failed without weakening the trust model to make a demo pass.

Begin at discovery, not at the token endpoint. Fetch the authorization-server metadata from the selected issuer and compare its issuer field byte-for-byte with the configured issuer map. Record the metadata URL, retrieval time, HTTP status, cache headers, and a content digest in the evidence event. A redirect to a different host, a trailing-slash mismatch, or an issuer assembled from a request header is a configuration failure, not a harmless formatting difference.

Next inspect the client inventory. For every client, an operator should be able to answer who owns it, what software release contains it, whether it is public or confidential, every allowed redirect URI, permitted grant types, allowed scopes, intended audiences, consent behavior, and retirement date. Reject a deployment if the answer is “the application sends it in the request.”

For atlas-admin-web, perform an interactive browser sign-in. Confirm that state and nonce are created before redirect, stored in a browser-safe session mechanism, and checked when the corporate IdP response returns. Confirm Better Auth validates the upstream issuer and subject, then selects the trusted Day 40 tenant context through Atlas-controlled logic. The downstream client should receive an ID token only for atlas-admin-web and should use its access token only against atlas-api.

For atlas-cli-mcp, inspect the authorization request before it leaves the process. It must include the registered client_id, the exact registered redirect URI, response_type=code, a unique state value, a code_challenge, and code_challenge_method=S256. The verifier stays local to the client until the token exchange. It is neither an audit field nor a log field.

At the token exchange, send the correct authorization code once with the correct verifier. Store only references, timings, and outcomes. A second exchange of the same code must fail. A matching-looking but different redirect URI must fail. A verifier generated by a different CLI instance must fail. These distinctions demonstrate that authorization code, redirect URI, and PKCE bindings are all enforced.

At the API, test validation in the order the implementation documents. First reject malformed or expired input. Then verify signature using a bounded JWKS retrieval strategy. Then compare issuer and audience exactly. Then require the operation’s scope. Only after those checks should the API load the trusted tenant context and evaluate the Atlas domain authorization policy.

Record an explicit decision trace for a successful API request: token_validation_id, issuer_result, audience_result, scope_result, tenant_context_id, policy_decision_id, and evidence_id. Do not include bearer values or decoded personal claims simply because a trace is convenient. Security evidence should prove process, not become a new secret store.

For denial tests, vary only one element at a time. A wrong issuer test should use a syntactically valid token from another controlled test issuer. A wrong audience test should use a valid signature under the selected issuer but a different API audience. A wrong redirect test should differ by path, port, scheme, or encoding according to the registration rules. This isolates the denial reason.

For lifecycle tests, measure time. Record the time revocation was written, the next API attempt, any revocation lookup, the API denial, and the maximum documented access-token lifetime. Record the time a new JWK appeared, the time each API process observed it, and the time the retired key was removed. A system that “eventually works” without these times has no usable operations contract.

Claim and policy separation

Claim design is where authentication systems frequently leak into authorization systems. The consequence is a compact token contract that is stable across tenant policy changes and easy for resource servers to validate.

An access token needs enough information to be validated and routed: issuer, subject or opaque principal reference, audience, expiry, issued-at time, token identifier where available, scopes, and signing key ID in the header. It may include a trusted tenant-context reference only if that reference is bounded, versioned, and validated by Atlas policy. It should not become a serialized authorization database.

An ID token is scoped to its RP. Its audience identifies the client that requested authentication, and the RP validates nonce and other OIDC requirements before creating or continuing its own browser state. Sending that ID token to atlas-api is rejected even if the browser client and API share a deployment team.

Scopes are coarse delegated capabilities. atlas.read can state that the holder requests read operations, while a tenant/domain policy decides whether the principal may read this tenant’s projects, this account’s invoices, or no records at all. Scopes should not encode unbounded combinations such as every tenant ID, every role, and every object permission.

Roles and entitlements belong to Atlas authorization ownership. They can be derived from tenant membership, business workflow, administrator assignment, and domain state. Better Auth may carry a bounded reference useful for policy lookup, but a new upstream group claim must not silently promote a user into an Atlas administrator role.

When claims are changed, version the contract and test consumers. A client might tolerate an optional display claim disappearing, but the resource server must not tolerate a changed issuer or audience. Maintain a consumer list for each claim that is security-relevant, including validation libraries, API gateways, CLI versions, and MCP integrations.

Incident response boundaries

Incident response must distinguish an upstream authentication incident from a downstream token incident. The consequence is a narrow corrective action that preserves availability for unaffected principals and clients.

If a corporate IdP account is compromised, use the corporate IdP’s session and credential controls first, then identify the linked Better Auth sessions and grants through stable upstream issuer-plus-subject references. Do not search or revoke by email alone. End Atlas sessions and grants according to incident policy, then require a fresh upstream authentication event.

If an Atlas access token is exposed, identify its grant, client, session, subject, tenant-context reference, signing key ID, and issue time using token metadata or an opaque correlation reference. Revoke the applicable grant or token family. Avoid global key rotation unless the signing key itself may be compromised; global rotation is expensive and does not by itself invalidate every token unless validators enforce the retirement policy.

If a signing key is compromised, mark the key as compromised, stop issuing with it, publish the safe key set, revoke or reject artifacts signed with the compromised key under the incident policy, and notify resource servers through their normal JWKS refresh behavior. Record the exact key ID and policy version. Do not delete the audit trail needed to explain the response.

If a redirect URI registration is abused, disable the individual client or the affected redirect entry if supported, preserve the client inventory version, and inspect authorization requests correlated to that client. Never accept a broad redirect pattern as an emergency workaround; create a reviewed replacement client or exact URI instead.

If a validator accidentally accepts an ID token at the API, treat that as an authorization-boundary defect. Disable the affected route or validator configuration if needed, ship a fix that requires the API audience and access-token contract, and review evidence for requests that passed through the mistaken path. The correct remediation is stricter validation, not reissuing browser ID tokens with broader claims.

Implementation review questions

Review questions surface hidden assumptions before production traffic arrives. The consequence is that product, platform, and security owners share the same answers instead of each maintaining a separate mental model.

  • What exact HTTPS string is the selected issuer, and who approves a change to it?
  • Which software inventory record owns each OAuth client, and what is its decommission date?
  • Which redirect URIs are exact registrations, and which clients are public?
  • Which audience string does atlas-api enforce for every protected endpoint?
  • Which scopes are necessary for each CLI/MCP operation, and which policy layer interprets them?
  • What internal immutable subject joins the corporate IdP identity to Atlas policy?
  • How is the Day 40 tenant context selected, validated, and logged without treating it as a client-controlled hint?
  • Where are grants, sessions, revocations, and key states stored, and who can modify each state?
  • What is the maximum access-token lifetime, JWKS cache duration, and key overlap window?
  • Which independent process validates a new signing key before retirement of the old key?
  • What does local logout end, and what does corporate logout end?
  • Which evidence IDs let an operator prove a denial was issuer, audience, redirect URI, PKCE, scope, tenant, or domain policy?

Non-goals for this slice

Non-goals prevent this architecture lesson from silently becoming an entitlement project. The consequence is that the issuer decision can be completed while downstream authorization remains intentionally owned by Atlas.

This slice does not define a complete role model, group synchronization policy, SCIM provisioning design, service-to-service workload identity, device posture signal, user-interface consent wording, or every protocol extension. Those may be important future work, but none changes the core rule that upstream authentication and resource authorization are separate.

This slice does not authorize arbitrary third-party developers to register clients. Client registration is an administrative control plane with ownership, review, exact redirect handling, and retirement. Dynamic registration, if introduced later, requires its own abuse model and evidence plan.

This slice does not claim that a JWT is always required. Better Auth may support opaque tokens or implementation-specific storage patterns. The resource-server contract remains the same: accept only the selected issuer’s intended access tokens and validate them through a supported method before tenant/domain policy.

This slice does not require a second issuer. The recommended target remains conditional. When the external enterprise IdP already supplies the complete downstream contract, consolidation means choosing that one issuer and resisting an unnecessary Better Auth provider layer.

Decision record template

The decision record makes the selected trust topology durable. The consequence is that a future client integration cannot reopen the issuer question without confronting the operating costs and evidence that supported the original choice.

Record the decision title as “Atlas downstream issuer for trust domain <immutable-domain-id>.” State whether Better Auth is the downstream OP or an RP/client only. Name the corporate IdP issuer, selected downstream issuer, API audience, client inventory version, and accountable operational owner.

Record the alternatives considered: external IdP as sole OP, Better Auth as downstream OP, and a temporary dual-issuer overlap. For each, state why it satisfies or fails discovery, client registration, public CLI PKCE, API audience, tenant context, logout/revocation, signing-key, and on-call requirements.

Record irreversible assumptions explicitly: redirect URIs are exact; upstream subject is joined by issuer plus subject; tokens do not carry application role authority; APIs do not accept ID tokens; and a rollback never opens arbitrary issuer acceptance. Attach the identifiers of positive and negative probes, rather than screenshots of bearer credentials.

Record a review date tied to client onboarding, Better Auth upgrades, corporate IdP contract changes, and signing-key policy changes. A stable issuer is an interface, so changing it requires the same care as changing a public API contract.

Handoff exercise

The handoff exercise lets a beginner demonstrate the decision without needing to write production code. The consequence is a clear explanation that survives the temptation to solve every identity problem by adding another provider.

Draw the issuer map for one tenant domain. Place the corporate IdP upstream, Better Auth in the middle only if it is selected as the downstream OP, the browser admin and CLI/MCP at the client boundary, and Atlas API at the resource boundary. Label every issuer and the sole accepted API audience.

For a browser request, identify the RP that validates the ID token and the resource server that validates the access token. Explain why these are different validations even when both components are owned by Atlas. Then identify the point where the Day 40 tenant context becomes an authorization input.

For a CLI request, name the verifier, challenge, authorization code, access token, and API policy decision. Explain which value is never written to logs, which artifact is one-time, and which artifact must have the atlas-api audience.

Finally, describe a wrong-audience denial and a key-rotation recovery using only immutable IDs. If the explanation needs an email address, raw bearer token, or a guessed tenant name to correlate events, the data model needs another review.

Primary sources