jwt_sessions 2.2.0 → 2.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +58 -11
- data/lib/jwt_sessions/session.rb +9 -1
- data/lib/jwt_sessions/version.rb +1 -1
- data/test/units/jwt_sessions/test_session.rb +8 -7
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b04e83f8dac3d2546364665988101a1a1fd6193
|
4
|
+
data.tar.gz: 9a84c2705ec41817a6ed79a5bdface8f5da48dba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc621aa40e3c9f4b5807655ee4198f8881cdb00814d32fa159fbf815f1061c47190456316606b67e4c4743b13a2c74a7fb1b1bea99219f8b4207beb693f263a2
|
7
|
+
data.tar.gz: c1bfcd3b9198dbdc641aa71dffaa8d0f9a1866dcb3ed800e690b967466a6f0d509daece7a3f58f45104d763aa4a7aafad0cb136608f91659f4d13db2fa64fc9a
|
data/README.md
CHANGED
@@ -34,13 +34,11 @@ Main goal of this gem is to provide configurable, manageable, and safe stateful
|
|
34
34
|
|
35
35
|
It's designed to be framework agnostic yet is easily integrable so Rails integration is also available out of the box.
|
36
36
|
|
37
|
-
Core concept behind jwt_sessions is that each session is represented by a pair of tokens: access and refresh,
|
38
|
-
|
39
|
-
|
37
|
+
Core concept behind `jwt_sessions` is that each session is represented by a pair of tokens: access and refresh, and a session store is used to handle CSRF checks and refresh token hijacking. Both tokens have configurable expiration times, but in general refresh token is supposed to have a longer lifespan than an access token. Access token is used to retrieve secured resources and refresh token is used to renew the access token once it's expired. Default token store is based on redis.
|
38
|
+
|
39
|
+
All tokens are encoded and decoded by [ruby-jwt](https://github.com/jwt/ruby-jwt) gem, and its reserved claim names are supported as well as it's allowed to configure claim checks and cryptographic signing algorithms supported by it.
|
40
|
+
`jwt_sessions` itself uses `ext` claim and `HS256` signing by default.
|
40
41
|
|
41
|
-
All tokens are encoded and decoded by [ruby-jwt](https://github.com/jwt/ruby-jwt) gem, and its reserved claim names are supported
|
42
|
-
as well as it's allowed to configure claim checks and cryptographic signing algorithms supported by it.
|
43
|
-
jwt_sessions itself uses `ext` claim and `HS256` signing by default.
|
44
42
|
|
45
43
|
## Installation
|
46
44
|
|
@@ -58,7 +56,7 @@ bundle install
|
|
58
56
|
|
59
57
|
## Getting Started
|
60
58
|
|
61
|
-
`Authorization` mixin is supposed to be included in your controllers and is used to retrieve access and refresh tokens from incoming requests and verify CSRF token if needed.
|
59
|
+
`Authorization` mixin is supposed to be included in your controllers and is used to retrieve access and refresh tokens from incoming requests and verify CSRF token if needed. It assumes that a token is either in a cookie or in a header (cookie and header names are configurable). It tries to retrieve it from headers first, then from cookies (CSRF check included) if the headers check failed.
|
62
60
|
|
63
61
|
### Rails integration
|
64
62
|
|
@@ -139,7 +137,7 @@ session = JWTSessions::Session.new(payload: payload, refresh_payload: refresh_pa
|
|
139
137
|
```
|
140
138
|
|
141
139
|
Now you can build a refresh endpoint. To protect the endpoint use before_action `authorize_refresh_request!`. \
|
142
|
-
|
140
|
+
The endpoint itself should return a renewed access token.
|
143
141
|
|
144
142
|
```ruby
|
145
143
|
class RefreshController < ApplicationController
|
@@ -156,7 +154,7 @@ class RefreshController < ApplicationController
|
|
156
154
|
end
|
157
155
|
end
|
158
156
|
```
|
159
|
-
|
157
|
+
In the example `found_token` - is a token fetched from request headers or cookies. In the context of `RefreshController` it's a refresh token. \
|
160
158
|
The refresh request with headers must include `X-Refresh-Token` (header name is configurable) with refresh token.
|
161
159
|
|
162
160
|
```
|
@@ -275,7 +273,7 @@ class SimpleApp < Sinatra::Base
|
|
275
273
|
payload.to_json
|
276
274
|
end
|
277
275
|
|
278
|
-
|
276
|
+
# ...
|
279
277
|
end
|
280
278
|
```
|
281
279
|
|
@@ -466,7 +464,10 @@ Flush a session by its access token.
|
|
466
464
|
```ruby
|
467
465
|
session = JWTSessions::Session.new(refresh_by_access_allowed: true)
|
468
466
|
tokens = session.login
|
469
|
-
session.
|
467
|
+
session.flush_by_access_payload
|
468
|
+
# or
|
469
|
+
session = JWTSessions::Session.new(refresh_by_access_allowed: true, payload: payload)
|
470
|
+
session.flush_by_access_payload
|
470
471
|
```
|
471
472
|
|
472
473
|
Or by refresh token UID
|
@@ -514,6 +515,52 @@ To logout with an access token `refresh_by_access_allowed` setting should be set
|
|
514
515
|
[Rails API](test/support/dummy_api) \
|
515
516
|
[Sinatra API](test/support/dummy_sinatra_api)
|
516
517
|
|
518
|
+
You can use a mixed approach for the cases when you'd like to store an access token in localStorage and refresh token in HTTP-only secure cookies. \
|
519
|
+
Rails controllers setup example:
|
520
|
+
|
521
|
+
```ruby
|
522
|
+
class LoginController < ApplicationController
|
523
|
+
def create
|
524
|
+
user = User.find_by(email: params[:email])
|
525
|
+
if user&.authenticate(params[:password])
|
526
|
+
|
527
|
+
payload = { user_id: user.id, role: user.role, permissions: user.permissions }
|
528
|
+
refresh_payload = { user_id: user.id }
|
529
|
+
session = JWTSessions::Session.new(payload: payload, refresh_payload: refresh_payload)
|
530
|
+
tokens = session.login
|
531
|
+
response.set_cookie(JWTSessions.refresh_cookie,
|
532
|
+
value: tokens[:refresh],
|
533
|
+
httponly: true,
|
534
|
+
secure: Rails.env.production?)
|
535
|
+
|
536
|
+
render json: { access: tokens[:access], csrf: tokens[:csrf] }
|
537
|
+
else
|
538
|
+
render json: 'Cannot login', status: :unauthorized
|
539
|
+
end
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
class RefreshController < ApplicationController
|
544
|
+
before_action :authorize_refresh_request!
|
545
|
+
|
546
|
+
def create
|
547
|
+
tokens = JWTSessions::Session.new(payload: access_payload).refresh(found_token)
|
548
|
+
render json: { access: tokens[:access], csrf: tokens[:csrf] }
|
549
|
+
end
|
550
|
+
|
551
|
+
def access_payload
|
552
|
+
user = User.find_by!(email: payload['user_id'])
|
553
|
+
{ user_id: user.id, role: user.role, permissions: user.permissions }
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
class ResourcesController < ApplicationController
|
558
|
+
before_action :authorize_access_request!
|
559
|
+
before_action :validate_role_and_permissions_from_payload
|
560
|
+
|
561
|
+
# ...
|
562
|
+
end
|
563
|
+
```
|
517
564
|
|
518
565
|
## Contributing
|
519
566
|
|
data/lib/jwt_sessions/session.rb
CHANGED
@@ -187,6 +187,14 @@ module JWTSessions
|
|
187
187
|
}
|
188
188
|
end
|
189
189
|
|
190
|
+
def refresh_tokens_hash
|
191
|
+
{
|
192
|
+
csrf: csrf_token,
|
193
|
+
access: access_token,
|
194
|
+
access_expires_at: Time.at(@_access.expiration.to_i)
|
195
|
+
}
|
196
|
+
end
|
197
|
+
|
190
198
|
def check_refresh_on_time
|
191
199
|
expiration = @_refresh.access_expiration
|
192
200
|
yield @_refresh.uid, expiration if expiration.to_i > Time.now.to_i
|
@@ -197,7 +205,7 @@ module JWTSessions
|
|
197
205
|
create_access_token
|
198
206
|
update_refresh_token
|
199
207
|
|
200
|
-
|
208
|
+
refresh_tokens_hash
|
201
209
|
end
|
202
210
|
|
203
211
|
def update_refresh_token
|
data/lib/jwt_sessions/version.rb
CHANGED
@@ -5,7 +5,8 @@ require 'jwt_sessions'
|
|
5
5
|
|
6
6
|
class TestSession < Minitest::Test
|
7
7
|
attr_reader :session, :payload, :tokens
|
8
|
-
|
8
|
+
LOGIN_KEYS = %i[access access_expires_at csrf refresh refresh_expires_at].freeze
|
9
|
+
REFRESH_KEYS = %i[access access_expires_at csrf].freeze
|
9
10
|
|
10
11
|
def setup
|
11
12
|
JWTSessions.encryption_key = 'encrypted'
|
@@ -22,14 +23,14 @@ class TestSession < Minitest::Test
|
|
22
23
|
|
23
24
|
def test_login
|
24
25
|
decoded_access = JWTSessions::Token.decode(tokens[:access]).first
|
25
|
-
assert_equal
|
26
|
+
assert_equal LOGIN_KEYS, tokens.keys.sort
|
26
27
|
assert_equal payload[:test], decoded_access['test']
|
27
28
|
end
|
28
29
|
|
29
30
|
def test_refresh
|
30
31
|
refreshed_tokens = session.refresh(tokens[:refresh])
|
31
32
|
decoded_access = JWTSessions::Token.decode(refreshed_tokens[:access]).first
|
32
|
-
assert_equal
|
33
|
+
assert_equal REFRESH_KEYS, refreshed_tokens.keys.sort
|
33
34
|
assert_equal payload[:test], decoded_access['test']
|
34
35
|
end
|
35
36
|
|
@@ -41,7 +42,7 @@ class TestSession < Minitest::Test
|
|
41
42
|
refreshed_tokens = session.refresh_by_access_payload
|
42
43
|
access2 = session.instance_variable_get('@_access')
|
43
44
|
decoded_access = JWTSessions::Token.decode(refreshed_tokens[:access]).first
|
44
|
-
assert_equal
|
45
|
+
assert_equal REFRESH_KEYS, refreshed_tokens.keys.sort
|
45
46
|
assert_equal payload[:test], decoded_access['test']
|
46
47
|
assert_equal session.instance_variable_get('@_refresh').uid, decoded_access['ruid']
|
47
48
|
assert_equal access2.expiration > access1.expiration, true
|
@@ -54,7 +55,7 @@ class TestSession < Minitest::Test
|
|
54
55
|
refreshed_tokens = session.refresh_by_access_payload
|
55
56
|
decoded_access = JWTSessions::Token.decode!(refreshed_tokens[:access]).first
|
56
57
|
JWTSessions.access_exp_time = 3600
|
57
|
-
assert_equal
|
58
|
+
assert_equal REFRESH_KEYS, refreshed_tokens.keys.sort
|
58
59
|
assert_equal payload[:test], decoded_access['test']
|
59
60
|
assert_equal session.instance_variable_get('@_refresh').uid, decoded_access['ruid']
|
60
61
|
end
|
@@ -68,7 +69,7 @@ class TestSession < Minitest::Test
|
|
68
69
|
end
|
69
70
|
decoded_access = JWTSessions::Token.decode!(refreshed_tokens[:access]).first
|
70
71
|
JWTSessions.access_exp_time = 3600
|
71
|
-
assert_equal
|
72
|
+
assert_equal REFRESH_KEYS, refreshed_tokens.keys.sort
|
72
73
|
assert_equal payload[:test], decoded_access['test']
|
73
74
|
assert_equal session.instance_variable_get('@_refresh').uid, decoded_access['ruid']
|
74
75
|
end
|
@@ -100,7 +101,7 @@ class TestSession < Minitest::Test
|
|
100
101
|
raise JWTSessions::Errors::Unauthorized
|
101
102
|
end
|
102
103
|
decoded_access = JWTSessions::Token.decode(refreshed_tokens[:access]).first
|
103
|
-
assert_equal
|
104
|
+
assert_equal REFRESH_KEYS, refreshed_tokens.keys.sort
|
104
105
|
assert_equal payload[:test], decoded_access['test']
|
105
106
|
end
|
106
107
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jwt_sessions
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.2.
|
4
|
+
version: 2.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yulia Oletskaya
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jwt
|