doorkeeper-jwt_assertion 0.0.1 → 0.0.2
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/README.md +16 -8
- data/lib/doorkeeper/jwt_assertion.rb +8 -3
- data/lib/doorkeeper/jwt_assertion/version.rb +1 -1
- data/lib/doorkeeper/oauth/assertion_access_token_request.rb +149 -0
- data/lib/doorkeeper/oauth/describable_error_response.rb +8 -0
- data/lib/doorkeeper/request/assertion.rb +7 -18
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1025f43737a9664dcc50c45898f896b533d3c63
|
4
|
+
data.tar.gz: f3b91579bb961c9d970d09016397bbb5e951faa1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a275595653379460375328fe8c5d43802d15ac096c48c2e07ad848f23f4063f50989cb0e386c235508a2182c8e8a0c139754ea4a25eeb8ae93481368852f893
|
7
|
+
data.tar.gz: 6ce0dbc0869904a8df606fbcc95d68a81c91548cca8b3c62df20c9f18edfdd61c6e8f382e701a1d221ff84918b1afc2ad948bf24c4e145e93ab44e8cc66ae318
|
data/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# Doorkeeper JWT Assertion
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
Extending (Doorkeeper)[https://github.com/doorkeeper-gem/doorkeeper] to support JWT Assertion grant type using a secret or a private key file.
|
3
|
+
Extending [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper) to support JWT Assertion grant type using a secret or a private key file.
|
6
4
|
|
7
5
|
**This library is in alpha. Future incompatible changes may be necessary.**
|
8
6
|
|
@@ -24,13 +22,18 @@ Doorkeeper.configure do
|
|
24
22
|
jwt_private_key Rails.root.join('config', 'keys', 'private.key')
|
25
23
|
|
26
24
|
jwt_secret 'notasecret'
|
25
|
+
|
26
|
+
# Optional
|
27
|
+
jwt_use_issuer_as_client_id true
|
27
28
|
end
|
28
29
|
```
|
29
30
|
|
30
31
|
This will automatically push `assertion` into the Doorkeeper's grant_types configuration attribute.
|
31
32
|
|
32
|
-
|
33
|
-
|
33
|
+
When `jwt_use_issuer_as_client_id` is set to false then the `client_id` MUST be available from the parameters. By default it will extract the 'iss' and use it as the client_id to retrieve the oauth application.
|
34
|
+
|
35
|
+
Use the `resource_owner_authenticator` in the configuration to identify the owner based on the JWT claim values. This values can be accessible from `jwt`.
|
36
|
+
If the client request a token with an invalid assertion, or an expired JWT claim, an :invalid_grant error response will be generated before retrieving the resource_owner.
|
34
37
|
|
35
38
|
``` ruby
|
36
39
|
Doorkeeper.configure do
|
@@ -38,8 +41,7 @@ Doorkeeper.configure do
|
|
38
41
|
resource_owner_authenticator do
|
39
42
|
|
40
43
|
if jwt
|
41
|
-
|
42
|
-
return user
|
44
|
+
jwt['prn'].present? and User.find_by_email(jwt['prn'])
|
43
45
|
end
|
44
46
|
|
45
47
|
end
|
@@ -60,14 +62,20 @@ p12 = OpenSSL::PKCS12.new( Rails.root.join('config', 'keys', 'private.p12').open
|
|
60
62
|
params = { :private_key => p12.key,
|
61
63
|
:aud => 'audience',
|
62
64
|
:prn => 'person', # or :sub => 'subject', not suported on OAuth2 1.0.0 yet.
|
63
|
-
:iss => '
|
65
|
+
:iss => 'client_id',
|
64
66
|
:scope => 'scope',
|
65
67
|
:exp => Time.now.utc.to_i + 5.minutes }
|
66
68
|
|
67
69
|
token = client.assertion.get_token(params)
|
68
70
|
```
|
69
71
|
|
72
|
+
> "[...] refresh tokens are not issued
|
73
|
+
> in response to assertion grant requests and access tokens will be
|
74
|
+
> issued with a reasonably short lifetime."
|
75
|
+
> - [draft-ietf-oauth-assertions-18](https://tools.ietf.org/html/draft-ietf-oauth-assertions-18#section-4.1)
|
76
|
+
|
70
77
|
## TO DO
|
71
78
|
|
72
79
|
* Better error handling
|
80
|
+
* JWT Client Authentication Flow
|
73
81
|
* Testing
|
@@ -7,7 +7,7 @@ require 'jwt'
|
|
7
7
|
module Doorkeeper
|
8
8
|
module JWTAssertion
|
9
9
|
|
10
|
-
attr_reader :jwt
|
10
|
+
attr_reader :jwt, :jwt_header
|
11
11
|
|
12
12
|
end
|
13
13
|
end
|
@@ -22,6 +22,10 @@ module Doorkeeper
|
|
22
22
|
context.instance_variable_set('@jwt', jwt)
|
23
23
|
end
|
24
24
|
|
25
|
+
def jwt_header=(jwt_header)
|
26
|
+
@jwt_header = jwt_header
|
27
|
+
context.instance_variable_set('@jwt_header', jwt_header)
|
28
|
+
end
|
25
29
|
|
26
30
|
end
|
27
31
|
end
|
@@ -30,6 +34,7 @@ module Doorkeeper
|
|
30
34
|
class Config
|
31
35
|
|
32
36
|
option :jwt_key
|
37
|
+
option :jwt_use_issuer_as_client_id, :default => true
|
33
38
|
|
34
39
|
class Builder
|
35
40
|
|
@@ -48,10 +53,10 @@ module Doorkeeper
|
|
48
53
|
|
49
54
|
Config.class_eval do
|
50
55
|
alias_method :remember_calculate_token_grant_types, :calculate_token_grant_types
|
51
|
-
|
52
56
|
define_method :calculate_token_grant_types do
|
53
|
-
remember_calculate_token_grant_types << 'assertion'
|
57
|
+
remember_calculate_token_grant_types << 'assertion' << 'urn:ietf:params:oauth:grant-type:jwt-bearer'
|
54
58
|
end
|
59
|
+
|
55
60
|
end
|
56
61
|
|
57
62
|
jwt_key key
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'doorkeeper/oauth/describable_error_response'
|
2
|
+
|
3
|
+
module Doorkeeper
|
4
|
+
module OAuth
|
5
|
+
class AssertionAccessTokenRequest
|
6
|
+
|
7
|
+
include Validations
|
8
|
+
include OAuth::RequestConcern
|
9
|
+
include OAuth::Helpers
|
10
|
+
|
11
|
+
attr_accessor :server, :original_scopes
|
12
|
+
attr_reader :resource_owner, :client, :configuration, :access_token, :response, :error_description
|
13
|
+
|
14
|
+
validate :assertion, error: :invalid_grant
|
15
|
+
validate :client, error: :invalid_client
|
16
|
+
validate :scopes, error: :invalid_scope
|
17
|
+
validate :resource_owner, error: :invalid_grant
|
18
|
+
validate :access_token, error: :invalid_grant
|
19
|
+
|
20
|
+
|
21
|
+
##
|
22
|
+
# @see https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12#section-2.1
|
23
|
+
#
|
24
|
+
def initialize(server, configuration)
|
25
|
+
@server = server
|
26
|
+
@configuration = configuration
|
27
|
+
@response = nil
|
28
|
+
@original_scopes = server.parameters[:scope]
|
29
|
+
end
|
30
|
+
|
31
|
+
def authorize
|
32
|
+
validate
|
33
|
+
if valid?
|
34
|
+
@response = TokenResponse.new(access_token)
|
35
|
+
else
|
36
|
+
@response = DescribableErrorResponse.from_request(self)
|
37
|
+
@response.description = error_description
|
38
|
+
@response
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
##
|
47
|
+
# > The value of the "grant_type" is "urn:ietf:params:oauth:grant-
|
48
|
+
# > type:jwt-bearer".
|
49
|
+
# > The value of the "assertion" parameter MUST contain a single JWT.
|
50
|
+
# > - draft-ietf-oauth-jwt-bearer-12
|
51
|
+
#
|
52
|
+
# @see https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12#section-2.1
|
53
|
+
#
|
54
|
+
# > assertion_type
|
55
|
+
# > REQUIRED. The format of the assertion as defined by the
|
56
|
+
# > authorization server. The value MUST be an absolute URI.
|
57
|
+
# >
|
58
|
+
# > assertion
|
59
|
+
# > REQUIRED. The assertion.
|
60
|
+
# >
|
61
|
+
# > - draft-ietf-oauth-v2-10
|
62
|
+
# @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.3
|
63
|
+
#
|
64
|
+
# Newer versions of ietf-oauth-v2 don't need assertion_type. So it's still optional.
|
65
|
+
def validate_assertion
|
66
|
+
assertion, assertion_type = server.parameters.values_at(:assertion, :assertion_type)
|
67
|
+
|
68
|
+
if assertion_type and assertion_type != 'urn:ietf:params:oauth:grant-type:jwt-bearer'
|
69
|
+
raise StandardError.new('Assertion type not valid. Expected urn:ietf:params:oauth:grant-type:jwt-bearer')
|
70
|
+
end
|
71
|
+
|
72
|
+
payload, header = JWT.decode(assertion, configuration.jwt_key)
|
73
|
+
server.jwt = payload
|
74
|
+
server.jwt_header = header
|
75
|
+
|
76
|
+
rescue => error
|
77
|
+
@error_description = error.message
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
##
|
84
|
+
# If `jwt_use_issuer_as_client_id` is `true` then validate the client using the issuer as the client_id.
|
85
|
+
# Otherwise, use the client_id directly from the parameters.
|
86
|
+
#
|
87
|
+
# > Authentication of the client is optional, as described in
|
88
|
+
# > Section 3.2.1 of OAuth 2.0 [RFC6749] and consequently, the
|
89
|
+
# > "client_id" is only needed when a form of client authentication that
|
90
|
+
# > relies on the parameter is used.
|
91
|
+
# > - draft-ietf-oauth-assertions-18
|
92
|
+
#
|
93
|
+
# @see https://tools.ietf.org/html/draft-ietf-oauth-assertions-18#section-4.1
|
94
|
+
#
|
95
|
+
# > The JWT MUST contain an "iss" (issuer) claim that contains a
|
96
|
+
# > unique identifier for the entity that issued the JWT.
|
97
|
+
# > - draft-ietf-oauth-jwt-bearer-12
|
98
|
+
#
|
99
|
+
# @see https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12#section-3
|
100
|
+
#
|
101
|
+
def validate_client
|
102
|
+
@client ||= if configuration.jwt_use_issuer_as_client_id
|
103
|
+
OAuth::Client.find(server.jwt['iss']) if server.jwt['iss'].present?
|
104
|
+
|
105
|
+
elsif sever.parameters[:client_id].present?
|
106
|
+
OAuth::Client.find( sever.parameters[:client_id] )
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
##
|
113
|
+
# > The "scope" parameter may be used, as defined in the Assertion
|
114
|
+
# > Framework for OAuth 2.0 Client Authentication and Authorization
|
115
|
+
# > Grants [I-D.ietf-oauth-assertions] specification, to indicate the
|
116
|
+
# > requested scope.
|
117
|
+
#
|
118
|
+
# @see https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12#section-2.1
|
119
|
+
#
|
120
|
+
def validate_scopes
|
121
|
+
return true unless @original_scopes.present?
|
122
|
+
ScopeChecker.valid? @original_scopes, configuration.scopes, client.try(:scopes)
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
def validate_resource_owner
|
127
|
+
resource_owner || (@resource_owner = server.current_resource_owner)
|
128
|
+
end
|
129
|
+
|
130
|
+
def validate_access_token
|
131
|
+
access_token or create_token
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
##
|
136
|
+
# > [...] refresh tokens are not issued
|
137
|
+
# > in response to assertion grant requests and access tokens will be
|
138
|
+
# > issued with a reasonably short lifetime.
|
139
|
+
#
|
140
|
+
# @see https://tools.ietf.org/html/draft-ietf-oauth-assertions-18#section-4.1
|
141
|
+
#
|
142
|
+
def create_token
|
143
|
+
expires_in = Authorization::Token.access_token_expires_in(configuration, client)
|
144
|
+
@access_token = AccessToken.find_or_create_for(client, resource_owner.id, scopes, expires_in, false)
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -1,38 +1,27 @@
|
|
1
|
+
require 'doorkeeper/oauth/assertion_access_token_request'
|
2
|
+
|
1
3
|
module Doorkeeper
|
2
4
|
module Request
|
3
5
|
|
4
6
|
class Assertion
|
5
7
|
def self.build(server)
|
6
|
-
|
7
|
-
begin
|
8
|
-
jwt = JWT.decode(assertion, Doorkeeper.configuration.jwt_key)
|
9
|
-
rescue JWT::ExpiredSignature => e
|
10
|
-
raise Errors::ExpiredSignature
|
11
|
-
end
|
12
|
-
server.jwt = jwt.is_a?(Array) ? jwt.first : jwt
|
13
|
-
|
14
|
-
new(server.credentials, server.current_resource_owner, server)
|
8
|
+
new(server)
|
15
9
|
end
|
16
10
|
|
17
|
-
|
11
|
+
attr_reader :server
|
18
12
|
|
19
|
-
def initialize(
|
20
|
-
@credentials = credentials
|
21
|
-
@resource_owner = resource_owner
|
13
|
+
def initialize(server)
|
22
14
|
@server = server
|
23
15
|
end
|
24
16
|
|
25
17
|
def request
|
26
|
-
@request ||= OAuth::
|
27
|
-
Doorkeeper.configuration,
|
28
|
-
credentials,
|
29
|
-
resource_owner,
|
30
|
-
server.parameters)
|
18
|
+
@request ||= OAuth::AssertionAccessTokenRequest.new(server, Doorkeeper.configuration)
|
31
19
|
end
|
32
20
|
|
33
21
|
def authorize
|
34
22
|
request.authorize
|
35
23
|
end
|
24
|
+
|
36
25
|
end
|
37
26
|
|
38
27
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: doorkeeper-jwt_assertion
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Omac
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-04-
|
11
|
+
date: 2015-04-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: doorkeeper
|
@@ -83,6 +83,8 @@ files:
|
|
83
83
|
- lib/doorkeeper/jwt_assertion.rb
|
84
84
|
- lib/doorkeeper/jwt_assertion/railtie.rb
|
85
85
|
- lib/doorkeeper/jwt_assertion/version.rb
|
86
|
+
- lib/doorkeeper/oauth/assertion_access_token_request.rb
|
87
|
+
- lib/doorkeeper/oauth/describable_error_response.rb
|
86
88
|
- lib/doorkeeper/request/assertion.rb
|
87
89
|
homepage: https://github.com/kioru/doorkeeper-jwt_assertion
|
88
90
|
licenses:
|