warden-jwt_auth 0.1.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 +7 -0
- data/.codeclimate.yml +17 -0
- data/.gitignore +10 -0
- data/.overcommit.yml +53 -0
- data/.overcommit_gems.rb +15 -0
- data/.reek +0 -0
- data/.rspec +2 -0
- data/.rubocop.yml +10 -0
- data/.travis.yml +21 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Dockerfile +8 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +187 -0
- data/Rakefile +8 -0
- data/bin/console +16 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +7 -0
- data/lib/warden/jwt_auth.rb +101 -0
- data/lib/warden/jwt_auth/errors.rb +17 -0
- data/lib/warden/jwt_auth/header_parser.rb +48 -0
- data/lib/warden/jwt_auth/hooks.rb +55 -0
- data/lib/warden/jwt_auth/interfaces.rb +57 -0
- data/lib/warden/jwt_auth/middleware.rb +27 -0
- data/lib/warden/jwt_auth/middleware/revocation_manager.rb +46 -0
- data/lib/warden/jwt_auth/middleware/token_dispatcher.rb +35 -0
- data/lib/warden/jwt_auth/payload_user_helper.rb +39 -0
- data/lib/warden/jwt_auth/strategy.rb +37 -0
- data/lib/warden/jwt_auth/token_decoder.rb +22 -0
- data/lib/warden/jwt_auth/token_encoder.rb +34 -0
- data/lib/warden/jwt_auth/token_revoker.rb +20 -0
- data/lib/warden/jwt_auth/user_decoder.rb +45 -0
- data/lib/warden/jwt_auth/user_encoder.rb +29 -0
- data/lib/warden/jwt_auth/version.rb +7 -0
- data/warden-jwt_auth.gemspec +35 -0
- metadata +233 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'warden'
|
4
|
+
|
5
|
+
module Warden
|
6
|
+
module JWTAuth
|
7
|
+
# Warden strategy to authenticate an user through a JWT token in the
|
8
|
+
# `Authorization` request header
|
9
|
+
# :reek:PrimmaDonnaMethod
|
10
|
+
class Strategy < Warden::Strategies::Base
|
11
|
+
attr_reader :token
|
12
|
+
|
13
|
+
def valid?
|
14
|
+
!token.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
def store?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def authenticate!
|
22
|
+
user = UserDecoder.new.call(token, scope)
|
23
|
+
success!(user)
|
24
|
+
rescue JWT::DecodeError
|
25
|
+
fail!
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def token
|
31
|
+
@token ||= HeaderParser.from_env(env)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Warden::Strategies.add(:jwt, Warden::JWTAuth::Strategy)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warden
|
4
|
+
module JWTAuth
|
5
|
+
# Decodes a JWT into a hash payload into a JWT token
|
6
|
+
class TokenDecoder
|
7
|
+
include JWTAuth::Import['secret']
|
8
|
+
|
9
|
+
# Decodes the payload from a JWT as a hash
|
10
|
+
#
|
11
|
+
# @param token [String] a JWT
|
12
|
+
# @return [Hash] payload decoded from the JWT
|
13
|
+
def call(token)
|
14
|
+
JWT.decode(token,
|
15
|
+
secret,
|
16
|
+
true,
|
17
|
+
algorithm: TokenEncoder::ALG,
|
18
|
+
verify_jti: true)[0]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warden
|
4
|
+
module JWTAuth
|
5
|
+
# Encodes a payload into a JWT token, adding some configurable
|
6
|
+
# claims
|
7
|
+
class TokenEncoder
|
8
|
+
include JWTAuth::Import['secret', 'expiration_time']
|
9
|
+
|
10
|
+
# Algorithm used to encode
|
11
|
+
ALG = 'HS256'
|
12
|
+
|
13
|
+
# Encodes a payload into a JWT
|
14
|
+
#
|
15
|
+
# @param payload [Hash] what has to be encoded
|
16
|
+
# @return [String] JWT
|
17
|
+
def call(payload)
|
18
|
+
payload_to_encode = merge_with_default_claims(payload)
|
19
|
+
JWT.encode(payload_to_encode, secret, ALG)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
#:reek:FeatureEnvy
|
25
|
+
def merge_with_default_claims(payload)
|
26
|
+
now = Time.now.to_i
|
27
|
+
payload['iat'] ||= now
|
28
|
+
payload['exp'] ||= now + expiration_time
|
29
|
+
payload['jti'] ||= SecureRandom.uuid
|
30
|
+
payload
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warden
|
4
|
+
module JWTAuth
|
5
|
+
# Revokes a JWT using configured revocation strategy
|
6
|
+
class TokenRevoker
|
7
|
+
include JWTAuth::Import['revocation_strategies']
|
8
|
+
|
9
|
+
# Revokes the JWT token
|
10
|
+
#
|
11
|
+
# @param token [String] a JWT
|
12
|
+
def call(token)
|
13
|
+
payload = TokenDecoder.new.call(token)
|
14
|
+
scope = payload['scp'].to_sym
|
15
|
+
user = PayloadUserHelper.find_user(payload)
|
16
|
+
revocation_strategies[scope].revoke_jwt(payload, user)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'warden/jwt_auth/errors'
|
4
|
+
|
5
|
+
module Warden
|
6
|
+
module JWTAuth
|
7
|
+
# Layer above token decoding which directly decodes a user from a JWT
|
8
|
+
class UserDecoder
|
9
|
+
include JWTAuth::Import['revocation_strategies']
|
10
|
+
|
11
|
+
attr_reader :helper
|
12
|
+
|
13
|
+
def initialize(*args)
|
14
|
+
super
|
15
|
+
@helper = PayloadUserHelper
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the user that is encoded in a JWT. The scope is used to choose
|
19
|
+
# the user repository to which send `#find_for_jwt_authentication(sub)`
|
20
|
+
# with decoded `sub` claim.
|
21
|
+
#
|
22
|
+
# @param token [String] a JWT
|
23
|
+
# @param scope [Symbol] Warden scope
|
24
|
+
# @return [Interfaces::User] an user, whatever it is
|
25
|
+
# @raise [Errors::RevokedToken] when token has been revoked for the
|
26
|
+
# encoded user
|
27
|
+
# @raise [Errors::WrongScope] when encoded scope does not match with scope
|
28
|
+
# argument
|
29
|
+
def call(token, scope)
|
30
|
+
payload = TokenDecoder.new.call(token)
|
31
|
+
raise Errors::WrongScope unless helper.scope_matches?(payload, scope)
|
32
|
+
user = helper.find_user(payload)
|
33
|
+
raise Errors::RevokedToken if revoked?(payload, user, scope)
|
34
|
+
user
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def revoked?(payload, user, scope)
|
40
|
+
strategy = revocation_strategies[scope]
|
41
|
+
strategy.jwt_revoked?(payload, user)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'warden/jwt_auth/errors'
|
4
|
+
|
5
|
+
module Warden
|
6
|
+
module JWTAuth
|
7
|
+
# Layer above token encoding which directly encodes a user to a JWT
|
8
|
+
class UserEncoder
|
9
|
+
attr_reader :helper
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@helper = PayloadUserHelper
|
13
|
+
end
|
14
|
+
|
15
|
+
# Encodes a user for given scope into a JWT. Payload generated includes a
|
16
|
+
# `sub` claim which is build calling `jwt_subject` in `user`, and a custom
|
17
|
+
# `scp` claim which value is `scope` as a string. The result of
|
18
|
+
# calling `jwt_payload` in user is also merged into the payload.
|
19
|
+
#
|
20
|
+
# @param user [Interfaces::User] an user, whatever it is
|
21
|
+
# @param scope [Symbol] Warden scope
|
22
|
+
# @return [String] encoded JWT
|
23
|
+
def call(user, scope)
|
24
|
+
payload = helper.payload_for_user(user, scope)
|
25
|
+
TokenEncoder.new.call(payload)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'warden/jwt_auth/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "warden-jwt_auth"
|
8
|
+
spec.version = Warden::JWTAuth::VERSION
|
9
|
+
spec.authors = ["Marc Busqué"]
|
10
|
+
spec.email = ["marc@lamarciana.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{JWT authentication for Warden.}
|
13
|
+
spec.description = %q{JWT authentication for Warden, ORM agnostic and accepting the implementation of token revocation strategies.}
|
14
|
+
spec.homepage = "https://github.com/waiting-for-dev/warden-jwt_auth"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency 'dry-configurable', '~> 0.5'
|
23
|
+
spec.add_dependency 'dry-auto_inject', '~> 0.4'
|
24
|
+
spec.add_dependency 'jwt', '~> 1.5'
|
25
|
+
spec.add_dependency 'warden', '~> 1.2'
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
28
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
29
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
30
|
+
spec.add_development_dependency "rack-test", "~> 0.6"
|
31
|
+
spec.add_development_dependency "pry-byebug", "~> 3.4"
|
32
|
+
# Test reporting
|
33
|
+
spec.add_development_dependency 'simplecov', '~> 0.13'
|
34
|
+
spec.add_development_dependency 'codeclimate-test-reporter', '~> 1.0'
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: warden-jwt_auth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Marc Busqué
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-configurable
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dry-auto_inject
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.4'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: jwt
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.5'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: warden
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.2'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.12'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.12'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '10.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '10.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rack-test
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.6'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.6'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: pry-byebug
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '3.4'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.4'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: simplecov
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.13'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0.13'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: codeclimate-test-reporter
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '1.0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '1.0'
|
167
|
+
description: JWT authentication for Warden, ORM agnostic and accepting the implementation
|
168
|
+
of token revocation strategies.
|
169
|
+
email:
|
170
|
+
- marc@lamarciana.com
|
171
|
+
executables: []
|
172
|
+
extensions: []
|
173
|
+
extra_rdoc_files: []
|
174
|
+
files:
|
175
|
+
- ".codeclimate.yml"
|
176
|
+
- ".gitignore"
|
177
|
+
- ".overcommit.yml"
|
178
|
+
- ".overcommit_gems.rb"
|
179
|
+
- ".reek"
|
180
|
+
- ".rspec"
|
181
|
+
- ".rubocop.yml"
|
182
|
+
- ".travis.yml"
|
183
|
+
- CODE_OF_CONDUCT.md
|
184
|
+
- Dockerfile
|
185
|
+
- Gemfile
|
186
|
+
- LICENSE.txt
|
187
|
+
- README.md
|
188
|
+
- Rakefile
|
189
|
+
- bin/console
|
190
|
+
- bin/setup
|
191
|
+
- docker-compose.yml
|
192
|
+
- lib/warden/jwt_auth.rb
|
193
|
+
- lib/warden/jwt_auth/errors.rb
|
194
|
+
- lib/warden/jwt_auth/header_parser.rb
|
195
|
+
- lib/warden/jwt_auth/hooks.rb
|
196
|
+
- lib/warden/jwt_auth/interfaces.rb
|
197
|
+
- lib/warden/jwt_auth/middleware.rb
|
198
|
+
- lib/warden/jwt_auth/middleware/revocation_manager.rb
|
199
|
+
- lib/warden/jwt_auth/middleware/token_dispatcher.rb
|
200
|
+
- lib/warden/jwt_auth/payload_user_helper.rb
|
201
|
+
- lib/warden/jwt_auth/strategy.rb
|
202
|
+
- lib/warden/jwt_auth/token_decoder.rb
|
203
|
+
- lib/warden/jwt_auth/token_encoder.rb
|
204
|
+
- lib/warden/jwt_auth/token_revoker.rb
|
205
|
+
- lib/warden/jwt_auth/user_decoder.rb
|
206
|
+
- lib/warden/jwt_auth/user_encoder.rb
|
207
|
+
- lib/warden/jwt_auth/version.rb
|
208
|
+
- warden-jwt_auth.gemspec
|
209
|
+
homepage: https://github.com/waiting-for-dev/warden-jwt_auth
|
210
|
+
licenses:
|
211
|
+
- MIT
|
212
|
+
metadata: {}
|
213
|
+
post_install_message:
|
214
|
+
rdoc_options: []
|
215
|
+
require_paths:
|
216
|
+
- lib
|
217
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
218
|
+
requirements:
|
219
|
+
- - ">="
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: '0'
|
222
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
223
|
+
requirements:
|
224
|
+
- - ">="
|
225
|
+
- !ruby/object:Gem::Version
|
226
|
+
version: '0'
|
227
|
+
requirements: []
|
228
|
+
rubyforge_project:
|
229
|
+
rubygems_version: 2.5.1
|
230
|
+
signing_key:
|
231
|
+
specification_version: 4
|
232
|
+
summary: JWT authentication for Warden.
|
233
|
+
test_files: []
|