Many identity providers (AWS Cognito, Okta, Azure AD) reject or mishandle colons in OAuth scope names. This migrates all custom scopes from `resource:action` to `resource.action` format (e.g., `notes:read` → `notes.read`), which is universally accepted and aligns with industry conventions (Microsoft, Google). Includes Alembic migration 004 for stored scope strings and ADR-024 documenting the rationale and RFC references. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5.4 KiB
ADR-024: OAuth Scope Separator Migration (colon to dot)
Status: Accepted Date: 2026-04-07 Supersedes: Scope naming conventions in ADR-004, ADR-009, ADR-011
Context
The MCP server defines application-level OAuth scopes using the resource:action
pattern (e.g., notes:read, calendar:write). While the colon separator is
visually intuitive and used by some OAuth implementations, it causes
interoperability problems with many identity providers.
IDP Compatibility Issues
Several widely-deployed identity providers reject or mishandle colons in OAuth scope names:
- Keycloak: Accepts colons but requires special configuration for scope mappers; colons can conflict with realm-qualified scope names
- Auth0: Permits colons but treats them as namespace delimiters in their Resource Server API, leading to unexpected scope resolution behavior
- Azure AD / Entra ID: Uses colons internally for delegated permissions
(e.g.,
User.Read) and may reject custom scopes containing colons - AWS Cognito: Restricts scope names to alphanumeric characters, hyphens, periods, and underscores — colons are not allowed
- Okta: Custom scopes are restricted to
[a-zA-Z0-9._-]; colons are explicitly rejected
RFC References
The OAuth 2.0 framework (RFC 6749, Section 3.3) defines scope values as:
scope-token = 1*( %x21 / %x23-5B / %x5D-7E )
This technically permits the colon character (%x3A), so colons are
spec-compliant. However, the specification also notes:
The authorization server MAY fully or partially ignore the scope requested by the client, based on the authorization server policy or the resource owner's instructions.
In practice, many authorization servers impose stricter character restrictions
than the RFC minimum. The dot separator (.) is universally accepted across all
major OAuth/OIDC implementations and is the de facto convention used by:
- Microsoft Identity Platform (
User.Read,Mail.Send) - Google OAuth (
https://www.googleapis.com/auth/calendar.readonly) - MCP specification examples in RFC 9728 (OAuth Protected Resource Metadata)
RFC 9728 (OAuth Protected Resource Metadata)
RFC 9728 defines the Protected Resource Metadata endpoint used by this server
(/.well-known/oauth-protected-resource). While the RFC does not mandate a
specific scope naming convention, its examples and the broader OAuth ecosystem
favor dot-separated scopes for maximum interoperability.
Decision
Replace the colon (:) separator with a dot (.) in all application-level
OAuth scope names:
| Before | After |
|---|---|
notes:read |
notes.read |
notes:write |
notes.write |
calendar:read |
calendar.read |
calendar:write |
calendar.write |
todo:read |
todo.read |
todo:write |
todo.write |
contacts:read |
contacts.read |
contacts:write |
contacts.write |
files:read |
files.read |
files:write |
files.write |
tables:read |
tables.read |
tables:write |
tables.write |
deck:read |
deck.read |
deck:write |
deck.write |
cookbook:read |
cookbook.read |
cookbook:write |
cookbook.write |
sharing:read |
sharing.read |
sharing:write |
sharing.write |
news:read |
news.read |
news:write |
news.write |
collectives:read |
collectives.read |
collectives:write |
collectives.write |
semantic:read |
semantic.read |
Standard OIDC scopes (openid, profile, email, offline_access) are
unchanged — they are defined by OIDC Core and do not use separators.
Consequences
Positive
- Universal IDP compatibility: Dot-separated scopes work with every major identity provider without special configuration
- Industry alignment: Matches the naming convention used by Microsoft, Google, and other major OAuth implementations
- No logic changes: The authorization system uses string comparison and
startswith()prefix matching — changing the separator character requires no algorithmic changes
Negative
- Breaking change: Existing OAuth clients, stored tokens, and IDP configurations must be updated to use the new scope names
- Migration required: An Alembic database migration updates stored scope
strings in
app_passwordsandlogin_flow_sessionstables
Migration
- Database: Alembic migration
004handlesREPLACE(scopes, ':', '.')on stored scope JSON - Keycloak: The realm export (
keycloak/realm-export.json) has been updated; existing Keycloak deployments must re-import or manually update scope definitions - Nextcloud OIDC app: The
astrolabeOAuth client hook (26-configure-astrolabe-oauth.sh) has been updated with new scope names - Existing MCP clients: Must update their scope requests to use dot separators; old colon-separated scope requests will be rejected
Alternatives Considered
Hyphen separator (notes-read)
Rejected: While universally compatible, hyphens are commonly used within scope
component names (e.g., hypothetical file-share.read), creating ambiguity about
which hyphen is the separator.
Underscore separator (notes_read)
Rejected: Also universally compatible but less conventional in the OAuth ecosystem. Dot is the dominant separator in industry practice.
Keep colons with IDP-specific workarounds
Rejected: Requires per-IDP configuration, documentation, and ongoing maintenance. The root cause is a poor separator choice, not an IDP deficiency.