doorkeeper 5.9.5 → 6.0.0.beta2

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +70 -3
  3. data/README.md +204 -0
  4. data/app/controllers/doorkeeper/applications_controller.rb +38 -9
  5. data/app/controllers/doorkeeper/authorizations_controller.rb +17 -3
  6. data/app/controllers/doorkeeper/metadata_controller.rb +20 -0
  7. data/app/controllers/doorkeeper/tokens_controller.rb +15 -2
  8. data/app/views/doorkeeper/authorizations/new.html.erb +24 -0
  9. data/config/locales/en.yml +2 -0
  10. data/lib/doorkeeper/client_authentication/credentials.rb +11 -0
  11. data/lib/doorkeeper/client_authentication/fallback_method.rb +19 -0
  12. data/lib/doorkeeper/client_authentication/legacy_callable.rb +52 -0
  13. data/lib/doorkeeper/client_authentication/method.rb +40 -0
  14. data/lib/doorkeeper/client_authentication/registry.rb +47 -0
  15. data/lib/doorkeeper/client_authentication/verified_credentials.rb +19 -0
  16. data/lib/doorkeeper/client_authentication.rb +102 -0
  17. data/lib/doorkeeper/config/option.rb +1 -1
  18. data/lib/doorkeeper/config/validations.rb +162 -1
  19. data/lib/doorkeeper/config.rb +161 -9
  20. data/lib/doorkeeper/document_cache.rb +81 -0
  21. data/lib/doorkeeper/errors.rb +49 -0
  22. data/lib/doorkeeper/http_fetcher.rb +232 -0
  23. data/lib/doorkeeper/models/access_grant_mixin.rb +16 -0
  24. data/lib/doorkeeper/models/access_token_mixin.rb +81 -5
  25. data/lib/doorkeeper/models/application_mixin.rb +14 -6
  26. data/lib/doorkeeper/models/concerns/secret_storable.rb +10 -1
  27. data/lib/doorkeeper/oauth/authorization/code.rb +10 -0
  28. data/lib/doorkeeper/oauth/authorization/token.rb +13 -2
  29. data/lib/doorkeeper/oauth/authorization/uri_builder.rb +11 -0
  30. data/lib/doorkeeper/oauth/authorization_code_request.rb +127 -3
  31. data/lib/doorkeeper/oauth/base_request.rb +1 -2
  32. data/lib/doorkeeper/oauth/client.rb +24 -0
  33. data/lib/doorkeeper/oauth/client_authentication/client_secret_basic.rb +57 -0
  34. data/lib/doorkeeper/oauth/client_authentication/client_secret_post.rb +33 -0
  35. data/lib/doorkeeper/oauth/client_authentication/none.rb +71 -0
  36. data/lib/doorkeeper/oauth/client_authentication/private_key_jwt/key_resolver.rb +104 -0
  37. data/lib/doorkeeper/oauth/client_authentication/private_key_jwt/replay_guard.rb +78 -0
  38. data/lib/doorkeeper/oauth/client_authentication/private_key_jwt.rb +247 -0
  39. data/lib/doorkeeper/oauth/client_credentials/creator.rb +25 -9
  40. data/lib/doorkeeper/oauth/client_credentials_request.rb +36 -5
  41. data/lib/doorkeeper/oauth/code_response.rb +16 -2
  42. data/lib/doorkeeper/oauth/error_response.rb +11 -2
  43. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +35 -23
  44. data/lib/doorkeeper/oauth/metadata_response.rb +165 -0
  45. data/lib/doorkeeper/oauth/password_access_token_request.rb +27 -1
  46. data/lib/doorkeeper/oauth/pre_authorization.rb +63 -8
  47. data/lib/doorkeeper/oauth/refresh_token_request.rb +51 -1
  48. data/lib/doorkeeper/oauth/resource_indicator_validator.rb +70 -0
  49. data/lib/doorkeeper/oauth/scopes.rb +25 -0
  50. data/lib/doorkeeper/oauth/token_introspection.rb +33 -7
  51. data/lib/doorkeeper/orm/active_record/mixins/application.rb +2 -2
  52. data/lib/doorkeeper/rails/routes/mapping.rb +1 -0
  53. data/lib/doorkeeper/rails/routes.rb +6 -0
  54. data/lib/doorkeeper/request.rb +59 -0
  55. data/lib/doorkeeper/server.rb +5 -2
  56. data/lib/doorkeeper/version.rb +4 -4
  57. data/lib/doorkeeper.rb +12 -4
  58. data/lib/generators/doorkeeper/grant_reuse_revocation_generator.rb +43 -0
  59. data/lib/generators/doorkeeper/resource_indicators_generator.rb +33 -0
  60. data/lib/generators/doorkeeper/templates/add_access_token_to_access_grants.rb.erb +10 -0
  61. data/lib/generators/doorkeeper/templates/enable_resource_indicators_migration.rb.erb +8 -0
  62. data/lib/generators/doorkeeper/templates/initializer.rb +131 -6
  63. data/lib/generators/doorkeeper/templates/migration.rb.erb +13 -0
  64. metadata +52 -4
  65. data/lib/doorkeeper/oauth/client/credentials.rb +0 -71
  66. data/lib/doorkeeper/oauth/stateless_token.rb +0 -139
@@ -8,9 +8,13 @@ module Doorkeeper
8
8
  class TokenIntrospection
9
9
  attr_reader :token, :error, :invalid_request_reason
10
10
 
11
- def initialize(server, token)
11
+ # +token_type+ tells which credential of the token record the caller
12
+ # actually received (:access_token or :refresh_token), so the "active"
13
+ # state and the response describe the presented token per RFC 7662 §2.2.
14
+ def initialize(server, token, token_type: :access_token)
12
15
  @server = server
13
16
  @token = token
17
+ @token_type = token_type
14
18
  end
15
19
 
16
20
  def authorized?
@@ -106,12 +110,25 @@ module Doorkeeper
106
110
  active: true,
107
111
  scope: @token.scopes_string,
108
112
  client_id: @token.try(:application).try(:uid),
109
- token_type: @token.token_type,
110
113
  iat: @token.created_at.to_i,
111
114
  }
112
- # `exp` is OPTIONAL per RFC 7662 §2.2; omit it for non-expiring tokens
113
- # so clients don't interpret `0` as "expired at 1970-01-01".
114
- response[:exp] = @token.expires_at.to_i if @token.expires_at
115
+
116
+ # RFC 8707: include audience restriction when resource indicators are present
117
+ if @token.try(:resource).present?
118
+ aud = @token.resource.split
119
+ response[:aud] = aud.length == 1 ? aud.first : aud
120
+ end
121
+
122
+ # `token_type` (RFC 6749 §7.1) and `exp` describe the access token:
123
+ # a refresh token is not a Bearer credential and has no expiry of its
124
+ # own, so both are omitted (they are OPTIONAL per RFC 7662 §2.2) when
125
+ # a refresh token is presented.
126
+ unless refresh_token_presented?
127
+ response[:token_type] = @token.token_type
128
+ # `exp` is OPTIONAL per RFC 7662 §2.2; omit it for non-expiring tokens
129
+ # so clients don't interpret `0` as "expired at 1970-01-01".
130
+ response[:exp] = @token.expires_at.to_i if @token.expires_at
131
+ end
115
132
 
116
133
  customize_response(response)
117
134
  end
@@ -176,9 +193,18 @@ module Doorkeeper
176
193
  end
177
194
  end
178
195
 
179
- # Token can be valid only if it is not expired or revoked.
196
+ # The presented token can be valid only if it is not revoked; an access
197
+ # token must additionally be unexpired. A refresh token has no expiry of
198
+ # its own and stays usable at the token endpoint after its paired access
199
+ # token expires, so that expiry is not consulted here.
180
200
  def valid_token?
181
- @token&.accessible?
201
+ return false if @token.blank?
202
+
203
+ refresh_token_presented? ? !@token.revoked? : @token.accessible?
204
+ end
205
+
206
+ def refresh_token_presented?
207
+ @token_type == :refresh_token
182
208
  end
183
209
 
184
210
  def valid_authorized_token?
@@ -116,9 +116,9 @@ module Doorkeeper::Orm::ActiveRecord::Mixins
116
116
 
117
117
  return generator if generator.respond_to?(:generate)
118
118
 
119
- raise Errors::UnableToGenerateToken, "#{generator} does not respond to `.generate`."
119
+ raise Doorkeeper::Errors::UnableToGenerateToken, "#{generator} does not respond to `.generate`."
120
120
  rescue NameError
121
- raise Errors::TokenGeneratorNotFound, "#{generator_name} not found"
121
+ raise Doorkeeper::Errors::TokenGeneratorNotFound, "#{generator_name} not found"
122
122
  end
123
123
 
124
124
  def generate_uid
@@ -11,6 +11,7 @@ module Doorkeeper
11
11
  authorizations: "doorkeeper/authorizations",
12
12
  applications: "doorkeeper/applications",
13
13
  authorized_applications: "doorkeeper/authorized_applications",
14
+ metadata: "doorkeeper/metadata",
14
15
  tokens: "doorkeeper/tokens",
15
16
  token_info: "doorkeeper/token_info",
16
17
  }
@@ -41,10 +41,16 @@ module Doorkeeper
41
41
  map_route(:authorized_applications, :authorized_applications_routes)
42
42
  map_route(:token_info, :token_info_routes)
43
43
  end
44
+
45
+ map_route(:metadata, :metadata_routes)
44
46
  end
45
47
 
46
48
  private
47
49
 
50
+ def metadata_routes(mapping)
51
+ routes.get ".well-known/oauth-authorization-server", controller: mapping[:controllers], action: :show
52
+ end
53
+
48
54
  def authorization_routes(mapping)
49
55
  routes.resource(
50
56
  :authorization,
@@ -3,6 +3,28 @@
3
3
  module Doorkeeper
4
4
  module Request
5
5
  class << self
6
+ # Detect the OAuth client authentication method (RFC 6749 §2.3) that the
7
+ # given request uses. Returns the matching method's strategy (not the
8
+ # registry's Method wrapper), or FallbackMethod when none matches
9
+ # (which authenticates to no credentials).
10
+ #
11
+ # Raises Errors::MultipleClientAuthMethods when the request itself
12
+ # uses more than one client authentication method, since RFC 6749 §2.3
13
+ # forbids that (see +validate_client_authentication!+).
14
+ def client_authentication_method(request)
15
+ validate_client_authentication!(request)
16
+
17
+ authentication_method = client_authentication_methods.detect do |method|
18
+ method.matches_request?(request)
19
+ end
20
+
21
+ if authentication_method
22
+ authentication_method.strategy
23
+ else
24
+ Doorkeeper::ClientAuthentication::FallbackMethod
25
+ end
26
+ end
27
+
6
28
  def authorization_strategy(response_type)
7
29
  grant_flow = authorization_flows.detect do |flow|
8
30
  flow.matches_response_type?(response_type)
@@ -40,6 +62,43 @@ module Doorkeeper
40
62
 
41
63
  private
42
64
 
65
+ # RFC 6749 §2.3 forbids clients to "use more than one authentication
66
+ # method in each request", so the request payload is validated against
67
+ # every *registered* method — regardless of which ones are configured —
68
+ # before any method is selected: a client sending, say, both Basic
69
+ # credentials and body credentials is rejected even when only one of
70
+ # those methods is enabled on the server.
71
+ #
72
+ # Only real authentication mechanisms count towards the limit:
73
+ #
74
+ # * +:none+ is the absence of client authentication (a public client
75
+ # identifying itself with a bare +client_id+), not a mechanism of its
76
+ # own — RFC 7521 §4.2, for example, explicitly allows a +client_id+
77
+ # next to a client assertion.
78
+ # * Deprecated +client_credentials+ callable extractors are
79
+ # configuration adapters rather than registered methods, so they
80
+ # cannot count either; they keep the historical "first extractor that
81
+ # returns a uid wins" selection (see
82
+ # ClientAuthentication::LegacyCallable) for the deprecation window.
83
+ def validate_client_authentication!(request)
84
+ matched = 0
85
+
86
+ Doorkeeper::ClientAuthentication.registered_methods.each_value do |method|
87
+ next if method.name == :none
88
+ next unless method.matches_request?(request)
89
+
90
+ matched += 1
91
+
92
+ # RFC 6749 §2.3 only forbids using more than one method, so bail out
93
+ # on the second match instead of evaluating the remaining methods.
94
+ raise Errors::MultipleClientAuthMethods if matched > 1
95
+ end
96
+ end
97
+
98
+ def client_authentication_methods
99
+ Doorkeeper.configuration.client_authentication_methods
100
+ end
101
+
43
102
  def authorization_flows
44
103
  Doorkeeper.configuration.authorization_response_flows
45
104
  end
@@ -8,6 +8,10 @@ module Doorkeeper
8
8
  @context = context
9
9
  end
10
10
 
11
+ def client_authentication_method_for_request
12
+ Request.client_authentication_method(context.request)
13
+ end
14
+
11
15
  def authorization_request(strategy)
12
16
  klass = Request.authorization_strategy(strategy)
13
17
  klass.new(self)
@@ -37,8 +41,7 @@ module Doorkeeper
37
41
  end
38
42
 
39
43
  def credentials
40
- methods = Doorkeeper.config.client_credentials_methods
41
- @credentials ||= OAuth::Client::Credentials.from_request(context.request, *methods)
44
+ @credentials ||= client_authentication_method_for_request.authenticate(context.request)
42
45
  end
43
46
  end
44
47
  end
@@ -3,10 +3,10 @@
3
3
  module Doorkeeper
4
4
  module VERSION
5
5
  # Semantic versioning
6
- MAJOR = 5
7
- MINOR = 9
8
- TINY = 5
9
- PRE = nil
6
+ MAJOR = 6
7
+ MINOR = 0
8
+ TINY = 0
9
+ PRE = "beta2"
10
10
 
11
11
  # Full version number
12
12
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
data/lib/doorkeeper.rb CHANGED
@@ -7,7 +7,10 @@ require "doorkeeper/engine"
7
7
  #
8
8
  module Doorkeeper
9
9
  autoload :Errors, "doorkeeper/errors"
10
+ autoload :ClientAuthentication, "doorkeeper/client_authentication"
11
+ autoload :DocumentCache, "doorkeeper/document_cache"
10
12
  autoload :GrantFlow, "doorkeeper/grant_flow"
13
+ autoload :HttpFetcher, "doorkeeper/http_fetcher"
11
14
  autoload :OAuth, "doorkeeper/oauth"
12
15
  autoload :Rake, "doorkeeper/rake"
13
16
  autoload :Request, "doorkeeper/request"
@@ -52,16 +55,25 @@ module Doorkeeper
52
55
  autoload :InvalidTokenResponse, "doorkeeper/oauth/invalid_token_response"
53
56
  autoload :InvalidRequestResponse, "doorkeeper/oauth/invalid_request_response"
54
57
  autoload :ForbiddenTokenResponse, "doorkeeper/oauth/forbidden_token_response"
58
+ autoload :MetadataResponse, "doorkeeper/oauth/metadata_response"
55
59
  autoload :NonStandard, "doorkeeper/oauth/nonstandard"
56
60
  autoload :PasswordAccessTokenRequest, "doorkeeper/oauth/password_access_token_request"
57
61
  autoload :PreAuthorization, "doorkeeper/oauth/pre_authorization"
58
62
  autoload :RefreshTokenRequest, "doorkeeper/oauth/refresh_token_request"
63
+ autoload :ResourceIndicatorValidator, "doorkeeper/oauth/resource_indicator_validator"
59
64
  autoload :Scopes, "doorkeeper/oauth/scopes"
60
65
  autoload :Token, "doorkeeper/oauth/token"
61
66
  autoload :TokenIntrospection, "doorkeeper/oauth/token_introspection"
62
67
  autoload :TokenRequest, "doorkeeper/oauth/token_request"
63
68
  autoload :TokenResponse, "doorkeeper/oauth/token_response"
64
69
 
70
+ module ClientAuthentication
71
+ autoload :None, "doorkeeper/oauth/client_authentication/none"
72
+ autoload :ClientSecretBasic, "doorkeeper/oauth/client_authentication/client_secret_basic"
73
+ autoload :ClientSecretPost, "doorkeeper/oauth/client_authentication/client_secret_post"
74
+ autoload :PrivateKeyJwt, "doorkeeper/oauth/client_authentication/private_key_jwt"
75
+ end
76
+
65
77
  module Authorization
66
78
  autoload :Code, "doorkeeper/oauth/authorization/code"
67
79
  autoload :Context, "doorkeeper/oauth/authorization/context"
@@ -69,10 +81,6 @@ module Doorkeeper
69
81
  autoload :URIBuilder, "doorkeeper/oauth/authorization/uri_builder"
70
82
  end
71
83
 
72
- class Client
73
- autoload :Credentials, "doorkeeper/oauth/client/credentials"
74
- end
75
-
76
84
  module ClientCredentials
77
85
  autoload :Validator, "doorkeeper/oauth/client_credentials/validator"
78
86
  autoload :Creator, "doorkeeper/oauth/client_credentials/creator"
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module Doorkeeper
7
+ # Generates migration to add the access token reference column to the
8
+ # access grants table, enabling revocation of previously issued tokens
9
+ # when an authorization code is reused (RFC 6749 §4.1.2, §10.5).
10
+ #
11
+ class GrantReuseRevocationGenerator < ::Rails::Generators::Base
12
+ include ::Rails::Generators::Migration
13
+ source_root File.expand_path("templates", __dir__)
14
+ desc "Support revoking issued tokens on authorization code reuse"
15
+
16
+ def self.next_migration_number(path)
17
+ ActiveRecord::Generators::Base.next_migration_number(path)
18
+ end
19
+
20
+ def grant_reuse_revocation
21
+ return unless no_access_token_id_column?
22
+
23
+ migration_template(
24
+ "add_access_token_to_access_grants.rb.erb",
25
+ "db/migrate/add_access_token_to_access_grants.rb",
26
+ migration_version: migration_version,
27
+ )
28
+ end
29
+
30
+ private
31
+
32
+ def migration_version
33
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
34
+ end
35
+
36
+ def no_access_token_id_column?
37
+ !ActiveRecord::Base.connection.column_exists?(
38
+ :oauth_access_grants,
39
+ :access_token_id,
40
+ )
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module Doorkeeper
7
+ # Generates migration with the `resource` column for access grants and
8
+ # access tokens, required for RFC 8707 Resource Indicators support.
9
+ #
10
+ class ResourceIndicatorsGenerator < ::Rails::Generators::Base
11
+ include ::Rails::Generators::Migration
12
+ source_root File.expand_path("templates", __dir__)
13
+ desc "Add resource indicators support (RFC 8707) to Doorkeeper tables."
14
+
15
+ def resource_indicators
16
+ migration_template(
17
+ "enable_resource_indicators_migration.rb.erb",
18
+ "db/migrate/enable_resource_indicators.rb",
19
+ migration_version: migration_version,
20
+ )
21
+ end
22
+
23
+ def self.next_migration_number(dirname)
24
+ ActiveRecord::Generators::Base.next_migration_number(dirname)
25
+ end
26
+
27
+ private
28
+
29
+ def migration_version
30
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddAccessTokenToAccessGrants < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ # Indexed: when a code is replayed, the revocation checks whether the
6
+ # issued token is still referenced by another grant, which filters
7
+ # oauth_access_grants by access_token_id.
8
+ add_reference :oauth_access_grants, :access_token, index: true
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EnableResourceIndicators < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ add_column :oauth_access_grants, :resource, :text, null: true
6
+ add_column :oauth_access_tokens, :resource, :text, null: true
7
+ end
8
+ end
@@ -150,6 +150,15 @@ Doorkeeper.configure do
150
150
  # token found for the application, resources owner and/or set of scopes.
151
151
  # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383
152
152
  #
153
+ # Matching considers only the application, resource owner, scopes, custom access token
154
+ # attributes (see +custom_access_token_attributes+) and whether a refresh token is
155
+ # expected — not the authorization request that produced the token. Separate authorization
156
+ # grants for the same combination share a single access token, so concurrent sessions of
157
+ # the same client become interdependent (e.g. refreshing the token in one session revokes
158
+ # it for the others). If you need independent tokens per session or device, keep this
159
+ # option disabled or differentiate the sessions with +custom_access_token_attributes+.
160
+ # See https://github.com/doorkeeper-gem/doorkeeper/issues/1693
161
+ #
153
162
  # You can not enable this option together with +hash_token_secrets+.
154
163
  #
155
164
  # reuse_access_token
@@ -188,8 +197,8 @@ Doorkeeper.configure do
188
197
  #
189
198
  # revoke_previous_authorization_code_token
190
199
 
191
- # Require non-confidential clients to use PKCE when using an authorization code
192
- # to obtain an access_token (disabled by default)
200
+ # Require all clients (including confidential ones) to use PKCE when using an
201
+ # authorization code to obtain an access_token (disabled by default)
193
202
  #
194
203
  # force_pkce
195
204
 
@@ -277,13 +286,52 @@ Doorkeeper.configure do
277
286
  #
278
287
  # enforce_configured_scopes
279
288
 
280
- # Change the way client credentials are retrieved from the request object.
281
- # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
282
- # falls back to the `:client_id` and `:client_secret` params from the `params` object.
289
+ # Configure the OAuth client authentication methods (RFC 6749 §2.3) Doorkeeper
290
+ # will accept and the order in which they are tried. By default it accepts
291
+ # HTTP Basic auth (`client_secret_basic`), credentials in the request body
292
+ # (`client_secret_post`), and public clients with no secret (`none`).
283
293
  # Check out https://github.com/doorkeeper-gem/doorkeeper/wiki/Changing-how-clients-are-authenticated
284
294
  # for more information on customization
285
295
  #
286
- # client_credentials :from_basic, :from_params
296
+ # client_authentication %i[client_secret_basic client_secret_post none]
297
+ #
298
+ # The legacy `client_credentials` option is deprecated; `:from_basic` and
299
+ # `:from_params` are automatically mapped to `:client_secret_basic` and
300
+ # `:client_secret_post`.
301
+ #
302
+ # A `private_key_jwt` method (RFC 7523 / OIDC Core §9) is also registered
303
+ # but not enabled by default — add it to the list above to accept it. It
304
+ # requires the `jwt` gem (>= 2.7) in your bundle, and verifies assertions
305
+ # against the client's published public keys: `jwks` / `jwks_uri`
306
+ # attributes you define on your Application model (Doorkeeper does not add
307
+ # these columns itself). Assertions must carry iss = sub =
308
+ # client_id, an aud of your `issuer` (or the token endpoint URL), a bounded
309
+ # exp (at most 1 hour ahead), a kid header, and a single-use jti.
310
+ #
311
+ # jti replay is tracked in process-local memory by default (bounded at
312
+ # 10 000 entries, each held at most 1 hour), so an assertion replayed to a
313
+ # different server process is not caught. To share the tracking across
314
+ # processes, supply your own store (e.g. backed by Redis):
315
+ #
316
+ # private_key_jwt_replay_guard MyRedisReplayGuard.new
317
+ #
318
+ # JWK Sets fetched from a `jwks_uri` are cached in process-local memory
319
+ # for 60 seconds; to change the TTL or share the cache across processes:
320
+ #
321
+ # private_key_jwt_jwks_cache Doorkeeper::DocumentCache.new(ttl: 300)
322
+ #
323
+ # The accepted audiences are built from your `issuer` or from Rails'
324
+ # `default_url_options`; set at least one of them, otherwise Doorkeeper has
325
+ # nothing but the request's Host header to identify itself with and the
326
+ # audience check cannot tell your server apart from another one.
327
+ #
328
+ # A client's `jwks_uri` is fetched with a hardened HTTP client (HTTPS
329
+ # only, no redirects, hosts resolving to RFC 6890 special-use addresses
330
+ # refused), so a jwks_uri on a private network or on localhost is refused
331
+ # even though you configured it yourself; inline `jwks` has no such
332
+ # restriction.
333
+ #
334
+ # client_authentication %i[client_secret_basic client_secret_post none private_key_jwt]
287
335
 
288
336
  # Change the way access token is authenticated from the request object.
289
337
  # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
@@ -319,6 +367,9 @@ Doorkeeper.configure do
319
367
  # types, but you **need** to manually drop `NOT NULL` constraint from `redirect_uri`
320
368
  # column for `oauth_applications` database table.
321
369
  #
370
+ # You almost certainly do not want to change this. There are very, very few cases
371
+ # where you want to change this configuration value.
372
+ #
322
373
  # You can completely disable this feature with:
323
374
  #
324
375
  # allow_blank_redirect_uri false
@@ -377,6 +428,9 @@ Doorkeeper.configure do
377
428
  # If not specified, Doorkeeper enables authorization_code and
378
429
  # client_credentials.
379
430
  #
431
+ # The Refresh Token Grant Flow ("refresh_token") doesn't need to be listed
432
+ # here: it is enabled automatically when +use_refresh_token+ is configured.
433
+ #
380
434
  # implicit and password grant flows have risks that you should understand
381
435
  # before enabling:
382
436
  # https://datatracker.ietf.org/doc/html/rfc6819#section-4.4.2
@@ -541,4 +595,75 @@ Doorkeeper.configure do
541
595
  # WWW-Authenticate Realm (default: "Doorkeeper").
542
596
  #
543
597
  # realm "Doorkeeper"
598
+
599
+ # OAuth 2.0 Authorization Server Metadata (RFC 8414).
600
+ #
601
+ # Doorkeeper exposes an authorization server metadata document at
602
+ # `/.well-known/oauth-authorization-server`, built from the configuration
603
+ # above. The two options below let you customize that document.
604
+ #
605
+ # `issuer` is the authorization server's issuer identifier. When left as nil
606
+ # (the default) the request base URL is used for the metadata `issuer` field
607
+ # only. Note the asymmetry: RFC 9207 below is gated on an explicitly
608
+ # configured issuer, so leaving it nil still advertises a metadata `issuer`
609
+ # (the base URL) while `authorization_response_iss_parameter_supported` stays
610
+ # false and no `iss` parameter is emitted.
611
+ #
612
+ # Configuring an issuer also enables RFC 9207 (Authorization Server Issuer
613
+ # Identification): the `iss` parameter is added to the authorization
614
+ # responses redirected back to the client - both successful responses and
615
+ # error responses such as access_denied - and advertised via the
616
+ # `authorization_response_iss_parameter_supported` metadata field. Clients
617
+ # that parse the authorization redirect will start seeing this parameter.
618
+ # Per RFC 8414 and RFC 9207 the value should be an https URL with no query or
619
+ # fragment; a non-compliant value logs a warning at boot but is still used.
620
+ # Prefer a host-only issuer: Doorkeeper serves its metadata only at the root
621
+ # /.well-known/oauth-authorization-server, so a path-bearing issuer (e.g.
622
+ # https://auth.example.com/tenant) is not discoverable by RFC 8414 clients and
623
+ # also logs a warning.
624
+ #
625
+ # issuer "https://auth.example.com"
626
+ #
627
+ # `custom_metadata` is a Hash that is merged into the metadata response. Use
628
+ # it to advertise additional or non-default metadata fields, for example a
629
+ # `userinfo_endpoint` (which Doorkeeper itself leaves null) when pairing with
630
+ # an OpenID Connect extension.
631
+ #
632
+ # The merge happens last, so it can also override computed fields - including
633
+ # `authorization_response_iss_parameter_supported`, which Doorkeeper derives
634
+ # from whether `issuer` is set. Overriding a computed field is allowed but is
635
+ # your responsibility to keep consistent with the server's actual behaviour
636
+ # (e.g. don't advertise iss support as true if no `issuer` is configured).
637
+ #
638
+ # custom_metadata(
639
+ # userinfo_endpoint: "https://auth.example.com/oauth/userinfo",
640
+ # )
641
+
642
+ # Resource Indicators for OAuth 2.0 (RFC 8707)
643
+ #
644
+ # When configured with a callable, enables RFC 8707 support. The callable
645
+ # receives an array of resource indicator URIs and the OAuth client, and must
646
+ # return true if the resources are acceptable, or false to reject with
647
+ # `invalid_target`.
648
+ #
649
+ # Clients may then include one or more `resource` parameters in authorization
650
+ # and token requests to signal which protected resource(s) they intend to
651
+ # access. The authorization server will:
652
+ # - Validate resource URIs (must be absolute, no fragment)
653
+ # - Store resource indicators on grants and tokens
654
+ # - Enforce subset restrictions on token/refresh requests
655
+ # - Include `aud` in token introspection responses
656
+ #
657
+ # NOTE: RFC 8707 specifies repeated query parameters (?resource=…&resource=…)
658
+ # for multiple values, but Rack collapses repeated keys to the last value.
659
+ # Clients must use the Rails bracket syntax (resource[]=…&resource[]=…) to
660
+ # send multiple resource indicators. A single resource=… works as-is.
661
+ #
662
+ # To use this feature, first run `rails generate doorkeeper:resource_indicators`
663
+ # to add the required `resource` column to the access grants and tokens tables.
664
+ #
665
+ # resource_indicator_validator ->(resource_indicators, client) {
666
+ # allowed = %w[https://api.example.com/ https://calendar.example.com/]
667
+ # resource_indicators.all? { |r| allowed.include?(r) }
668
+ # }
544
669
  end
@@ -28,6 +28,19 @@ class CreateDoorkeeperTables < ActiveRecord::Migration<%= migration_version %>
28
28
  t.string :scopes, null: false, default: ''
29
29
  t.datetime :created_at, null: false
30
30
  t.datetime :revoked_at
31
+
32
+ # Authorization codes are single-use (RFC 6749 §4.1.2). Doorkeeper
33
+ # records the access token issued when the code is exchanged, so that
34
+ # a second exchange attempt revokes the previously issued tokens as
35
+ # recommended by RFC 6749 §10.5.
36
+ #
37
+ # Comment out this line if you don't want a reused authorization code
38
+ # to revoke the tokens already issued for it.
39
+ #
40
+ # Indexed: when a code is replayed, the revocation checks whether the
41
+ # issued token is still referenced by another grant, which filters
42
+ # this table by access_token_id.
43
+ t.references :access_token, index: true
31
44
  end
32
45
 
33
46
  add_index :oauth_access_grants, :token, unique: true