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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +70 -3
- data/README.md +204 -0
- data/app/controllers/doorkeeper/applications_controller.rb +38 -9
- data/app/controllers/doorkeeper/authorizations_controller.rb +17 -3
- data/app/controllers/doorkeeper/metadata_controller.rb +20 -0
- data/app/controllers/doorkeeper/tokens_controller.rb +15 -2
- data/app/views/doorkeeper/authorizations/new.html.erb +24 -0
- data/config/locales/en.yml +2 -0
- data/lib/doorkeeper/client_authentication/credentials.rb +11 -0
- data/lib/doorkeeper/client_authentication/fallback_method.rb +19 -0
- data/lib/doorkeeper/client_authentication/legacy_callable.rb +52 -0
- data/lib/doorkeeper/client_authentication/method.rb +40 -0
- data/lib/doorkeeper/client_authentication/registry.rb +47 -0
- data/lib/doorkeeper/client_authentication/verified_credentials.rb +19 -0
- data/lib/doorkeeper/client_authentication.rb +102 -0
- data/lib/doorkeeper/config/option.rb +1 -1
- data/lib/doorkeeper/config/validations.rb +162 -1
- data/lib/doorkeeper/config.rb +161 -9
- data/lib/doorkeeper/document_cache.rb +81 -0
- data/lib/doorkeeper/errors.rb +49 -0
- data/lib/doorkeeper/http_fetcher.rb +232 -0
- data/lib/doorkeeper/models/access_grant_mixin.rb +16 -0
- data/lib/doorkeeper/models/access_token_mixin.rb +81 -5
- data/lib/doorkeeper/models/application_mixin.rb +14 -6
- data/lib/doorkeeper/models/concerns/secret_storable.rb +10 -1
- data/lib/doorkeeper/oauth/authorization/code.rb +10 -0
- data/lib/doorkeeper/oauth/authorization/token.rb +13 -2
- data/lib/doorkeeper/oauth/authorization/uri_builder.rb +11 -0
- data/lib/doorkeeper/oauth/authorization_code_request.rb +127 -3
- data/lib/doorkeeper/oauth/base_request.rb +1 -2
- data/lib/doorkeeper/oauth/client.rb +24 -0
- data/lib/doorkeeper/oauth/client_authentication/client_secret_basic.rb +57 -0
- data/lib/doorkeeper/oauth/client_authentication/client_secret_post.rb +33 -0
- data/lib/doorkeeper/oauth/client_authentication/none.rb +71 -0
- data/lib/doorkeeper/oauth/client_authentication/private_key_jwt/key_resolver.rb +104 -0
- data/lib/doorkeeper/oauth/client_authentication/private_key_jwt/replay_guard.rb +78 -0
- data/lib/doorkeeper/oauth/client_authentication/private_key_jwt.rb +247 -0
- data/lib/doorkeeper/oauth/client_credentials/creator.rb +25 -9
- data/lib/doorkeeper/oauth/client_credentials_request.rb +36 -5
- data/lib/doorkeeper/oauth/code_response.rb +16 -2
- data/lib/doorkeeper/oauth/error_response.rb +11 -2
- data/lib/doorkeeper/oauth/helpers/uri_checker.rb +35 -23
- data/lib/doorkeeper/oauth/metadata_response.rb +165 -0
- data/lib/doorkeeper/oauth/password_access_token_request.rb +27 -1
- data/lib/doorkeeper/oauth/pre_authorization.rb +63 -8
- data/lib/doorkeeper/oauth/refresh_token_request.rb +51 -1
- data/lib/doorkeeper/oauth/resource_indicator_validator.rb +70 -0
- data/lib/doorkeeper/oauth/scopes.rb +25 -0
- data/lib/doorkeeper/oauth/token_introspection.rb +33 -7
- data/lib/doorkeeper/orm/active_record/mixins/application.rb +2 -2
- data/lib/doorkeeper/rails/routes/mapping.rb +1 -0
- data/lib/doorkeeper/rails/routes.rb +6 -0
- data/lib/doorkeeper/request.rb +59 -0
- data/lib/doorkeeper/server.rb +5 -2
- data/lib/doorkeeper/version.rb +4 -4
- data/lib/doorkeeper.rb +12 -4
- data/lib/generators/doorkeeper/grant_reuse_revocation_generator.rb +43 -0
- data/lib/generators/doorkeeper/resource_indicators_generator.rb +33 -0
- data/lib/generators/doorkeeper/templates/add_access_token_to_access_grants.rb.erb +10 -0
- data/lib/generators/doorkeeper/templates/enable_resource_indicators_migration.rb.erb +8 -0
- data/lib/generators/doorkeeper/templates/initializer.rb +131 -6
- data/lib/generators/doorkeeper/templates/migration.rb.erb +13 -0
- metadata +52 -4
- data/lib/doorkeeper/oauth/client/credentials.rb +0 -71
- 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
|
-
|
|
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
|
-
|
|
113
|
-
#
|
|
114
|
-
|
|
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
|
-
#
|
|
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
|
|
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,
|
data/lib/doorkeeper/request.rb
CHANGED
|
@@ -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
|
data/lib/doorkeeper/server.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
data/lib/doorkeeper/version.rb
CHANGED
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
|
|
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
|
-
#
|
|
281
|
-
#
|
|
282
|
-
#
|
|
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
|
-
#
|
|
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
|