jwt_sessions 1.0.0.pre.alpha.1 → 1.0.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: ee5dd37a8cd52828709a23ec230fa5997aa70476
4
- data.tar.gz: 0613763ad3c80126e3a1f6b0af21b9894c32ecd7
3
+ metadata.gz: bfd4418b2bba47f5127afce7c69075372ed995f1
4
+ data.tar.gz: 5d51066a3533865f9c329d203f87819b704d47d8
5
5
  SHA512:
6
- metadata.gz: 174d300ba94e370d20f5ae2ca368e593996896aed4b05f64025d52a0ff95fb7c2f78e00f37504a7901ca8f2cbb93c3c085ab7711ed35f68f94234025186c5925
7
- data.tar.gz: f0d04da2cfec6ec7911875aea2c1dd7f45838b0d5f82b7f6cb91ce9cd33ec7c0e2e5bf15fac24433162cdee01173e33fa5826c1399172748ca5ea9cd60b6b164
6
+ metadata.gz: 39ee0e2618f6e10577e88ed8c574799888c84e73a4d757d800487b30cb901fb6042e7b53a05e57b6b30f7c2f5f46c78c2fd35ec44653b0f6fa99ef5d7fb0a14c
7
+ data.tar.gz: 1a84c6f9d28a1b5571d114b9d6fd2fba37dacc3d0f4eec340e5699728425712ccc271374cd94019a3d54bdc258db579da793e00ca61f7fc66274b24557ebde39
data/README.md CHANGED
@@ -3,4 +3,298 @@
3
3
 
4
4
  XSS/CSRF safe JWT auth designed for SPA
5
5
 
6
- This gem provides framework agnostic sessions based on JSON Web Tokens.
6
+ ## Synopsis
7
+
8
+ Main goal of this gem is to provide configurable, manageable, and safe stateful sessions based on JSON Web Tokens.
9
+
10
+ It's designed to be framework agnostic yet is easily integrable so Rails integration is also available out of the box.
11
+
12
+ Core concept behind jwt_sessions is that each session is represented by a pair of tokens: access and refresh,
13
+ and a session store used to handle CSRF checks and refresh token hijacking. Default token store is based on redis
14
+ but you can freely implement your own store with whichever backend you prefer.
15
+
16
+ All tokens are encoded and decoded by [ruby-jwt](https://github.com/jwt/ruby-jwt) gem, and its reserved claim names are supported
17
+ as well as it's allowed to configure claim checks and cryptographic signing algorithms supported by it.
18
+ jwt_sessions itself uses `ext` claim and `HS256` signing by default.
19
+
20
+ ## Installation
21
+
22
+ Put this line in your Gemfile
23
+
24
+ ```
25
+ gem 'jwt_sessions'
26
+ ```
27
+
28
+ Then run
29
+
30
+ ```
31
+ bundle install
32
+ ```
33
+
34
+ ## Getting Started
35
+
36
+ `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.
37
+
38
+ ### Rails integration
39
+
40
+ Include `JWTSessions::RailsAuthorization` in your controllers, add `JWTSessions::Errors::Unauthorized` exceptions handling if needed.
41
+
42
+ ```
43
+ class ApplicationController < ActionController::API
44
+ include JWTSessions::RailsAuthorization
45
+ rescue_from JWTSessions::Errors::Unauthorized, with: :not_authorized
46
+
47
+ private
48
+
49
+ def not_authorized
50
+ render json: { error: 'Not authorized' }, status: :unauthorized
51
+ end
52
+ end
53
+ ```
54
+
55
+ Specify an encryption key for JSON Web Tokens in `config/initializers/jwt_session.rb` \
56
+ It's adviced to store the key itself within the app secrets.
57
+
58
+ ```
59
+ JWTSessions.encryption_key = Rails.application.secrets.secret_jwt_encryption_key
60
+ ```
61
+
62
+ Generate access/refresh/csrf tokens with a custom payload. \
63
+ The payload will be available in the controllers once the access (or refresh) token is authorized.
64
+
65
+ ```
66
+ > payload = { user_id: user.id }
67
+ => {:user_id=>1}
68
+
69
+ > session = JWTSessions::Session.new(payload: payload)
70
+ => #<JWTSessions::Session:0x00007fbe2cce9ea0...>
71
+
72
+ > session.login
73
+ => {:csrf=>"BmhxDRW5NAEIx...",
74
+ :access=>"eyJhbGciOiJIUzI1NiJ9...",
75
+ :refresh=>"eyJhbGciOiJIUzI1NiJ9..."}
76
+ ```
77
+
78
+ You can build login controller to receive access, refresh and csrf tokens in exchange for user's login/password. \
79
+ Refresh controller - to be able to get a new access token using refresh token after access is expired. \
80
+ Here is example of a simple login controller, which returns set of tokens as a plain JSON response. \
81
+ It's also possible to set tokens as cookies in the response instead.
82
+
83
+ ```
84
+ class LoginController < ApplicationController
85
+ def create
86
+ user = User.find_by!(email: params[:email])
87
+ if user.authenticate(params[:password])
88
+ payload = { user_id: user.id }
89
+ session = JWTSessions::Session.new(payload: payload)
90
+ render json: session.login
91
+ else
92
+ render json: 'Invalid user', status: :unauthorized
93
+ end
94
+ end
95
+ end
96
+ ```
97
+
98
+ Since it's not required to pass an access token when you want to perform a refresh you may need to have some data in the payload of the refresh token to allow you to construct a payload of the new access token during refresh.
99
+
100
+ ```
101
+ session = JWTSessions::Session.new(payload: payload, refresh_payload: refresh_payload)
102
+ ```
103
+
104
+ Now you can build a refresh endpoint. To protect the endpoint use before_action `authorize_refresh_request!`. \
105
+ In the example `found_token` - is a token fetched from request headers or cookies.
106
+
107
+ ```
108
+ class RefreshController < ApplicationController
109
+ before_action :authorize_refresh_request!
110
+
111
+ def create
112
+ session = JWTSessions::Session.new(payload: access_payload)
113
+ render json: session.refresh(found_token)
114
+ end
115
+
116
+ def access_payload
117
+ # payload here stands for refresh token payload
118
+ build_access_payload_based_on_refresh(payload)
119
+ end
120
+ end
121
+ ```
122
+
123
+ The refresh request with headers must include `X-Refresh-Token` (header name is configurable) with refresh token.
124
+
125
+ ```
126
+ X-Refresh-Token: eyJhbGciOiJIUzI1NiJ9...
127
+ POST /refresh
128
+ ```
129
+
130
+ Now when there're login and refresh endpoints, you can protect the rest of your secure controllers with `before_action :authorize_access_request!`.
131
+
132
+ ```
133
+ class UsersController < ApplicationController
134
+ before_action :authorize_access_request!
135
+
136
+ def index
137
+ ...
138
+ end
139
+
140
+ def show
141
+ ...
142
+ end
143
+ end
144
+ ```
145
+ Headers must include `Authorization: Bearer` with access token.
146
+
147
+ ```
148
+ Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
149
+ GET /users
150
+ ```
151
+
152
+ The `payload` method is available to fetch encoded data from the token.
153
+
154
+ ```
155
+ def current_user
156
+ @current_user ||= User.find(payload['user_id'])
157
+ end
158
+ ```
159
+
160
+ ### Non-Rails usage
161
+
162
+ You must include `JWTSessions::Authorization` module to your auth class and implement within it next methods:
163
+
164
+ 1. request_headers
165
+
166
+ ```
167
+ def request_headers
168
+ # must return hash-like object with request headers
169
+ end
170
+ ```
171
+
172
+ 2. request_cookies
173
+
174
+ ```
175
+ def request_cookies
176
+ # must return hash-like object with request cookies
177
+ end
178
+ ```
179
+
180
+ 3. request_method
181
+
182
+ ```
183
+ def request_method
184
+ # must return current request verb as a string in upcase, f.e. 'GET', 'HEAD', 'POST', 'PATCH', etc
185
+ end
186
+ ```
187
+
188
+ Example Sinatra app
189
+
190
+ ```
191
+ require 'sinatra/base'
192
+
193
+ class SimpleApp < Sinatra::Base
194
+ include JWTSessions::Authorization
195
+
196
+ def request_headers
197
+ request.headers
198
+ end
199
+
200
+ def request_cookies
201
+ request.cookies
202
+ end
203
+
204
+ def request_method
205
+ request.request_method
206
+ end
207
+
208
+ post '/refresh' do
209
+ content_type :json
210
+ authorize_refresh_request!
211
+ session = JWTSessions::Session.new(payload: payload)
212
+ session.refresh(found_token).to_json
213
+ end
214
+
215
+ ....
216
+ end
217
+ ```
218
+
219
+ ## Configuration
220
+
221
+ List of configurable settings with their default values.
222
+
223
+ ##### Redis
224
+
225
+ Default token store configurations
226
+
227
+ ```
228
+ JWTSessions.redis_host = '127.0.0.1'
229
+ JWTSessions.redis_port = '6379'
230
+ JWTSessions.redis_db_name = 'jwtokens'
231
+ JWTSessions.token_prefix = 'jwt_' # used for redis db keys
232
+ ```
233
+
234
+ ##### JWT encryption
235
+
236
+ ```
237
+ JWTSessions.algorithm = 'HS256'
238
+ ```
239
+
240
+ You need to specify a secret to use for HMAC, this setting doesn't have a default value.
241
+
242
+ ```
243
+ JWTSessions.secret = 'secret'
244
+ ```
245
+
246
+ Or you need to specify public and private keys for RSA/EDCSA/EDDSA, there are no default values for keys. You can use instructions from [ruby-jwt](https://github.com/jwt/ruby-jwt) to generate keys corresponding keys.
247
+
248
+ ```
249
+ JWTSessions.private_key = 'private_key'
250
+ JWTSessions.public_key = 'public_key_for_private'
251
+ ```
252
+
253
+ ##### Request headers and cookies names
254
+
255
+ Default request headers/cookies names can be re-configured
256
+
257
+ ```
258
+ JWTSessions.access_header = 'Authorization'
259
+ JWTSessions.access_cookie = 'jwt_access'
260
+ JWTSessions.refresh_header = 'X-Refresh-Token'
261
+ JWTSessions.refresh_cookie = 'jwt_refresh'
262
+ JWTSessions.csrf_header = 'X-CSRF-Token'
263
+ ```
264
+
265
+ ##### Expiration time
266
+
267
+ Acces token must have a short life span, while refresh tokens can be stored for a longer time period
268
+
269
+ ```
270
+ JWTSessions.access_exp_time = 3600 # 1 hour in seconds
271
+ JWTSessions.refresh_exp_time = 604800 # 1 week in seconds
272
+ ```
273
+
274
+ #### CSRF and cookies
275
+
276
+ In case when you use cookies as your tokens transport it gets vulnerable to CSRF. That's why both login and refresh methods of the `Session` class produce CSRF tokens for you. `Authorization` mixin expects that this token is sent with all requests except GET and HEAD in a header specified among this gem's settings (X-CSRF-Token by default). Verification will be done automatically and `Authorization` exception will be raised in case of mismatch between the token from the header and the one stored in session. \
277
+ Although you don't need to mitigate BREACH attacks it's still possible to generate a new masked token with the access token
278
+
279
+ ```
280
+ session = JWTSessions::Session.new
281
+ session.masked_csrf(access_token)
282
+ ```
283
+
284
+ #### Refresh token hijack protection
285
+
286
+ There is a security recommendation regarding the usage of refresh tokens: only perform refresh when an access token gets expired. \
287
+ Since sessions are always defined by a pair of tokens and there can't be multiple access tokens for a single refresh token simultaneous usage of the refresh token by multiple users can be easily noticed as refresh will be perfomed before the expiration of the access token by one of the users. Because of that `refresh` method of the `Session` class supports optional block as one of its arguments which will be executed only in case of refresh being performed before the expiration of the access token.
288
+
289
+ ```
290
+ session = JwtSessions::Session.new(payload: payload)
291
+ session.refresh(refresh_token) { |refresh_token_uid, access_token_expiration| ... }
292
+ ```
293
+
294
+ ## Contributing
295
+
296
+ Fork & Pull Request
297
+
298
+ ## License
299
+
300
+ MIT
@@ -8,25 +8,25 @@ module JWTSessions
8
8
  protected
9
9
 
10
10
  TOKEN_TYPES.each do |token_type|
11
- define_method("authenticate_#{token_type}_request!") do
11
+ define_method("authorize_#{token_type}_request!") do
12
12
  begin
13
13
  cookieless_auth(token_type)
14
14
  rescue Errors::Unauthorized
15
15
  cookie_based_auth(token_type)
16
16
  end
17
- invalid_authentication unless Token.valid_payload?(payload)
17
+ invalid_authorization unless Token.valid_payload?(payload)
18
18
  check_csrf(token_type)
19
19
  end
20
20
  end
21
21
 
22
22
  private
23
23
 
24
- def invalid_authentication
24
+ def invalid_authorization
25
25
  raise Errors::Unauthorized
26
26
  end
27
27
 
28
28
  def check_csrf(token_type)
29
- invalid_authentication if should_check_csrf? && @_csrf_check && !valid_csrf_token?(retrieve_csrf, token_type)
29
+ invalid_authorization if should_check_csrf? && @_csrf_check && !valid_csrf_token?(retrieve_csrf, token_type)
30
30
  end
31
31
 
32
32
  def should_check_csrf?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWTSessions
4
- VERSION = '1.0.0-alpha.1'
4
+ VERSION = '1.0.0'
5
5
  end
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: 1.0.0.pre.alpha.1
4
+ version: 1.0.0
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-03-08 00:00:00.000000000 Z
11
+ date: 2018-04-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jwt
@@ -91,9 +91,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
91
91
  version: '0'
92
92
  required_rubygems_version: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: 1.3.1
96
+ version: '0'
97
97
  requirements: []
98
98
  rubyforge_project:
99
99
  rubygems_version: 2.6.13