-
-
Notifications
You must be signed in to change notification settings - Fork 0
Configuration Reference
This page documents all available configuration options for TokenAuthority.
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| 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. |
| 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"). |
| 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 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. |
-
Authorization Request: Client includes
scopeparameter with space-separated scope tokens - Consent Screen: Requested scopes are displayed with their descriptions so users understand what access they're granting
- Token Exchange: Client can optionally request a subset of granted scopes (downscoping)
-
Access Token: The
scopeclaim contains the space-delimited scope tokens - Resource Server: Validates that required scopes are present before allowing access
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_supportedfield is automatically included in authorization server metadata (RFC 8414) and protected resource metadata (RFC 9728) - Authorization requests can include a
scopeparameter - The consent screen displays the requested scopes with their descriptions
- Access tokens include a
scopeclaim
Scopes are required by default. To make scopes optional:
config.scopes = {
"read" => "Read your data",
"write" => "Create and modify your data"
}
config.require_scope = falseWhen require_scope is true (the default), authorization requests without a scope parameter receive an invalid_scope error.
Note: Setting
require_scope = truewithout configuringscopeswill raise aConfigurationErrorat startup.
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)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) |
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
To disable scopes entirely:
config.scopes = {}
config.require_scope = falseWhen disabled:
- The
scopeparameter in requests is ignored - The
scopes_supportedfield is omitted from metadata - Access tokens do not include a
scopeclaim
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.
The resources configuration serves multiple purposes:
-
Protected Resource Metadata (RFC 9728): Configures the
/.well-known/oauth-protected-resourceendpoint -
Resource Indicators (RFC 8707): Defines the allowlist of valid resource URIs for the
resourceparameter -
Audience Claim: When
token_audience_urlis nil (the default), theresourceURL is used as theaudclaim 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. |
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. |
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"]
}
}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]
}
}The resources hash is keyed by symbol. When serving protected resource metadata:
- The controller extracts the subdomain from the request
- It looks for a matching key in
resources(e.g., subdomainapimatches key:api) - If no match is found, the first resource in the hash is used as a fallback
- 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
endWhen resources are configured, resource indicators are automatically enabled:
-
Authorization Request: Client includes
resourceparameter(s) specifying target resources -
Consent Screen: Requested resources are displayed using their
resource_namevalues - Token Exchange: Client can request a subset of granted resources (downscoping)
-
Access Token: The
audclaim contains theresourceURL(s) from the requested resource(s) -
Resource Server: Validates that its URI is in the token's
audclaim
The allowlist of valid resource URIs is automatically derived from the resource values in your configuration.
Resources are required by default. To make them optional:
config.require_resource = falseWhen require_resource is true (the default), authorization requests without a resource parameter receive an invalid_target error.
Note: Setting
require_resource = truewithout configuringresourceswill raise aConfigurationErrorat startup.
The aud (audience) claim in access tokens is determined as follows:
- If
token_audience_urlis set, that value is always used - If
token_audience_urlis nil (the default), theresourceURL from the requested resource(s) is used
This design supports MCP and other resource-indicator-based flows where each resource has its own identifier.
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
To disable resource features entirely:
config.resources = {}
config.require_resource = falseWhen disabled:
- The
resourceparameter in requests returns aninvalid_targeterror - Access tokens use
token_audience_urlfor theaudclaim (must be set) - The
/.well-known/oauth-protected-resourceendpoint returns 404
| 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. |
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.
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.
- 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.
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"]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. |
Dynamic client registration is enabled by default. To disable it:
config.dcr_enabled = falseBy 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"}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
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 onlyThese 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. |
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"]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
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.
To only support pre-registered clients:
config.client_metadata_document_enabled = falseUse 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- Installation Guide - Initial setup
- User Authentication - Authentication integration details
Getting Started
- Installation Guide
- MCP Quickstart
- Configuration Reference
- User Authentication
- Protecting API Endpoints
- Customizing Views
- Event Logging
- Instrumentation
Process Flows
- Authorization Code Grant
- Authorization Code Redemption
- Token Refresh
- Token Revocation
- Authorization Server Metadata
- Protected Resource Metadata
- Dynamic Client Registration
- Client Metadata Documents
Development