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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9129d2a81114b968dc51a11fa2e04aef75b9324f
4
- data.tar.gz: 26c8980b2a75ba0f225193cc5cffed9b8bbb9c1f
3
+ metadata.gz: 7d9d15287c66562066c4c4a729506396e7ec4007
4
+ data.tar.gz: 1043788671b10218a3bc7c778f10a2f2c258ee84
5
5
  SHA512:
6
- metadata.gz: 9e89f30b0eee4ffce4632572a71b6c66a1075ba8e0a20e24bc64c0eb2227c27f4ca5df2af7a25296cccf19edb320cfa18236d4d365bf2f50a37983d6746d71de
7
- data.tar.gz: e4838a029bc9e0753ffb6cd49bf9cdeb50ea88f0c7a909b43e6e60a8e7ecb87639058199350d04d0fbb876db923cdf0e13d0f27bc4c431ffb081a7e02bbd3c68
6
+ metadata.gz: 1fb0e64bff0707c6a476868f5b1ed9fe967472e0fa385dac3e7c74b8f54433a98d57b1e16b057e59d566721ab91444aed1ec5ece13e309867c43da5bfe5554c4
7
+ data.tar.gz: d2cb740987f7c7321a16a38def56fb7e31e90cc4b17f3cdb37ae65f38ac308b64619d52e4ac929f5fbab0ccb18e264837805a98342a9160710df806413b5e9f9
@@ -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.4.4'
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 two revocation strategies out of the box. They 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.
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:
@@ -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.2.1'
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"
@@ -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.
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'devise/jwt/revocation_strategies/jti_matcher'
4
4
  require 'devise/jwt/revocation_strategies/blacklist'
5
+ require 'devise/jwt/revocation_strategies/whitelist'
5
6
  require 'devise/jwt/revocation_strategies/null'
6
7
 
7
8
  module Devise
@@ -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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Devise
4
4
  module JWT
5
- VERSION = '0.4.4'
5
+ VERSION = '0.5.0'
6
6
  end
7
7
  end
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.4
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-04 00:00:00.000000000 Z
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.2.1
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.2.1
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: