token_authority 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -1
- data/README.md +52 -14
- data/app/controllers/concerns/token_authority/initial_access_token_authentication.rb +2 -2
- data/app/controllers/token_authority/protected_resource_metadata_controller.rb +39 -0
- data/app/helpers/token_authority/authorization_grants_helper.rb +2 -3
- data/app/models/concerns/token_authority/claim_validatable.rb +2 -2
- data/app/models/concerns/token_authority/resourceable.rb +2 -2
- data/app/models/token_authority/access_token.rb +2 -2
- data/app/models/token_authority/access_token_request.rb +1 -1
- data/app/models/token_authority/authorization_request.rb +2 -2
- data/app/models/token_authority/authorization_server_metadata.rb +4 -4
- data/app/models/token_authority/client.rb +4 -4
- data/app/models/token_authority/client_metadata_document.rb +2 -2
- data/app/models/token_authority/client_registration_request.rb +5 -5
- data/app/models/token_authority/jwks_fetcher.rb +1 -1
- data/app/models/token_authority/protected_resource_metadata.rb +110 -31
- data/app/models/token_authority/refresh_token.rb +2 -2
- data/app/models/token_authority/refresh_token_request.rb +1 -1
- data/lib/generators/token_authority/install/templates/token_authority.rb +100 -114
- data/lib/token_authority/configuration.rb +345 -175
- data/lib/token_authority/errors.rb +29 -0
- data/lib/token_authority/routing/constraints.rb +2 -2
- data/lib/token_authority/routing/routes.rb +74 -16
- data/lib/token_authority/version.rb +1 -1
- data/lib/token_authority.rb +2 -2
- metadata +2 -2
- data/app/controllers/token_authority/resource_metadata_controller.rb +0 -12
|
@@ -10,52 +10,55 @@ module TokenAuthority
|
|
|
10
10
|
# Configuration is typically set in a Rails initializer using a configure block
|
|
11
11
|
# that yields this configuration object.
|
|
12
12
|
#
|
|
13
|
-
# @example
|
|
13
|
+
# @example Minimal configuration
|
|
14
14
|
# TokenAuthority.configure do |config|
|
|
15
15
|
# config.secret_key = Rails.application.credentials.secret_key_base
|
|
16
|
-
# config.user_class = "User"
|
|
17
|
-
# config.rfc_9068_audience_url = "https://api.example.com"
|
|
18
|
-
# config.rfc_9068_issuer_url = "https://example.com"
|
|
19
|
-
# end
|
|
20
16
|
#
|
|
21
|
-
# @example Enabling scopes
|
|
22
|
-
# TokenAuthority.configure do |config|
|
|
23
17
|
# config.scopes = {
|
|
24
18
|
# "read" => "Read access to your data",
|
|
25
19
|
# "write" => "Write access to your data"
|
|
26
20
|
# }
|
|
27
|
-
#
|
|
21
|
+
#
|
|
22
|
+
# config.resources = {
|
|
23
|
+
# api: {
|
|
24
|
+
# resource: "https://example.com/api",
|
|
25
|
+
# resource_name: "My API",
|
|
26
|
+
# scopes_supported: %w[read write],
|
|
27
|
+
# authorization_servers: ["https://example.com"]
|
|
28
|
+
# }
|
|
29
|
+
# }
|
|
28
30
|
# end
|
|
29
31
|
#
|
|
30
32
|
# @since 0.2.0
|
|
31
33
|
class Configuration
|
|
34
|
+
# ==========================================================================
|
|
35
|
+
# General
|
|
36
|
+
# ==========================================================================
|
|
37
|
+
|
|
32
38
|
# @!attribute [rw] secret_key
|
|
33
39
|
# The secret key used for JWT signing and HMAC operations.
|
|
34
40
|
# This should be a secure random string, typically derived from Rails credentials.
|
|
35
41
|
# @return [String] the secret key
|
|
36
42
|
attr_accessor :secret_key
|
|
37
43
|
|
|
38
|
-
# @!attribute [rw]
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
attr_accessor :rfc_9068_audience_url
|
|
44
|
+
# @!attribute [rw] event_logging_enabled
|
|
45
|
+
# Enable structured event logging for OAuth flows and security events.
|
|
46
|
+
# @return [Boolean] true if enabled (default: true)
|
|
47
|
+
attr_accessor :event_logging_enabled
|
|
43
48
|
|
|
44
|
-
# @!attribute [rw]
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
attr_accessor :rfc_9068_issuer_url
|
|
49
|
+
# @!attribute [rw] event_logging_debug_events
|
|
50
|
+
# Enable debug-level event logging for troubleshooting.
|
|
51
|
+
# @return [Boolean] true if enabled (default: false)
|
|
52
|
+
attr_accessor :event_logging_debug_events
|
|
49
53
|
|
|
50
|
-
# @!attribute [rw]
|
|
51
|
-
#
|
|
52
|
-
# @return [
|
|
53
|
-
attr_accessor :
|
|
54
|
+
# @!attribute [rw] instrumentation_enabled
|
|
55
|
+
# Enable ActiveSupport::Notifications instrumentation for performance monitoring.
|
|
56
|
+
# @return [Boolean] true if enabled (default: true)
|
|
57
|
+
attr_accessor :instrumentation_enabled
|
|
54
58
|
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
attr_accessor :rfc_9068_default_refresh_token_duration
|
|
59
|
+
# ==========================================================================
|
|
60
|
+
# User Authentication
|
|
61
|
+
# ==========================================================================
|
|
59
62
|
|
|
60
63
|
# @!attribute [rw] authenticatable_controller
|
|
61
64
|
# The controller class name that provides authentication methods.
|
|
@@ -68,6 +71,10 @@ module TokenAuthority
|
|
|
68
71
|
# @return [String] user class name (default: "User")
|
|
69
72
|
attr_accessor :user_class
|
|
70
73
|
|
|
74
|
+
# ==========================================================================
|
|
75
|
+
# UI/Layout
|
|
76
|
+
# ==========================================================================
|
|
77
|
+
|
|
71
78
|
# @!attribute [rw] consent_page_layout
|
|
72
79
|
# The layout to use for the OAuth consent screen.
|
|
73
80
|
# @return [String] layout name (default: "application")
|
|
@@ -78,10 +85,9 @@ module TokenAuthority
|
|
|
78
85
|
# @return [String] layout name (default: "application")
|
|
79
86
|
attr_accessor :error_page_layout
|
|
80
87
|
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
attr_accessor :rfc_8414_service_documentation
|
|
88
|
+
# ==========================================================================
|
|
89
|
+
# Scopes
|
|
90
|
+
# ==========================================================================
|
|
85
91
|
|
|
86
92
|
# @!attribute [rw] scopes
|
|
87
93
|
# Hash mapping scope strings to human-readable descriptions.
|
|
@@ -96,109 +102,180 @@ module TokenAuthority
|
|
|
96
102
|
|
|
97
103
|
# @!attribute [rw] require_scope
|
|
98
104
|
# Whether clients must include a scope parameter in authorization requests.
|
|
99
|
-
# @return [Boolean] true if scope is required (default:
|
|
105
|
+
# @return [Boolean] true if scope is required (default: true)
|
|
100
106
|
attr_accessor :require_scope
|
|
101
107
|
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
attr_accessor :rfc_9728_resource
|
|
106
|
-
|
|
107
|
-
# @!attribute [rw] rfc_9728_scopes_supported
|
|
108
|
-
# Array of OAuth scopes supported by the protected resource per RFC 9728.
|
|
109
|
-
# @return [Array<String>, nil] supported scopes
|
|
110
|
-
attr_accessor :rfc_9728_scopes_supported
|
|
111
|
-
|
|
112
|
-
# @!attribute [rw] rfc_9728_authorization_servers
|
|
113
|
-
# Array of authorization server issuer URLs per RFC 9728.
|
|
114
|
-
# @return [Array<String>, nil] authorization server URLs
|
|
115
|
-
attr_accessor :rfc_9728_authorization_servers
|
|
116
|
-
|
|
117
|
-
# @!attribute [rw] rfc_9728_bearer_methods_supported
|
|
118
|
-
# Array of bearer token methods supported per RFC 9728.
|
|
119
|
-
# @return [Array<String>, nil] bearer methods
|
|
120
|
-
attr_accessor :rfc_9728_bearer_methods_supported
|
|
121
|
-
|
|
122
|
-
# @!attribute [rw] rfc_9728_jwks_uri
|
|
123
|
-
# URL to the JWKS for protected resource per RFC 9728.
|
|
124
|
-
# @return [String, nil] JWKS URI
|
|
125
|
-
attr_accessor :rfc_9728_jwks_uri
|
|
126
|
-
|
|
127
|
-
# @!attribute [rw] rfc_9728_resource_name
|
|
128
|
-
# Human-readable name of the protected resource per RFC 9728.
|
|
129
|
-
# @return [String, nil] resource name
|
|
130
|
-
attr_accessor :rfc_9728_resource_name
|
|
131
|
-
|
|
132
|
-
# @!attribute [rw] rfc_9728_resource_documentation
|
|
133
|
-
# URL to documentation for the protected resource per RFC 9728.
|
|
134
|
-
# @return [String, nil] documentation URL
|
|
135
|
-
attr_accessor :rfc_9728_resource_documentation
|
|
108
|
+
# ==========================================================================
|
|
109
|
+
# Resources (RFC 9728 / RFC 8707)
|
|
110
|
+
# ==========================================================================
|
|
136
111
|
|
|
137
|
-
# @!attribute [rw]
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
|
|
112
|
+
# @!attribute [rw] resources
|
|
113
|
+
# Protected resource metadata keyed by resource identifier (RFC 9728).
|
|
114
|
+
#
|
|
115
|
+
# Maps resource symbols to metadata hashes. When a request arrives at
|
|
116
|
+
# the /.well-known/oauth-protected-resource endpoint, the controller extracts
|
|
117
|
+
# the subdomain and looks it up in this hash. If no match is found, the first
|
|
118
|
+
# resource in the hash is used as the default.
|
|
119
|
+
#
|
|
120
|
+
# For single-resource deployments, simply configure one entry - it will be used
|
|
121
|
+
# for all requests regardless of subdomain.
|
|
122
|
+
#
|
|
123
|
+
# Each entry must include the :resource field (required per RFC 9728). The
|
|
124
|
+
# validate! method raises ConfigurationError if any entry is missing this field.
|
|
125
|
+
# All other fields are optional and will be omitted from responses if not set.
|
|
126
|
+
#
|
|
127
|
+
# == Available Resource Options
|
|
128
|
+
#
|
|
129
|
+
# [resource] (Required) The protected resource URI. Used as the
|
|
130
|
+
# audience (aud) claim in JWT access tokens.
|
|
131
|
+
# [resource_name] Human-readable name shown on the consent screen.
|
|
132
|
+
# [scopes_supported] Array of scope strings this resource accepts.
|
|
133
|
+
# [authorization_servers] Array of authorization server URLs. The first entry
|
|
134
|
+
# is used as the issuer (iss) claim if token_issuer_url
|
|
135
|
+
# is not set. Also appears in RFC 9728 metadata responses.
|
|
136
|
+
# [bearer_methods_supported] Array of supported bearer token methods (e.g., ["header"]).
|
|
137
|
+
# [jwks_uri] URI for the JSON Web Key Set endpoint.
|
|
138
|
+
# [resource_documentation] URL for API documentation.
|
|
139
|
+
# [resource_policy_uri] URL for the privacy policy.
|
|
140
|
+
# [resource_tos_uri] URL for terms of service.
|
|
141
|
+
#
|
|
142
|
+
# @example Single resource with all options
|
|
143
|
+
# config.resources = {
|
|
144
|
+
# api: {
|
|
145
|
+
# resource: "https://example.com/api",
|
|
146
|
+
# resource_name: "Example API",
|
|
147
|
+
# scopes_supported: %w[read write admin],
|
|
148
|
+
# authorization_servers: ["https://example.com"],
|
|
149
|
+
# bearer_methods_supported: ["header"],
|
|
150
|
+
# jwks_uri: "https://example.com/.well-known/jwks.json",
|
|
151
|
+
# resource_documentation: "https://example.com/docs/api",
|
|
152
|
+
# resource_policy_uri: "https://example.com/privacy",
|
|
153
|
+
# resource_tos_uri: "https://example.com/terms"
|
|
154
|
+
# }
|
|
155
|
+
# }
|
|
156
|
+
#
|
|
157
|
+
# @example Multi-resource deployment with subdomains
|
|
158
|
+
# config.resources = {
|
|
159
|
+
# api: {
|
|
160
|
+
# resource: "https://api.example.com",
|
|
161
|
+
# resource_name: "REST API",
|
|
162
|
+
# scopes_supported: %w[api:read api:write],
|
|
163
|
+
# authorization_servers: ["https://auth.example.com"]
|
|
164
|
+
# },
|
|
165
|
+
# mcp: {
|
|
166
|
+
# resource: "https://mcp.example.com",
|
|
167
|
+
# resource_name: "MCP Server",
|
|
168
|
+
# scopes_supported: %w[mcp:tools mcp:prompts mcp:resources],
|
|
169
|
+
# authorization_servers: ["https://auth.example.com"]
|
|
170
|
+
# }
|
|
171
|
+
# }
|
|
172
|
+
#
|
|
173
|
+
# @return [Hash{Symbol => Hash}, nil] resource identifier to metadata mapping
|
|
174
|
+
attr_accessor :resources
|
|
141
175
|
|
|
142
|
-
# @!attribute [rw]
|
|
143
|
-
#
|
|
144
|
-
# @return [
|
|
145
|
-
attr_accessor :
|
|
176
|
+
# @!attribute [rw] require_resource
|
|
177
|
+
# Whether clients must include a resource parameter in authorization requests.
|
|
178
|
+
# @return [Boolean] true if resource is required (default: true)
|
|
179
|
+
attr_accessor :require_resource
|
|
146
180
|
|
|
147
|
-
#
|
|
181
|
+
# ==========================================================================
|
|
182
|
+
# JWT Access Tokens (RFC 9068)
|
|
183
|
+
# ==========================================================================
|
|
184
|
+
|
|
185
|
+
# @!attribute [rw] token_audience_url
|
|
186
|
+
# The default audience (aud) claim for JWT access tokens per RFC 9068.
|
|
187
|
+
# Identifies the intended recipient of the token (typically the API server).
|
|
188
|
+
# @return [String, nil] the audience URL
|
|
189
|
+
attr_accessor :token_audience_url
|
|
190
|
+
|
|
191
|
+
# @!attribute [rw] token_issuer_url
|
|
192
|
+
# The issuer (iss) claim for JWT access tokens per RFC 9068.
|
|
193
|
+
# Identifies the authorization server that issued the token.
|
|
194
|
+
# @return [String, nil] the issuer URL
|
|
195
|
+
attr_accessor :token_issuer_url
|
|
196
|
+
|
|
197
|
+
# @!attribute [rw] default_access_token_duration
|
|
198
|
+
# Default lifetime for access tokens in seconds.
|
|
199
|
+
# @return [Integer] duration in seconds (default: 300)
|
|
200
|
+
attr_accessor :default_access_token_duration
|
|
201
|
+
|
|
202
|
+
# @!attribute [rw] default_refresh_token_duration
|
|
203
|
+
# Default lifetime for refresh tokens in seconds.
|
|
204
|
+
# @return [Integer] duration in seconds (default: 1,209,600)
|
|
205
|
+
attr_accessor :default_refresh_token_duration
|
|
206
|
+
|
|
207
|
+
# ==========================================================================
|
|
208
|
+
# Server Metadata (RFC 8414)
|
|
209
|
+
# ==========================================================================
|
|
210
|
+
|
|
211
|
+
# @!attribute [rw] authorization_server_documentation
|
|
212
|
+
# URL for service documentation in authorization server metadata per RFC 8414.
|
|
213
|
+
# @return [String, nil] documentation URL
|
|
214
|
+
attr_accessor :authorization_server_documentation
|
|
215
|
+
|
|
216
|
+
# ==========================================================================
|
|
217
|
+
# Dynamic Client Registration (RFC 7591)
|
|
218
|
+
# ==========================================================================
|
|
219
|
+
|
|
220
|
+
# @!attribute [rw] dcr_enabled
|
|
148
221
|
# Enable dynamic client registration per RFC 7591.
|
|
149
|
-
# @return [Boolean] true if enabled (default:
|
|
150
|
-
attr_accessor :
|
|
222
|
+
# @return [Boolean] true if enabled (default: true)
|
|
223
|
+
attr_accessor :dcr_enabled
|
|
151
224
|
|
|
152
|
-
# @!attribute [rw]
|
|
225
|
+
# @!attribute [rw] dcr_require_initial_access_token
|
|
153
226
|
# Require initial access token for client registration per RFC 7591.
|
|
154
227
|
# @return [Boolean] true if required (default: false)
|
|
155
|
-
attr_accessor :
|
|
228
|
+
attr_accessor :dcr_require_initial_access_token
|
|
156
229
|
|
|
157
|
-
# @!attribute [rw]
|
|
230
|
+
# @!attribute [rw] dcr_initial_access_token_validator
|
|
158
231
|
# Callable object to validate initial access tokens.
|
|
159
232
|
# Should accept a token string and return true/false.
|
|
160
233
|
# @return [Proc, nil] validator callable
|
|
161
|
-
attr_accessor :
|
|
234
|
+
attr_accessor :dcr_initial_access_token_validator
|
|
162
235
|
|
|
163
|
-
# @!attribute [rw]
|
|
236
|
+
# @!attribute [rw] dcr_allowed_grant_types
|
|
164
237
|
# Array of grant types allowed during client registration per RFC 7591.
|
|
165
238
|
# @return [Array<String>] allowed grant types
|
|
166
|
-
attr_accessor :
|
|
239
|
+
attr_accessor :dcr_allowed_grant_types
|
|
167
240
|
|
|
168
|
-
# @!attribute [rw]
|
|
241
|
+
# @!attribute [rw] dcr_allowed_response_types
|
|
169
242
|
# Array of response types allowed during client registration per RFC 7591.
|
|
170
243
|
# @return [Array<String>] allowed response types
|
|
171
|
-
attr_accessor :
|
|
244
|
+
attr_accessor :dcr_allowed_response_types
|
|
172
245
|
|
|
173
|
-
# @!attribute [rw]
|
|
246
|
+
# @!attribute [rw] dcr_allowed_scopes
|
|
174
247
|
# Array of scopes allowed during client registration per RFC 7591.
|
|
175
248
|
# @return [Array<String>, nil] allowed scopes
|
|
176
|
-
attr_accessor :
|
|
249
|
+
attr_accessor :dcr_allowed_scopes
|
|
177
250
|
|
|
178
|
-
# @!attribute [rw]
|
|
251
|
+
# @!attribute [rw] dcr_allowed_token_endpoint_auth_methods
|
|
179
252
|
# Array of token endpoint authentication methods allowed per RFC 7591.
|
|
180
253
|
# @return [Array<String>] allowed auth methods
|
|
181
|
-
attr_accessor :
|
|
254
|
+
attr_accessor :dcr_allowed_token_endpoint_auth_methods
|
|
182
255
|
|
|
183
|
-
# @!attribute [rw]
|
|
256
|
+
# @!attribute [rw] dcr_client_secret_expiration
|
|
184
257
|
# Duration in seconds before client secrets expire, or nil for no expiration.
|
|
185
258
|
# @return [Integer, nil] expiration duration in seconds
|
|
186
|
-
attr_accessor :
|
|
259
|
+
attr_accessor :dcr_client_secret_expiration
|
|
187
260
|
|
|
188
|
-
# @!attribute [rw]
|
|
261
|
+
# @!attribute [rw] dcr_software_statement_jwks
|
|
189
262
|
# JWKS for verifying software statements during registration per RFC 7591.
|
|
190
263
|
# @return [Hash, nil] JWKS
|
|
191
|
-
attr_accessor :
|
|
264
|
+
attr_accessor :dcr_software_statement_jwks
|
|
192
265
|
|
|
193
|
-
# @!attribute [rw]
|
|
266
|
+
# @!attribute [rw] dcr_software_statement_required
|
|
194
267
|
# Require software statements during client registration per RFC 7591.
|
|
195
268
|
# @return [Boolean] true if required (default: false)
|
|
196
|
-
attr_accessor :
|
|
269
|
+
attr_accessor :dcr_software_statement_required
|
|
197
270
|
|
|
198
|
-
# @!attribute [rw]
|
|
271
|
+
# @!attribute [rw] dcr_jwks_cache_ttl
|
|
199
272
|
# Time-to-live for cached JWKS in seconds.
|
|
200
273
|
# @return [Integer] TTL in seconds (default: 3600)
|
|
201
|
-
attr_accessor :
|
|
274
|
+
attr_accessor :dcr_jwks_cache_ttl
|
|
275
|
+
|
|
276
|
+
# ==========================================================================
|
|
277
|
+
# Client Metadata Document (draft-ietf-oauth-client-id-metadata-document)
|
|
278
|
+
# ==========================================================================
|
|
202
279
|
|
|
203
280
|
# @!attribute [rw] client_metadata_document_enabled
|
|
204
281
|
# Enable support for client metadata documents (URL-based client IDs).
|
|
@@ -235,40 +312,12 @@ module TokenAuthority
|
|
|
235
312
|
# @return [Integer] timeout in seconds (default: 5)
|
|
236
313
|
attr_accessor :client_metadata_document_read_timeout
|
|
237
314
|
|
|
238
|
-
# @!attribute [rw] rfc_8707_resources
|
|
239
|
-
# Hash mapping resource URIs to human-readable descriptions per RFC 8707.
|
|
240
|
-
# Set to nil or empty hash to disable resource indicators.
|
|
241
|
-
# @example
|
|
242
|
-
# config.rfc_8707_resources = {
|
|
243
|
-
# "https://api.example.com" => "Main API",
|
|
244
|
-
# "https://files.example.com" => "File Storage API"
|
|
245
|
-
# }
|
|
246
|
-
# @return [Hash{String => String}, nil] resource mappings
|
|
247
|
-
attr_accessor :rfc_8707_resources
|
|
248
|
-
|
|
249
|
-
# @!attribute [rw] rfc_8707_require_resource
|
|
250
|
-
# Whether clients must include a resource parameter per RFC 8707.
|
|
251
|
-
# @return [Boolean] true if required (default: false)
|
|
252
|
-
attr_accessor :rfc_8707_require_resource
|
|
253
|
-
|
|
254
|
-
# @!attribute [rw] event_logging_enabled
|
|
255
|
-
# Enable structured event logging for OAuth flows and security events.
|
|
256
|
-
# @return [Boolean] true if enabled (default: true)
|
|
257
|
-
attr_accessor :event_logging_enabled
|
|
258
|
-
|
|
259
|
-
# @!attribute [rw] event_logging_debug_events
|
|
260
|
-
# Enable debug-level event logging for troubleshooting.
|
|
261
|
-
# @return [Boolean] true if enabled (default: false)
|
|
262
|
-
attr_accessor :event_logging_debug_events
|
|
263
|
-
|
|
264
|
-
# @!attribute [rw] instrumentation_enabled
|
|
265
|
-
# Enable ActiveSupport::Notifications instrumentation for performance monitoring.
|
|
266
|
-
# @return [Boolean] true if enabled (default: true)
|
|
267
|
-
attr_accessor :instrumentation_enabled
|
|
268
|
-
|
|
269
315
|
def initialize
|
|
270
316
|
# General
|
|
271
317
|
@secret_key = nil
|
|
318
|
+
@event_logging_enabled = true
|
|
319
|
+
@event_logging_debug_events = false
|
|
320
|
+
@instrumentation_enabled = true
|
|
272
321
|
|
|
273
322
|
# User Authentication
|
|
274
323
|
@authenticatable_controller = "ApplicationController"
|
|
@@ -279,41 +328,34 @@ module TokenAuthority
|
|
|
279
328
|
@error_page_layout = "application"
|
|
280
329
|
|
|
281
330
|
# Scopes
|
|
282
|
-
@scopes =
|
|
283
|
-
@require_scope =
|
|
331
|
+
@scopes = {}
|
|
332
|
+
@require_scope = true
|
|
333
|
+
|
|
334
|
+
# Resources
|
|
335
|
+
@resources = {}
|
|
336
|
+
@require_resource = true
|
|
284
337
|
|
|
285
338
|
# JWT Access Tokens (RFC 9068)
|
|
286
|
-
@
|
|
287
|
-
@
|
|
288
|
-
@
|
|
289
|
-
@
|
|
339
|
+
@token_audience_url = nil
|
|
340
|
+
@token_issuer_url = nil
|
|
341
|
+
@default_access_token_duration = 300 # 5 minutes in seconds
|
|
342
|
+
@default_refresh_token_duration = 1_209_600 # 14 days in seconds
|
|
290
343
|
|
|
291
344
|
# Server Metadata (RFC 8414)
|
|
292
|
-
@
|
|
293
|
-
|
|
294
|
-
# Protected Resource Metadata (RFC 9728)
|
|
295
|
-
@rfc_9728_resource = nil
|
|
296
|
-
@rfc_9728_scopes_supported = nil
|
|
297
|
-
@rfc_9728_authorization_servers = nil
|
|
298
|
-
@rfc_9728_bearer_methods_supported = nil
|
|
299
|
-
@rfc_9728_jwks_uri = nil
|
|
300
|
-
@rfc_9728_resource_name = nil
|
|
301
|
-
@rfc_9728_resource_documentation = nil
|
|
302
|
-
@rfc_9728_resource_policy_uri = nil
|
|
303
|
-
@rfc_9728_resource_tos_uri = nil
|
|
345
|
+
@authorization_server_documentation = nil
|
|
304
346
|
|
|
305
347
|
# Dynamic Client Registration (RFC 7591)
|
|
306
|
-
@
|
|
307
|
-
@
|
|
308
|
-
@
|
|
309
|
-
@
|
|
310
|
-
@
|
|
311
|
-
@
|
|
312
|
-
@
|
|
313
|
-
@
|
|
314
|
-
@
|
|
315
|
-
@
|
|
316
|
-
@
|
|
348
|
+
@dcr_enabled = true
|
|
349
|
+
@dcr_require_initial_access_token = false
|
|
350
|
+
@dcr_initial_access_token_validator = nil
|
|
351
|
+
@dcr_allowed_grant_types = %w[authorization_code refresh_token]
|
|
352
|
+
@dcr_allowed_response_types = %w[code]
|
|
353
|
+
@dcr_allowed_scopes = nil
|
|
354
|
+
@dcr_allowed_token_endpoint_auth_methods = %w[none client_secret_basic client_secret_post client_secret_jwt private_key_jwt]
|
|
355
|
+
@dcr_client_secret_expiration = nil
|
|
356
|
+
@dcr_software_statement_jwks = nil
|
|
357
|
+
@dcr_software_statement_required = false
|
|
358
|
+
@dcr_jwks_cache_ttl = 3600
|
|
317
359
|
|
|
318
360
|
# Client Metadata Document (draft-ietf-oauth-client-id-metadata-document)
|
|
319
361
|
@client_metadata_document_enabled = true
|
|
@@ -323,17 +365,6 @@ module TokenAuthority
|
|
|
323
365
|
@client_metadata_document_blocked_hosts = []
|
|
324
366
|
@client_metadata_document_connect_timeout = 5
|
|
325
367
|
@client_metadata_document_read_timeout = 5
|
|
326
|
-
|
|
327
|
-
# Resource Indicators (RFC 8707)
|
|
328
|
-
@rfc_8707_resources = nil
|
|
329
|
-
@rfc_8707_require_resource = false
|
|
330
|
-
|
|
331
|
-
# Event Logging
|
|
332
|
-
@event_logging_enabled = true
|
|
333
|
-
@event_logging_debug_events = false
|
|
334
|
-
|
|
335
|
-
# Instrumentation
|
|
336
|
-
@instrumentation_enabled = true
|
|
337
368
|
end
|
|
338
369
|
|
|
339
370
|
# Checks whether the scopes feature is enabled.
|
|
@@ -344,28 +375,167 @@ module TokenAuthority
|
|
|
344
375
|
scopes.is_a?(Hash) && scopes.any?
|
|
345
376
|
end
|
|
346
377
|
|
|
347
|
-
# Checks whether
|
|
348
|
-
#
|
|
378
|
+
# Checks whether resources are configured.
|
|
379
|
+
# Resources are enabled when at least one resource is configured.
|
|
380
|
+
#
|
|
381
|
+
# @return [Boolean] true if resources are enabled
|
|
382
|
+
def resources_enabled?
|
|
383
|
+
resource_registry.any?
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Builds a mapping of resource URIs to display names from resource configuration.
|
|
387
|
+
#
|
|
388
|
+
# This derives the RFC 8707 resource allowlist from the resources configuration.
|
|
389
|
+
# Each configured resource's :resource URI becomes a key, with its :resource_name
|
|
390
|
+
# (or the URI itself) as the display name.
|
|
391
|
+
#
|
|
392
|
+
# The result is used for:
|
|
393
|
+
# - Validating resource indicators in authorization requests
|
|
394
|
+
# - Displaying resource names on the consent screen
|
|
395
|
+
#
|
|
396
|
+
# @return [Hash{String => String}] mapping of resource URIs to display names
|
|
349
397
|
#
|
|
350
|
-
# @
|
|
351
|
-
|
|
352
|
-
|
|
398
|
+
# @example With resources configured
|
|
399
|
+
# config.resources = {
|
|
400
|
+
# api: { resource: "https://api.example.com", resource_name: "REST API" },
|
|
401
|
+
# mcp: { resource: "https://mcp.example.com", resource_name: "MCP Server" }
|
|
402
|
+
# }
|
|
403
|
+
# config.resource_registry
|
|
404
|
+
# # => { "https://api.example.com" => "REST API", "https://mcp.example.com" => "MCP Server" }
|
|
405
|
+
def resource_registry
|
|
406
|
+
return {} unless resources.is_a?(Hash)
|
|
407
|
+
|
|
408
|
+
resources.each_with_object({}) do |(_key, config), registry|
|
|
409
|
+
next unless config.is_a?(Hash) && config[:resource].present?
|
|
410
|
+
|
|
411
|
+
uri = config[:resource]
|
|
412
|
+
registry[uri] = config[:resource_name] || uri
|
|
413
|
+
end
|
|
353
414
|
end
|
|
354
415
|
|
|
355
416
|
# Validates the configuration for internal consistency.
|
|
356
417
|
# Ensures that required features are properly configured before use.
|
|
357
418
|
#
|
|
358
419
|
# @raise [ConfigurationError] if require_scope is true but scopes are not configured
|
|
359
|
-
# @raise [ConfigurationError] if
|
|
420
|
+
# @raise [ConfigurationError] if require_resource is true but no resources are configured
|
|
421
|
+
# @raise [ConfigurationError] if any resource entry is missing the required :resource field
|
|
422
|
+
# @raise [ConfigurationError] if no issuer URL is available
|
|
360
423
|
# @return [void]
|
|
361
424
|
def validate!
|
|
362
425
|
if require_scope && !scopes_enabled?
|
|
363
426
|
raise ConfigurationError, "require_scope is true but no scopes are configured"
|
|
364
427
|
end
|
|
365
428
|
|
|
366
|
-
if
|
|
367
|
-
|
|
429
|
+
# Validate resource entries first (before checking if any valid resources exist)
|
|
430
|
+
if resources.is_a?(Hash)
|
|
431
|
+
resources.each do |key, config|
|
|
432
|
+
next unless config.is_a?(Hash)
|
|
433
|
+
|
|
434
|
+
if config[:resource].blank?
|
|
435
|
+
raise ConfigurationError, "resource :#{key} is missing the required :resource field"
|
|
436
|
+
end
|
|
437
|
+
end
|
|
368
438
|
end
|
|
439
|
+
|
|
440
|
+
if require_resource && !resources_enabled?
|
|
441
|
+
raise ConfigurationError, "require_resource is true but no resources are configured"
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
if issuer_url.blank?
|
|
445
|
+
raise ConfigurationError,
|
|
446
|
+
"no issuer URL configured: set token_issuer_url or add authorization_servers to a resource"
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# Resolves protected resource configuration using subdomain-aware lookup.
|
|
451
|
+
#
|
|
452
|
+
# Lookup strategy:
|
|
453
|
+
# 1. If resource_key is present, look it up as a symbol in resources
|
|
454
|
+
# 2. If not found or resource_key is blank, use the first resource in the hash
|
|
455
|
+
# 3. If resources is empty, return nil (controller will 404)
|
|
456
|
+
#
|
|
457
|
+
# @param resource_key [String, nil] the subdomain or lookup key
|
|
458
|
+
# @return [Hash, nil] the resource metadata, or nil if not configured
|
|
459
|
+
#
|
|
460
|
+
# @example Subdomain-specific lookup
|
|
461
|
+
# config.resources = { api: { resource: "https://api.example.com" } }
|
|
462
|
+
# config.protected_resource_for("api") # => { resource: "https://api.example.com" }
|
|
463
|
+
#
|
|
464
|
+
# @example Fallback to first resource
|
|
465
|
+
# config.resources = { api: { resource: "https://api.example.com" } }
|
|
466
|
+
# config.protected_resource_for("unknown") # => { resource: "https://api.example.com" }
|
|
467
|
+
#
|
|
468
|
+
# @example Not configured
|
|
469
|
+
# config.resources = {}
|
|
470
|
+
# config.protected_resource_for(nil) # => nil (will cause 404)
|
|
471
|
+
def protected_resource_for(resource_key)
|
|
472
|
+
return nil unless resources.is_a?(Hash) && resources.any?
|
|
473
|
+
|
|
474
|
+
# Try subdomain lookup first, fall back to first resource
|
|
475
|
+
if resource_key.present?
|
|
476
|
+
resources[resource_key.to_sym] || resources.values.first
|
|
477
|
+
else
|
|
478
|
+
resources.values.first
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# Returns the effective audience URL for JWT tokens.
|
|
483
|
+
#
|
|
484
|
+
# The audience URL is determined as follows:
|
|
485
|
+
# 1. If token_audience_url is set, use that value
|
|
486
|
+
# 2. Otherwise, derive from the first resource's :resource URL
|
|
487
|
+
#
|
|
488
|
+
# @return [String, nil] the audience URL, or nil if not configured
|
|
489
|
+
#
|
|
490
|
+
# @example Explicit audience URL
|
|
491
|
+
# config.token_audience_url = "https://api.example.com"
|
|
492
|
+
# config.audience_url # => "https://api.example.com"
|
|
493
|
+
#
|
|
494
|
+
# @example Derived from resources
|
|
495
|
+
# config.token_audience_url = nil
|
|
496
|
+
# config.resources = { api: { resource: "https://api.example.com" } }
|
|
497
|
+
# config.audience_url # => "https://api.example.com"
|
|
498
|
+
def audience_url
|
|
499
|
+
return token_audience_url if token_audience_url.present?
|
|
500
|
+
|
|
501
|
+
# Derive from first resource's :resource URL
|
|
502
|
+
return nil unless resources.is_a?(Hash) && resources.any?
|
|
503
|
+
|
|
504
|
+
first_resource = resources.values.first
|
|
505
|
+
return nil unless first_resource.is_a?(Hash)
|
|
506
|
+
|
|
507
|
+
first_resource[:resource]
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
# Returns the effective issuer URL for JWT tokens.
|
|
511
|
+
#
|
|
512
|
+
# The issuer URL is determined as follows:
|
|
513
|
+
# 1. If token_issuer_url is set, use that value
|
|
514
|
+
# 2. Otherwise, derive from the first resource's authorization_servers
|
|
515
|
+
#
|
|
516
|
+
# @return [String, nil] the issuer URL, or nil if not configured
|
|
517
|
+
#
|
|
518
|
+
# @example Explicit issuer URL
|
|
519
|
+
# config.token_issuer_url = "https://auth.example.com"
|
|
520
|
+
# config.issuer_url # => "https://auth.example.com"
|
|
521
|
+
#
|
|
522
|
+
# @example Derived from authorization_servers
|
|
523
|
+
# config.token_issuer_url = nil
|
|
524
|
+
# config.resources = { api: { authorization_servers: ["https://auth.example.com"] } }
|
|
525
|
+
# config.issuer_url # => "https://auth.example.com"
|
|
526
|
+
def issuer_url
|
|
527
|
+
return token_issuer_url if token_issuer_url.present?
|
|
528
|
+
|
|
529
|
+
# Derive from first resource's authorization_servers
|
|
530
|
+
return nil unless resources.is_a?(Hash) && resources.any?
|
|
531
|
+
|
|
532
|
+
first_resource = resources.values.first
|
|
533
|
+
return nil unless first_resource.is_a?(Hash)
|
|
534
|
+
|
|
535
|
+
auth_servers = first_resource[:authorization_servers]
|
|
536
|
+
return nil unless auth_servers.is_a?(Array) && auth_servers.any?
|
|
537
|
+
|
|
538
|
+
auth_servers.first
|
|
369
539
|
end
|
|
370
540
|
end
|
|
371
541
|
|