Upgrading v0.1.x → v0.2.0
TL;DR — v0.2.0 is the MCP Authorization 2026-07-28 release, and it turns the spec-surface features on: DPoP, client credentials, token exchange and XAA now default to
true. Three other changes bite a deployment that edits no config — a cross-client token exchange needs an operator allowlist entry, an origin-only CIMDclient_idis refused, andresourceat the token endpoint is now enforced. Config and database from v0.1.2 work unchanged otherwise; migration004is additive and applies on first start.
These docs describe v0.2.x. Pages that document a feature whose default moved say so inline (“on by default since v0.2.0; on v0.1.x set …”), so you can read them while still on the old line. This page is the whole delta in one place.
The authoritative list, with the wire-level detail, is the authserver CHANGELOG.
Decide this before you upgrade
1. Four defaults flipped on
A stock v0.1.x server advertised only authorization_code and refresh_token; a stock v0.2.0 server advertises all four surfaces, and the discovery document grows accordingly. The Helm chart follows suit from 0.4.0.
Three things this does not mean:
dpop.enabledmeans supported, not required. A client that sends no proof still gets a plain bearer token. Enforcement is a separate, SDK-side switch — see Enable DPoP.- Grants are still gated by client registration. Turning a grant on widens what a client may register for; it grants nothing retroactively to clients already registered.
- XAA validates nothing until you register a trusted IdP via
POST /admin/idps. See Cross-app access (XAA).
To keep the v0.1.x posture, set the ones you don’t want to false explicitly, before or during the upgrade:
dpop: { enabled: false }
client_credentials: { enabled: false }
token_exchange: { enabled: false }
xaa: { enabled: false }
2. A cross-client token exchange needs an allowlist entry
A client presenting a token minted for a different client — typically an MCP server exchanging a user’s web-app or agent token for a Mint resource token — must now be named on the target Resource. On v0.1.x an empty allowlist meant “any client may act”, so any client holding another client’s token could spend that client’s consent.
PATCH /admin/resources/{id}
{"policy": {"exchange": {"allowed_client_ids": ["<exchanging-client-id>"]}}}
policy.runtime.client_ids counts too. Without one of them the exchange is refused with access_denied — not consent_required, because re-prompting the user would not fix it.
Unaffected: a client exchanging a token issued to itself, fronted exchanges, Broker resources, and an MCP server exchanging for its own resource where it already sits in runtime.client_ids. Full walkthrough in Token Vault.
3. A CIMD client_id URL needs https and a path
Per the 2026-07-28 client-registration rules. An origin-only identifier (https://example.com) collapses every client on that domain into one identity and one consent record, so it is refused at /oauth/authorize with invalid_client. Dot-segments and empty segments do not count as a path. Re-register affected clients against a metadata URL that has one.
4. resource at the token endpoint is enforced
On the authorization-code and refresh paths it used to be read and dropped — the audience came from the session, so a client naming a different resource got a token for the one it authorized, with a 200. A resource the grant does not cover is now refused with invalid_target (RFC 8707 §2.2), before the code or refresh token is consumed. Omitting it is unchanged.
The likeliest way to hit this is a spelling difference: resource URIs match exactly, and a trailing slash counts.
5. Two CIMD hardening changes
cimd.require_https: falseis only accepted with a localhost issuer. The server refuses to boot otherwise. Both shipped configs already use a localhost issuer.cimd.require_httpsno longer governs SSRF address filtering — the newcimd.allow_private_addresses(defaultfalse,AUTHPLANE_CIMD_ALLOW_PRIVATE_ADDRESSES) does. If you runrequire_https: falseand serve CIMD documents from loopback, a container network or a LAN address, addallow_private_addresses: trueor the fetch is blocked.
Running the upgrade
Same mechanics as any release — see Backup, upgrade, purge. Two specifics for this one:
- Migration
004applies on first start and is additive: a nullableapplication_typecolumn onclients. Existing rows stayNULLand read asweb. - Schedule
authserver purgeif you haven’t.serveruns no purge goroutines, and three of the four now-on features write expirable rows (dpop_nonces,machine_tokens,assertion_jti). On v0.1.x this only mattered if you had enabled one; on a stock v0.2.0 deployment it always does. Recipes in Scheduled purge.
What you get
Nothing below requires a config change.
- Protected Resource Metadata (RFC 9728) is served for every registered Resource, at
GET /.well-known/oauth-protected-resourceand/.well-known/oauth-protected-resource/{ref}—refbeing the §3.1 path suffix of the Resource URI, or its slug. A resource server that can’t host well-known paths itself now has a conformant document to point itsWWW-Authenticate: resource_metadataat. See Resource servers and PRM. - RFC 9207
isson every authorization response, success and error alike, plusauthorization_response_iss_parameter_supported: truein discovery. Clients compare the value byte for byte. authorization_grant_profiles_supportedin discovery, listingurn:ietf:params:oauth:grant-profile:id-jag— the field the stable MCP Enterprise-Managed Authorization extension tells clients to check.POST /oauth/registeraccepts and persistsapplication_type(webornative). MCP clients are required to send it; one that omits it is defaulted toweband told so in the response — which under OIDC refuses the loopback redirect URIs native clients need.- The whole
xaa.*block is configurable from the environment:AUTHPLANE_XAA_ENABLED,_TOKEN_EXPIRY,_MAX_ASSERTION_AGE,_REQUIRE_RESOURCE,_SUBJECT_MODE,_JWKS_CACHE_TTL. It was YAML-only on v0.1.x. See Configuration reference. - CIMD documents are cached according to their own HTTP cache headers (
Cache-Control,Expires), bounded above bycimd.cache_ttland below by a short floor;no-storeis honoured.cimd.cache_ttlis a ceiling now, not a fixed lifetime.
Deprecated and removed
identity_assertion_supportedin AS metadata is deprecated — still emitted this release, removed in v0.3.0. Readauthorization_grant_profiles_supportedinstead.- Dynamic Client Registration as the primary registration path is deprecated per the specification, which prefers CIMD — no registration endpoint needed, and on by default. DCR still works; the server now warns at boot when
dcr.modeisopen. Setdcr.mode: admin_onlyorapproved_redirectsonce your clients register via CIMD. may_actis removed. Its writer went long ago; the reader, claim field and docs were unreachable leftovers. Operator impact: none.
Embedding authserver as a Go library? The release also changes a handful of exported signatures — Fetcher.SetAllowLoopback and AccessTokenClaims.MayAct are gone, ssrf.NewSafeTransport is variadic, and api/public.NewServer panics without an IssuerProvider. The CHANGELOG lists them under Go API (embedders only).
Coming from v0.1.0 or v0.1.1?
Upgrade through v0.1.2 first, or read its breaking changes alongside this page: introspection now checks who is asking (each resource server that introspects must be authorized before you upgrade), rate_limit.enabled: false no longer disables account lockout, and the Helm probes moved to GET /livez. They’re in the same CHANGELOG.