devise-jwt 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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: