Skip to content

Configuration Reference

Dick Davis edited this page Jan 25, 2026 · 8 revisions

Configuration Reference

This page documents all available configuration options for TokenAuthority.

Full Configuration Example

TokenAuthority.configure do |config|
  # ==========================================================================
  # General
  # ==========================================================================
  config.secret_key = Rails.application.credentials.secret_key_base

  # Enable event logging (default: true)
  # config.event_logging_enabled = true

  # Enable debug events (default: false)
  # config.event_logging_debug_events = false

  # Enable ActiveSupport::Notifications instrumentation (default: true)
  # config.instrumentation_enabled = true

  # ==========================================================================
  # User Authentication
  # ==========================================================================

  # config.authenticatable_controller = "ApplicationController"
  # config.user_class = "User"

  # ==========================================================================
  # UI/Layout
  # ==========================================================================

  config.consent_page_layout = "application"
  config.error_page_layout = "application"

  # ==========================================================================
  # Scopes
  # ==========================================================================

  config.scopes = {
    "read" => "Read your data",
    "write" => "Create and modify your data"
  }
  # config.require_scope = true  # default

  # ==========================================================================
  # Resources (RFC 9728 / RFC 8707)
  # ==========================================================================

  config.resources = {
    api: {
      resource: "https://api.example.com",
      resource_name: "My API",
      scopes_supported: %w[read write]
    }
  }
  # config.require_resource = true  # default

  # ==========================================================================
  # JWT Access Tokens (RFC 9068)
  # ==========================================================================

  config.token_issuer_url = ENV.fetch("TOKEN_AUTHORITY_ISSUER_URL", "https://example.com")
  # config.token_audience_url = nil  # default; uses resource's authorization_servers
  # config.default_access_token_duration = 300 # 5 minutes
  # config.default_refresh_token_duration = 1_209_600 # 14 days

  # ==========================================================================
  # Server Metadata (RFC 8414)
  # ==========================================================================

  # config.authorization_server_documentation = "https://example.com/docs/oauth"

  # ==========================================================================
  # Dynamic Client Registration (RFC 7591)
  # ==========================================================================

  # config.dcr_enabled = true  # default
  # config.dcr_require_initial_access_token = false
  # config.dcr_initial_access_token_validator = ->(token) { token == "secret" }
  # config.dcr_allowed_grant_types = %w[authorization_code refresh_token]
  # config.dcr_allowed_response_types = %w[code]
  # config.dcr_allowed_token_endpoint_auth_methods = %w[none client_secret_basic client_secret_post client_secret_jwt private_key_jwt]
  # config.dcr_allowed_scopes = %w[read write]
  # config.dcr_client_secret_expiration = nil
  # config.dcr_software_statement_jwks = nil
  # config.dcr_software_statement_required = false
  # config.dcr_jwks_cache_ttl = 3600

  # ==========================================================================
  # Client Metadata Document (draft-ietf-oauth-client-id-metadata-document)
  # ==========================================================================

  # config.client_metadata_document_enabled = true  # default
  # config.client_metadata_document_cache_ttl = 3600
  # config.client_metadata_document_max_response_size = 5120
  # config.client_metadata_document_allowed_hosts = nil
  # config.client_metadata_document_blocked_hosts = []
  # config.client_metadata_document_connect_timeout = 5
  # config.client_metadata_document_read_timeout = 5
end

General Settings

Option Default Description
secret_key (required) Secret key for signing JWTs and generating client secrets. Use Rails.application.credentials.secret_key_base or a dedicated secret.
event_logging_enabled true Enable structured event logging to Rails.logger.
event_logging_debug_events false Enable verbose debug events (PKCE validation, cache hits, etc.).
instrumentation_enabled true Enable ActiveSupport::Notifications instrumentation events.

User Authentication Settings

Option Default Description
authenticatable_controller "ApplicationController" Controller that provides authenticate_user! and current_user methods. See User Authentication.
user_class "User" Class name of your user model (e.g., "User", "Account").

UI/Layout Settings

Option Default Description
consent_page_layout "application" Layout used for the OAuth consent screen.
error_page_layout "application" Layout used for error pages (e.g., invalid redirect URL).

Use a custom layout if you want different styling or navigation for OAuth pages:

config.consent_page_layout = "oauth"
config.error_page_layout = "oauth"

Scopes

Scopes define what access an OAuth client is requesting. When configured, scopes appear on the consent screen and are included in access tokens, allowing resource servers to make fine-grained authorization decisions.

Option Default Description
scopes {} Hash mapping scope tokens to human-readable descriptions. Empty hash disables scopes.
require_scope true When true, clients must include at least one scope in authorization requests.

How Scopes Work

  1. Authorization Request: Client includes scope parameter with space-separated scope tokens
  2. Consent Screen: Requested scopes are displayed with their descriptions so users understand what access they're granting
  3. Token Exchange: Client can optionally request a subset of granted scopes (downscoping)
  4. Access Token: The scope claim contains the space-delimited scope tokens
  5. Resource Server: Validates that required scopes are present before allowing access

Configuration

The scopes hash serves two purposes:

  • Keys are the scope tokens that form the allowlist
  • Values are human-friendly descriptions shown on the consent screen
config.scopes = {
  "read" => "Read your data",
  "write" => "Create and modify your data",
  "delete" => "Delete your data",
  "profile" => "View your profile information",
  "admin" => "Perform administrative actions"
}

When scopes are configured:

  • The scopes_supported field is automatically included in authorization server metadata (RFC 8414) and protected resource metadata (RFC 9728)
  • Authorization requests can include a scope parameter
  • The consent screen displays the requested scopes with their descriptions
  • Access tokens include a scope claim

Requiring Scopes

Scopes are required by default. To make scopes optional:

config.scopes = {
  "read" => "Read your data",
  "write" => "Create and modify your data"
}
config.require_scope = false

When require_scope is true (the default), authorization requests without a scope parameter receive an invalid_scope error.

Note: Setting require_scope = true without configuring scopes will raise a ConfigurationError at startup.

Scope Token Syntax

Scope tokens must conform to RFC 6749 syntax: one or more characters from the set \x21, \x23-\x5B, or \x5D-\x7E (printable ASCII excluding spaces, backslash, and double-quote). In practice, use lowercase letters, numbers, hyphens, underscores, and colons:

# Good scope tokens
config.scopes = {
  "read" => "Read access",
  "write" => "Write access",
  "user-profile" => "Access user profile",
  "mcp:tools" => "Access MCP tools"
}

# Avoid spaces and special characters
# "read write" => invalid (contains space)
# "read\"data" => invalid (contains double-quote)

Scope Validation

Scopes are validated at multiple points in the OAuth flow:

Stage Validation
Authorization Request Requested scopes must be in configured allowlist
Token Exchange Requested scopes must be subset of granted scopes
Token Refresh Requested scopes must be subset of original grant scopes
Dynamic Client Registration Client's requested scope must be in dcr_allowed_scopes (if configured)

Error Handling

When scope validation fails, the authorization server returns an invalid_scope error:

  • Scope token is malformed (invalid characters)
  • Scope is not in the configured allowlist
  • Scope is required but not provided
  • Token exchange/refresh requests scopes not in original grant

Disabling Scopes

To disable scopes entirely:

config.scopes = {}
config.require_scope = false

When disabled:

  • The scope parameter in requests is ignored
  • The scopes_supported field is omitted from metadata
  • Access tokens do not include a scope claim

Related Configuration

When using scopes with dynamic client registration (RFC 7591), you can restrict which scopes dynamically registered clients may request:

config.dcr_allowed_scopes = %w[read write]

See Dynamic Client Registration for details.

Resources (RFC 9728 / RFC 8707)

The resources configuration serves multiple purposes:

  • Protected Resource Metadata (RFC 9728): Configures the /.well-known/oauth-protected-resource endpoint
  • Resource Indicators (RFC 8707): Defines the allowlist of valid resource URIs for the resource parameter
  • Audience Claim: When token_audience_url is nil (the default), the resource URL is used as the aud claim in access tokens
Option Default Description
resources {} Hash of protected resources keyed by symbol. Empty hash disables resource features.
require_resource true When true, clients must include at least one resource in authorization requests.

Resource Configuration Keys

Each resource in the resources hash can include:

Key Required Description
resource Yes The protected resource's identifier URL (per RFC 9728). Used as the aud claim when token_audience_url is nil.
resource_name No Human-readable name shown on the consent screen.
scopes_supported No Array of scopes accepted by this resource.
authorization_servers No List of authorization server issuer URLs (for RFC 9728 metadata).
bearer_methods_supported No Token presentation methods (e.g., ["header"]).
jwks_uri No URL to the resource's JSON Web Key Set.
resource_documentation No URL to developer documentation.
resource_policy_uri No URL to the resource's privacy policy.
resource_tos_uri No URL to the resource's terms of service.

Single Resource Example

For applications with a single protected resource:

config.resources = {
  api: {
    resource: "https://api.example.com",
    resource_name: "My API",
    scopes_supported: %w[read write],
    bearer_methods_supported: ["header"]
  }
}

Multiple Resources Example

For applications with multiple protected resources:

config.resources = {
  api: {
    resource: "https://api.example.com",
    resource_name: "REST API",
    scopes_supported: %w[read write]
  },
  mcp: {
    resource: "https://mcp.example.com",
    resource_name: "MCP Server",
    scopes_supported: %w[mcp:tools mcp:resources]
  }
}

Subdomain-Based Resource Lookup

The resources hash is keyed by symbol. When serving protected resource metadata:

  1. The controller extracts the subdomain from the request
  2. It looks for a matching key in resources (e.g., subdomain api matches key :api)
  3. If no match is found, the first resource in the hash is used as a fallback
  4. If no resources are configured, a 404 is returned

Example routes for multiple subdomains:

# config/routes.rb
Rails.application.routes.draw do
  token_authority_auth_server_routes

  constraints subdomain: "api" do
    token_authority_protected_resource_route
  end

  constraints subdomain: "mcp" do
    token_authority_protected_resource_route
  end
end

Resource Indicators (RFC 8707)

When resources are configured, resource indicators are automatically enabled:

  1. Authorization Request: Client includes resource parameter(s) specifying target resources
  2. Consent Screen: Requested resources are displayed using their resource_name values
  3. Token Exchange: Client can request a subset of granted resources (downscoping)
  4. Access Token: The aud claim contains the resource URL(s) from the requested resource(s)
  5. Resource Server: Validates that its URI is in the token's aud claim

The allowlist of valid resource URIs is automatically derived from the resource values in your configuration.

Requiring Resources

Resources are required by default. To make them optional:

config.require_resource = false

When require_resource is true (the default), authorization requests without a resource parameter receive an invalid_target error.

Note: Setting require_resource = true without configuring resources will raise a ConfigurationError at startup.

Audience Claim Behavior

The aud (audience) claim in access tokens is determined as follows:

  1. If token_audience_url is set, that value is always used
  2. If token_audience_url is nil (the default), the resource URL from the requested resource(s) is used

This design supports MCP and other resource-indicator-based flows where each resource has its own identifier.

Error Handling

When resource validation fails, the authorization server returns an invalid_target error:

  • Resource URI is malformed (not absolute URI, has fragment, etc.)
  • Resource URI is not in the configured allowlist
  • Resource is required but not provided
  • Token exchange requests resources not granted during authorization

Disabling Resources

To disable resource features entirely:

config.resources = {}
config.require_resource = false

When disabled:

  • The resource parameter in requests returns an invalid_target error
  • Access tokens use token_audience_url for the aud claim (must be set)
  • The /.well-known/oauth-protected-resource endpoint returns 404

JWT Access Tokens (RFC 9068)

Option Default Description
token_issuer_url nil The issuer URL for JWT tokens (used as the iss claim). When nil, derived from resources' authorization_servers.
token_audience_url nil The audience URL for JWT tokens. When nil, the aud claim is derived from the resource URL.
default_access_token_duration 300 (5 minutes) Default duration for access tokens in seconds.
default_refresh_token_duration 1_209_600 (14 days) Default duration for refresh tokens in seconds.

Issuer URL Configuration

The issuer URL (iss claim) can be configured in two ways:

Option 1: Explicit issuer URL

config.token_issuer_url = "https://auth.example.com"

Option 2: Derive from authorization_servers (MCP-style)

config.token_issuer_url = nil
config.resources = {
  api: {
    resource: "https://api.example.com",
    authorization_servers: ["https://auth.example.com"]
  }
}
# Issuer will be: "https://auth.example.com"

When token_issuer_url is nil, the issuer is derived from the first resource's authorization_servers array. You must configure at least one of these options.

Audience Claim Configuration

The token_audience_url setting controls how the aud claim is populated:

When set to a URL:

config.token_audience_url = "https://api.example.com"
# All tokens will have: "aud": "https://api.example.com"

When nil (default, recommended for MCP):

config.token_audience_url = nil
config.resources = {
  api: {
    resource: "https://api.example.com",
    resource_name: "My API"
  }
}
# Tokens for this resource will have: "aud": "https://api.example.com"

The resource URL identifies the protected resource and is used as the audience claim, ensuring tokens are bound to specific resources.

Token Duration Notes

  • Access tokens are short-lived by design. The default of 5 minutes balances security with usability.
  • Refresh tokens allow clients to obtain new access tokens without user interaction. The 14-day default is suitable for most applications.
  • Clients can request shorter durations, but never longer than these defaults.

Server Metadata (RFC 8414)

These options configure the OAuth Authorization Server Metadata endpoint at /.well-known/oauth-authorization-server.

Option Default Description
authorization_server_documentation nil URL to developer documentation for your OAuth implementation.

The scopes_supported field in server metadata is automatically derived from config.scopes. If scopes are configured, the keys are included in the metadata response.

Example:

config.scopes = { "read" => "Read access", "write" => "Write access" }
config.authorization_server_documentation = "https://example.com/docs/oauth"

# Results in metadata: "scopes_supported": ["read", "write"]

Dynamic Client Registration (RFC 7591)

These options configure the Dynamic Client Registration endpoint at /oauth/register.

Option Default Description
dcr_enabled true Enable the /oauth/register endpoint.
dcr_require_initial_access_token false Require a Bearer token to register clients.
dcr_initial_access_token_validator nil Proc that validates initial access tokens. Receives the token string, returns true if valid.
dcr_allowed_grant_types ["authorization_code", "refresh_token"] Grant types allowed for dynamically registered clients.
dcr_allowed_response_types ["code"] Response types allowed for dynamically registered clients.
dcr_allowed_token_endpoint_auth_methods ["none", "client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt"] Token endpoint auth methods allowed for dynamically registered clients.
dcr_allowed_scopes nil Array of scopes that dynamically registered clients may request. nil allows any scope from config.scopes.
dcr_client_secret_expiration nil Client secret expiration in seconds. nil means secrets never expire.
dcr_software_statement_jwks nil JWT::JWK::Set for verifying software statement signatures.
dcr_software_statement_required false Require a software statement for registration.
dcr_jwks_cache_ttl 3600 (1 hour) TTL for cached JWKS fetched from jwks_uri.

Disabling Dynamic Client Registration

Dynamic client registration is enabled by default. To disable it:

config.dcr_enabled = false

Protected Registration

By default, when dcr_enabled is true, anyone can register a new OAuth client. To restrict registration, enable initial access token validation:

config.dcr_enabled = true
config.dcr_require_initial_access_token = true
config.dcr_initial_access_token_validator = ->(token) {
  # Validate against your token store
  InitialAccessToken.valid?(token)
}

Clients must then include a valid token when registering:

POST /oauth/register HTTP/1.1
Authorization: Bearer <initial-access-token>
Content-Type: application/json

{"redirect_uris": ["https://app.example.com/callback"], "client_name": "My App"}

About Initial Access Tokens

Initial access tokens are a gatekeeper for the registration endpoint—they control who can register new OAuth clients. TokenAuthority validates tokens via your configured proc but does not manage token creation, storage, or distribution. You must implement your own strategy for:

  • Token generation - How tokens are created (random strings, JWTs, etc.)
  • Token storage - Where tokens are stored (database, external service, etc.)
  • Token distribution - How developers obtain tokens (admin portal, API, email, etc.)
  • Token lifecycle - Expiration, revocation, usage limits, etc.

Common implementation patterns:

# Pattern 1: Database-backed tokens with expiration
config.dcr_initial_access_token_validator = ->(token) {
  InitialAccessToken.where(value: token)
                    .where("expires_at > ?", Time.current)
                    .exists?
}

# Pattern 2: JWT tokens (self-contained, no database lookup)
config.dcr_initial_access_token_validator = ->(token) {
  begin
    JWT.decode(token, Rails.application.credentials.dcr_signing_key, true, algorithm: "HS256")
    true
  rescue JWT::DecodeError
    false
  end
}

# Pattern 3: External validation service
config.dcr_initial_access_token_validator = ->(token) {
  response = HTTParty.post("https://auth.example.com/validate", body: {token: token})
  response.success? && response.parsed_response["valid"]
}

# Pattern 4: Static token for development/testing
config.dcr_initial_access_token_validator = ->(token) {
  ActiveSupport::SecurityUtils.secure_compare(
    token,
    Rails.application.credentials.dcr_static_token
  )
}

Use cases for protected registration:

  • Invite-only access - Issue tokens to approved developers only
  • Audit trail - Track which token was used to register each client
  • Rate limiting - Tie tokens to registration quotas
  • Staged rollout - Control access during beta periods

Restricting Client Capabilities

Limit what dynamically registered clients can do:

config.dcr_allowed_grant_types = ["authorization_code"] # No refresh tokens
config.dcr_allowed_token_endpoint_auth_methods = ["none"] # Public clients only

Client Metadata Document (draft-ietf-oauth-client-id-metadata-document)

These options configure URL-based client identifiers. When enabled, clients can use HTTPS URLs as their client_id instead of registering in advance. The authorization server fetches client metadata from the URL at runtime.

Note: This is the MCP-preferred approach for lightweight, decentralized client registration. URL-based clients are always treated as public clients and require PKCE.

Option Default Description
client_metadata_document_enabled true Enable URL-based client identifiers.
client_metadata_document_cache_ttl 3600 (1 hour) TTL in seconds for cached metadata documents.
client_metadata_document_max_response_size 5120 (5KB) Maximum response size when fetching metadata.
client_metadata_document_allowed_hosts nil (all) Array of allowed hostnames/patterns. nil allows all hosts.
client_metadata_document_blocked_hosts [] Array of blocked hostnames/patterns.
client_metadata_document_connect_timeout 5 Connection timeout in seconds.
client_metadata_document_read_timeout 5 Read timeout in seconds.

Security Considerations

By default, any HTTPS URL can be used as a client_id, meaning any server on the internet can act as an OAuth client to your authorization server. This is appropriate for MCP servers and open ecosystems where you want to allow any compliant client.

For restricted access, configure allowed_hosts to limit which domains can host client metadata documents:

# Only allow clients from trusted domains
config.client_metadata_document_allowed_hosts = ["trusted-partner.com", "*.mycompany.com"]

You can also block specific hosts while allowing others:

# Block internal domains
config.client_metadata_document_blocked_hosts = ["internal.example.com", "*.local"]

SSRF Protection

The metadata document fetcher includes built-in protections against Server-Side Request Forgery (SSRF):

  • Only HTTPS URLs are allowed (HTTP is rejected)
  • Private IP ranges are blocked (RFC 1918 addresses, localhost, link-local)
  • DNS resolution is validated before connecting
  • Response size limits prevent resource exhaustion
  • Configurable timeouts prevent slow-loris attacks

Client Metadata Document Format

URL-based clients must serve a JSON document at their client_id URL with at least these fields:

{
  "client_id": "https://example.com/oauth-client",
  "client_name": "My Application",
  "redirect_uris": ["https://example.com/callback"]
}

The client_id field must match the URL where the document is hosted.

Disabling URL-Based Clients

To only support pre-registered clients:

config.client_metadata_document_enabled = false

Environment-Specific Configuration

Use environment variables or Rails credentials for environment-specific values:

TokenAuthority.configure do |config|
  config.secret_key = Rails.application.credentials.token_authority_secret_key!
  config.token_issuer_url = ENV.fetch("TOKEN_AUTHORITY_ISSUER_URL")

  config.scopes = {
    "read" => "Read your data",
    "write" => "Create and modify your data"
  }

  config.resources = {
    api: {
      resource: ENV.fetch("TOKEN_AUTHORITY_RESOURCE_URL"),
      resource_name: "My API",
      scopes_supported: %w[read write]
    }
  }
end

See Also

Clone this wiki locally