devise-jwt 0.4.4 → 0.5.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 +5 -0
- data/README.md +95 -3
- data/devise-jwt.gemspec +1 -1
- data/lib/devise/jwt.rb +4 -0
- data/lib/devise/jwt/revocation_strategies.rb +1 -0
- data/lib/devise/jwt/revocation_strategies/whitelist.rb +51 -0
- data/lib/devise/jwt/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d9d15287c66562066c4c4a729506396e7ec4007
|
|
4
|
+
data.tar.gz: 1043788671b10218a3bc7c778f10a2f2c258ee84
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1fb0e64bff0707c6a476868f5b1ed9fe967472e0fa385dac3e7c74b8f54433a98d57b1e16b057e59d566721ab91444aed1ec5ece13e309867c43da5bfe5554c4
|
|
7
|
+
data.tar.gz: d2cb740987f7c7321a16a38def56fb7e31e90cc4b17f3cdb37ae65f38ac308b64619d52e4ac929f5fbab0ccb18e264837805a98342a9160710df806413b5e9f9
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.5.0] - 2017-12-11
|
|
8
|
+
### Added
|
|
9
|
+
- Added whitelist strategy
|
|
10
|
+
- Update `warden-jwt_auth` dependency
|
|
11
|
+
|
|
7
12
|
## [0.4.4] - 2017-12-04
|
|
8
13
|
### Fixed
|
|
9
14
|
- Configure classes as strings to avoid problems with Rails STI
|
data/README.md
CHANGED
|
@@ -26,7 +26,7 @@ You can read about which security concerns this library takes into account and a
|
|
|
26
26
|
Add this line to your application's Gemfile:
|
|
27
27
|
|
|
28
28
|
```ruby
|
|
29
|
-
gem 'devise-jwt', '~> 0.
|
|
29
|
+
gem 'devise-jwt', '~> 0.5.0'
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
And then execute:
|
|
@@ -86,6 +86,14 @@ def jwt_payload
|
|
|
86
86
|
end
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
You can add a hook method `on_jwt_dispatch` on the user model. It is executed when a token dispatched for that user instance, and it takes `token` and `payload` as parameters.
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
def on_jwt_dispatch(token, payload)
|
|
93
|
+
do_something(token, payload)
|
|
94
|
+
end
|
|
95
|
+
```
|
|
96
|
+
|
|
89
97
|
Note: if you are making cross-domain requests, make sure that you add `Authorization` header to the list of allowed request headers and exposed response headers. You can use something like [rack-cors](https://github.com/cyu/rack-cors) for that, for example:
|
|
90
98
|
|
|
91
99
|
```ruby
|
|
@@ -103,7 +111,7 @@ end
|
|
|
103
111
|
|
|
104
112
|
### Revocation strategies
|
|
105
113
|
|
|
106
|
-
`devise-jwt` comes with
|
|
114
|
+
`devise-jwt` comes with three revocation strategies out of the box. Some of them are implementations of what is discussed in the blog post [JWT Revocation Strategies](http://waiting-for-dev.github.io/blog/2017/01/24/jwt_revocation_strategies/), where I also talk about their pros and cons.
|
|
107
115
|
|
|
108
116
|
#### JTIMatcher
|
|
109
117
|
|
|
@@ -152,7 +160,7 @@ end
|
|
|
152
160
|
|
|
153
161
|
#### Blacklist
|
|
154
162
|
|
|
155
|
-
In this strategy, a database table is used as a blacklist of revoked JWT tokens. The `jti` claim, which uniquely identifies a token, is persisted.
|
|
163
|
+
In this strategy, a database table is used as a blacklist of revoked JWT tokens. The `jti` claim, which uniquely identifies a token, is persisted. The `exp` claim is also stored to allow the clean-up of staled tokens.
|
|
156
164
|
|
|
157
165
|
In order to use it, you need to create the blacklist table in a migration:
|
|
158
166
|
|
|
@@ -197,6 +205,74 @@ class User < ApplicationRecord
|
|
|
197
205
|
end
|
|
198
206
|
```
|
|
199
207
|
|
|
208
|
+
#### Whitelist
|
|
209
|
+
|
|
210
|
+
Here, the model itself acts also as a revocation strategy, but it needs to have
|
|
211
|
+
a one-to-many association with another table which stores the tokens (in fact
|
|
212
|
+
their `jti` claim, which uniquely identifies them) valids for each user record.
|
|
213
|
+
|
|
214
|
+
The workflow is as the following:
|
|
215
|
+
|
|
216
|
+
- Once a token is dispatched for a user, its `jti` claim is stored in the
|
|
217
|
+
associated table.
|
|
218
|
+
- At every authentication, the incoming token `jti` is matched against all the
|
|
219
|
+
`jti` associated to that user. The authentication only succeeds if one of
|
|
220
|
+
them matches.
|
|
221
|
+
- On a sign out, the token `jti` is deleted from the associated table.
|
|
222
|
+
|
|
223
|
+
In fact, besides the `jti` claim, the `aud` claim is also stored and matched at
|
|
224
|
+
every authentication. This, together with the [aud_header](#aud_header)
|
|
225
|
+
configuration parameter, can be used to differentiate between clients or
|
|
226
|
+
devices for the same user.
|
|
227
|
+
|
|
228
|
+
The `exp` claim is also stored to allow the clean-up of staled tokens.
|
|
229
|
+
|
|
230
|
+
In order to use it, you have to create yourself the associated table and model.
|
|
231
|
+
The association table must be called `whitelisted_jwts`:
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
def change
|
|
235
|
+
create_table :whitelisted_jwts do |t|
|
|
236
|
+
t.string :jti, null: false
|
|
237
|
+
t.string :aud
|
|
238
|
+
# If you want to leverage the `aud` claim, add to it a `NOT NULL` constraint:
|
|
239
|
+
# t.string :aud, null: false
|
|
240
|
+
t.datetime :exp, null: false
|
|
241
|
+
t.references :your_user_table, foreign_key: true
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
add_index :whitelisted_jwts, :jti, unique: true
|
|
245
|
+
end
|
|
246
|
+
```
|
|
247
|
+
Important: You are encouraged to set a unique index in the jti column. This way we can be sure at the database level that there aren't two valid tokens with same jti at the same time.
|
|
248
|
+
|
|
249
|
+
And then, the model:
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
class WhitelistedJwt < ApplicationRecord
|
|
253
|
+
end
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Finally, include de strategy in the model and configure it:
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
class User < ApplicationRecord
|
|
260
|
+
include Devise::JWT::RevocationStrategies::Whitelist
|
|
261
|
+
|
|
262
|
+
devise :database_authenticatable,
|
|
263
|
+
:jwt_authenticatable, jwt_revocation_strategy: self
|
|
264
|
+
end
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Be aware that this strategy makes uses of `on_jwt_dispatch` method in the user model, so if you need to use it don't forget to call `super`:
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
def on_jwt_dispatch(token, payload)
|
|
271
|
+
super
|
|
272
|
+
do_something(token, payload)
|
|
273
|
+
end
|
|
274
|
+
```
|
|
275
|
+
|
|
200
276
|
#### Null strategy
|
|
201
277
|
|
|
202
278
|
A [null object pattern](https://en.wikipedia.org/wiki/Null_Object_pattern) strategy, which does not revoke tokens, is provided out of the box just in case you are absolutely sure you don't need token revocation. It is recommended **not to use it**.
|
|
@@ -308,6 +384,22 @@ jwt.request_formats = {
|
|
|
308
384
|
|
|
309
385
|
By default, only requests without format are processed.
|
|
310
386
|
|
|
387
|
+
#### aud_header
|
|
388
|
+
|
|
389
|
+
Request header which content will be stored to the `aud` claim in the payload.
|
|
390
|
+
|
|
391
|
+
It is used to validate whether an incoming token was originally issued to the
|
|
392
|
+
same client, checking if `aud` and the `aud_header` header value match. If you
|
|
393
|
+
don't want to differentiate between clients, you don't need to provide that
|
|
394
|
+
header.
|
|
395
|
+
|
|
396
|
+
**Important:** Be aware that this workflow is not bullet proof. In some
|
|
397
|
+
scenarios a user can handcraft the request headers, therefore being able to
|
|
398
|
+
impersonate any client. In such cases you could need something more robust,
|
|
399
|
+
like an OAuth workflow with client id and client secret.
|
|
400
|
+
|
|
401
|
+
Defaults to `JWT_AUD`.
|
|
402
|
+
|
|
311
403
|
## Development
|
|
312
404
|
|
|
313
405
|
There are docker and docker-compose files configured to create a development environment for this gem. So, if you use Docker you only need to run:
|
data/devise-jwt.gemspec
CHANGED
|
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
|
|
|
22
22
|
spec.require_paths = ["lib"]
|
|
23
23
|
|
|
24
24
|
spec.add_dependency 'devise', '~> 4.0'
|
|
25
|
-
spec.add_dependency 'warden-jwt_auth', '~> 0.
|
|
25
|
+
spec.add_dependency 'warden-jwt_auth', '~> 0.3.0'
|
|
26
26
|
|
|
27
27
|
spec.add_development_dependency "bundler", "~> 1.12"
|
|
28
28
|
spec.add_development_dependency "rake", "~> 10.0"
|
data/lib/devise/jwt.rb
CHANGED
|
@@ -42,6 +42,10 @@ module Devise
|
|
|
42
42
|
forward_to_warden(:revocation_requests, value)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
setting(:aud_header) do |value|
|
|
46
|
+
forward_to_warden(:aud_header, value)
|
|
47
|
+
end
|
|
48
|
+
|
|
45
49
|
# A hash of warden scopes as keys and an array of request formats that will
|
|
46
50
|
# be processed as values. When a scope is not present or if it has a nil
|
|
47
51
|
# item, requests without format will be taken into account.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/concern'
|
|
4
|
+
|
|
5
|
+
module Devise
|
|
6
|
+
module JWT
|
|
7
|
+
module RevocationStrategies
|
|
8
|
+
# This strategy must be included in the user model.
|
|
9
|
+
#
|
|
10
|
+
# The JwtWhitelist table must include `jti`, `aud`, `exp` and `user_id`
|
|
11
|
+
# columns
|
|
12
|
+
#
|
|
13
|
+
# In order to tell whether a token is revoked, it just tries to find the
|
|
14
|
+
# `jti` and `aud` values from the token on the `whitelisted_jwts`
|
|
15
|
+
# table for the respective user.
|
|
16
|
+
#
|
|
17
|
+
# If the values don't exist means the token was revoked.
|
|
18
|
+
# On revocation, it deletes the matching record from the
|
|
19
|
+
# `whitelisted_jwts` table.
|
|
20
|
+
#
|
|
21
|
+
# On sign in, it creates a new record with the `jti` and `aud` values.
|
|
22
|
+
module Whitelist
|
|
23
|
+
extend ActiveSupport::Concern
|
|
24
|
+
|
|
25
|
+
included do
|
|
26
|
+
has_many :whitelisted_jwts, dependent: :destroy
|
|
27
|
+
|
|
28
|
+
# @see Warden::JWTAuth::Interfaces::RevocationStrategy#jwt_revoked?
|
|
29
|
+
def self.jwt_revoked?(payload, user)
|
|
30
|
+
!user.whitelisted_jwts.exists?(payload.slice('jti', 'aud'))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @see Warden::JWTAuth::Interfaces::RevocationStrategy#revoke_jwt
|
|
34
|
+
def self.revoke_jwt(payload, user)
|
|
35
|
+
user.whitelisted_jwts.find_by(payload.slice('jti', 'aud')).destroy!
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Warden::JWTAuth::Interfaces::User#on_jwt_dispatch
|
|
40
|
+
# :reek:FeatureEnvy
|
|
41
|
+
def on_jwt_dispatch(_token, payload)
|
|
42
|
+
whitelisted_jwts.create!(
|
|
43
|
+
jti: payload['jti'],
|
|
44
|
+
aud: payload['aud'],
|
|
45
|
+
exp: Time.at(payload['exp'].to_i)
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/devise/jwt/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: devise-jwt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Marc Busqué
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-12-
|
|
11
|
+
date: 2017-12-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: devise
|
|
@@ -30,14 +30,14 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: 0.
|
|
33
|
+
version: 0.3.0
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: 0.
|
|
40
|
+
version: 0.3.0
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: bundler
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -200,6 +200,7 @@ files:
|
|
|
200
200
|
- lib/devise/jwt/revocation_strategies/blacklist.rb
|
|
201
201
|
- lib/devise/jwt/revocation_strategies/jti_matcher.rb
|
|
202
202
|
- lib/devise/jwt/revocation_strategies/null.rb
|
|
203
|
+
- lib/devise/jwt/revocation_strategies/whitelist.rb
|
|
203
204
|
- lib/devise/jwt/version.rb
|
|
204
205
|
homepage: https://github.com/waiting-for-dev/devise-jwt
|
|
205
206
|
licenses:
|