jwt-auth 4.2.0 → 5.0.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/.travis.yml +3 -0
- data/Gemfile +3 -0
- data/README.md +119 -18
- data/bin/build +22 -0
- data/bin/release +40 -0
- data/jwt-auth.gemspec +18 -15
- data/lib/jwt/auth.rb +2 -0
- data/lib/jwt/auth/access_token.rb +20 -0
- data/lib/jwt/auth/authenticatable.rb +16 -0
- data/lib/jwt/auth/authentication.rb +63 -22
- data/lib/jwt/auth/configuration.rb +4 -1
- data/lib/jwt/auth/refresh_token.rb +20 -0
- data/lib/jwt/auth/token.rb +49 -41
- data/lib/jwt/auth/version.rb +3 -1
- data/spec/controllers/content_controller_spec.rb +95 -0
- data/spec/controllers/tokens_controller_spec.rb +140 -0
- data/spec/dummy/Rakefile +2 -0
- data/spec/dummy/app/channels/application_cable/channel.rb +2 -0
- data/spec/dummy/app/channels/application_cable/connection.rb +2 -0
- data/spec/dummy/app/controllers/application_controller.rb +6 -1
- data/spec/dummy/app/controllers/content_controller.rb +29 -0
- data/spec/dummy/app/controllers/tokens_controller.rb +53 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/helpers/authentication_helper.rb +2 -0
- data/spec/dummy/app/jobs/application_job.rb +2 -0
- data/spec/dummy/app/mailers/application_mailer.rb +3 -1
- data/spec/dummy/app/models/application_record.rb +2 -0
- data/spec/dummy/app/models/user.rb +3 -6
- data/spec/dummy/bin/bundle +2 -0
- data/spec/dummy/bin/rails +2 -0
- data/spec/dummy/bin/rake +2 -0
- data/spec/dummy/bin/setup +2 -0
- data/spec/dummy/bin/update +2 -0
- data/spec/dummy/bin/yarn +7 -7
- data/spec/dummy/config.ru +2 -0
- data/spec/dummy/config/application.rb +2 -0
- data/spec/dummy/config/boot.rb +3 -1
- data/spec/dummy/config/environment.rb +2 -0
- data/spec/dummy/config/environments/development.rb +3 -1
- data/spec/dummy/config/environments/production.rb +4 -2
- data/spec/dummy/config/environments/test.rb +2 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +2 -0
- data/spec/dummy/config/initializers/assets.rb +2 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +2 -0
- data/spec/dummy/config/initializers/content_security_policy.rb +2 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +2 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +2 -0
- data/spec/dummy/config/initializers/inflections.rb +2 -0
- data/spec/dummy/config/initializers/jwt_auth.rb +9 -2
- data/spec/dummy/config/initializers/mime_types.rb +2 -0
- data/spec/dummy/config/initializers/new_framework_defaults_5_2.rb +2 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +3 -1
- data/spec/dummy/config/puma.rb +5 -3
- data/spec/dummy/config/routes.rb +5 -4
- data/spec/dummy/config/spring.rb +4 -2
- data/spec/dummy/db/migrate/20170726110751_create_users.rb +2 -0
- data/spec/dummy/db/migrate/20170726110825_add_token_version_to_user.rb +2 -0
- data/spec/dummy/db/migrate/20170726112117_add_activated_to_user.rb +2 -0
- data/spec/dummy/db/migrate/20190221100103_add_password_to_user.rb +7 -0
- data/spec/dummy/db/schema.rb +10 -9
- data/spec/jwt/auth/access_token_spec.rb +35 -0
- data/spec/jwt/auth/configuration_spec.rb +36 -0
- data/spec/jwt/auth/refresh_token_spec.rb +35 -0
- data/spec/jwt/auth/token_spec.rb +144 -0
- data/spec/models/user_spec.rb +24 -0
- data/spec/rails_helper.rb +8 -0
- data/spec/spec_helper.rb +51 -53
- data/spec/support/database_cleaner.rb +22 -0
- data/spec/support/matchers/return_token.rb +33 -0
- data/version.yml +1 -0
- metadata +119 -54
- data/spec/authentication_spec.rb +0 -136
- data/spec/configuration_spec.rb +0 -18
- data/spec/dummy/app/controllers/authentication_controller.rb +0 -22
- data/spec/token_spec.rb +0 -125
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jwt/auth/configuration'
|
4
|
+
|
5
|
+
module JWT
|
6
|
+
module Auth
|
7
|
+
##
|
8
|
+
# JWT refresh token
|
9
|
+
#
|
10
|
+
class RefreshToken < Token
|
11
|
+
def type
|
12
|
+
:refresh
|
13
|
+
end
|
14
|
+
|
15
|
+
def lifetime
|
16
|
+
JWT::Auth.refresh_token_lifetime
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/jwt/auth/token.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# require 'active_support/core_ext/numeric/time'
|
4
|
-
|
5
3
|
require 'jwt/auth/configuration'
|
6
4
|
|
7
5
|
module JWT
|
@@ -10,68 +8,67 @@ module JWT
|
|
10
8
|
# In-memory representation of JWT
|
11
9
|
#
|
12
10
|
class Token
|
13
|
-
attr_accessor :issued_at,
|
11
|
+
attr_accessor :issued_at,
|
12
|
+
:subject,
|
13
|
+
:version
|
14
|
+
|
15
|
+
def initialize(params = {})
|
16
|
+
params.each { |key, value| send "#{key}=", value }
|
17
|
+
end
|
14
18
|
|
15
19
|
def valid?
|
16
20
|
# Reload subject to prevent caching the old token_version
|
17
|
-
subject
|
21
|
+
subject&.reload
|
18
22
|
|
19
|
-
return false if subject.nil? || issued_at.nil? ||
|
23
|
+
return false if subject.nil? || issued_at.nil? || version.nil?
|
20
24
|
return false if Time.at(issued_at + lifetime.to_i).past?
|
21
25
|
return false if Time.at(issued_at).future?
|
22
|
-
return false if
|
26
|
+
return false if version != subject.token_version
|
23
27
|
|
24
28
|
true
|
25
29
|
rescue ActiveRecord::RecordNotFound
|
26
30
|
false
|
27
31
|
end
|
28
32
|
|
29
|
-
def renew!
|
30
|
-
self.issued_at = nil
|
31
|
-
self.token_version = nil
|
32
|
-
end
|
33
|
-
|
34
33
|
def to_jwt
|
35
34
|
JWT.encode payload, JWT::Auth.secret
|
36
35
|
end
|
37
36
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
def payload
|
46
|
-
{
|
47
|
-
:iat => issued_at || Time.now.to_i,
|
48
|
-
:sub => subject.id,
|
49
|
-
:ver => token_version || subject.token_version
|
50
|
-
}
|
37
|
+
##
|
38
|
+
# Override this method in subclasses
|
39
|
+
#
|
40
|
+
def type
|
41
|
+
raise NotImplementedError
|
51
42
|
end
|
52
43
|
|
44
|
+
##
|
45
|
+
# Override this method in subclasses
|
46
|
+
#
|
53
47
|
def lifetime
|
54
|
-
|
48
|
+
raise NotImplementedError
|
55
49
|
end
|
56
50
|
|
57
51
|
class << self
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
@decoded_payload
|
52
|
+
def from_jwt(token)
|
53
|
+
@decoded_payload = JWT.decode(token, JWT::Auth.secret).first
|
54
|
+
|
55
|
+
params = {
|
56
|
+
:issued_at => @decoded_payload['iat'],
|
57
|
+
:version => @decoded_payload['ver'],
|
58
|
+
:subject => model.find_by_token(:id => @decoded_payload['sub'],
|
59
|
+
:token_version => @decoded_payload['ver'])
|
60
|
+
}
|
61
|
+
|
62
|
+
case @decoded_payload['typ']
|
63
|
+
when 'access'
|
64
|
+
return AccessToken.new params
|
65
|
+
when 'refresh'
|
66
|
+
return RefreshToken.new params
|
67
|
+
else
|
68
|
+
return nil
|
63
69
|
end
|
64
|
-
|
65
|
-
|
66
|
-
token.issued_at = @decoded_payload['iat']
|
67
|
-
token.token_version = @decoded_payload['ver']
|
68
|
-
|
69
|
-
if @decoded_payload['sub']
|
70
|
-
find_method = model.respond_to?(:find_by_token) ? :find_by_token : :find_by
|
71
|
-
token.subject = model.send find_method, :id => @decoded_payload['sub'], :token_version => @decoded_payload['ver']
|
72
|
-
end
|
73
|
-
|
74
|
-
token
|
70
|
+
rescue JWT::DecodeError
|
71
|
+
nil
|
75
72
|
end
|
76
73
|
|
77
74
|
private
|
@@ -80,6 +77,17 @@ module JWT
|
|
80
77
|
const_get JWT::Auth.model
|
81
78
|
end
|
82
79
|
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def payload
|
84
|
+
{
|
85
|
+
:iat => issued_at || Time.now.to_i,
|
86
|
+
:sub => subject.id,
|
87
|
+
:ver => version || subject.token_version,
|
88
|
+
:typ => type
|
89
|
+
}
|
90
|
+
end
|
83
91
|
end
|
84
92
|
end
|
85
93
|
end
|
data/lib/jwt/auth/version.rb
CHANGED
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe ContentController do
|
6
|
+
##
|
7
|
+
# Configuration
|
8
|
+
#
|
9
|
+
##
|
10
|
+
# Test variables
|
11
|
+
#
|
12
|
+
let(:user) { User.create :activated => true }
|
13
|
+
|
14
|
+
let(:headers) do
|
15
|
+
{ 'Authorization' => "Bearer #{token.to_jwt}" }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Ensure the user is created, which autoloads the model and initializes the JWT::Auth.model configuration entry
|
19
|
+
before { user }
|
20
|
+
|
21
|
+
##
|
22
|
+
# Tests
|
23
|
+
#
|
24
|
+
describe 'GET /unauthenticated' do
|
25
|
+
subject { get :unauthenticated }
|
26
|
+
|
27
|
+
context 'when no token was specified' do
|
28
|
+
it { is_expected.to have_http_status :no_content }
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when a refresh token was specified' do
|
32
|
+
let(:token) { JWT::Auth::RefreshToken.new :subject => user }
|
33
|
+
|
34
|
+
before { @request.headers.merge! headers }
|
35
|
+
|
36
|
+
it { is_expected.to have_http_status :unauthorized }
|
37
|
+
|
38
|
+
context 'when the token was invalid' do
|
39
|
+
before { user.increment! :token_version }
|
40
|
+
|
41
|
+
it { is_expected.to have_http_status :unauthorized }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when an access token was specified' do
|
46
|
+
let(:token) { JWT::Auth::AccessToken.new :subject => user }
|
47
|
+
|
48
|
+
before { @request.headers.merge! headers }
|
49
|
+
|
50
|
+
it { is_expected.to have_http_status :no_content }
|
51
|
+
|
52
|
+
context 'when the token was invalid' do
|
53
|
+
before { user.increment! :token_version }
|
54
|
+
|
55
|
+
it { is_expected.to have_http_status :unauthorized }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'GET /authenticated' do
|
61
|
+
subject { get :authenticated }
|
62
|
+
|
63
|
+
context 'when no token was specified' do
|
64
|
+
it { is_expected.to have_http_status :unauthorized }
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'when a refresh token was specified' do
|
68
|
+
let(:token) { JWT::Auth::RefreshToken.new :subject => user }
|
69
|
+
|
70
|
+
before { @request.headers.merge! headers }
|
71
|
+
|
72
|
+
it { is_expected.to have_http_status :unauthorized }
|
73
|
+
|
74
|
+
context 'when the token was invalid' do
|
75
|
+
before { user.increment! :token_version }
|
76
|
+
|
77
|
+
it { is_expected.to have_http_status :unauthorized }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'when an access token was specified' do
|
82
|
+
let(:token) { JWT::Auth::AccessToken.new :subject => user }
|
83
|
+
|
84
|
+
before { @request.headers.merge! headers }
|
85
|
+
|
86
|
+
it { is_expected.to have_http_status :no_content }
|
87
|
+
|
88
|
+
context 'when the token was invalid' do
|
89
|
+
before { user.increment! :token_version }
|
90
|
+
|
91
|
+
it { is_expected.to have_http_status :unauthorized }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe TokensController do
|
6
|
+
##
|
7
|
+
# Configuration
|
8
|
+
#
|
9
|
+
##
|
10
|
+
# Test variables
|
11
|
+
#
|
12
|
+
let(:user) { User.create :activated => true, :email => 'foo@bar', :password => 'foobar' }
|
13
|
+
|
14
|
+
let(:headers) do
|
15
|
+
{ 'Authorization' => "Bearer #{token.to_jwt}" }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Ensure the user is created, which autoloads the model and initializes the JWT::Auth.model configuration entry
|
19
|
+
before { user }
|
20
|
+
|
21
|
+
##
|
22
|
+
# Tests
|
23
|
+
#
|
24
|
+
describe 'POST /' do
|
25
|
+
let(:user) { User.create :activated => activated, :email => 'foo@bar', :password => 'foobar' }
|
26
|
+
let(:activated) { true }
|
27
|
+
let(:password) { 'foobar' }
|
28
|
+
|
29
|
+
subject { post :create, :params => { :email => 'foo@bar', :password => password } }
|
30
|
+
|
31
|
+
it { is_expected.to have_http_status :no_content }
|
32
|
+
it { is_expected.to return_token JWT::Auth::RefreshToken }
|
33
|
+
|
34
|
+
context 'when the credentials are incorrect' do
|
35
|
+
let(:password) { 'barfoo' }
|
36
|
+
|
37
|
+
it { is_expected.to have_http_status :unauthorized }
|
38
|
+
it { is_expected.not_to return_token }
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when the user is not activated' do
|
42
|
+
let(:activated) { false }
|
43
|
+
|
44
|
+
it { is_expected.to have_http_status :unauthorized }
|
45
|
+
it { is_expected.not_to return_token }
|
46
|
+
|
47
|
+
context 'when the credentials are incorrect' do
|
48
|
+
let(:password) { 'barfoo' }
|
49
|
+
|
50
|
+
it { is_expected.to have_http_status :unauthorized }
|
51
|
+
it { is_expected.not_to return_token }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'PATCH /' do
|
57
|
+
let(:user) { User.create :activated => activated, :email => 'foo@bar', :password => 'foobar' }
|
58
|
+
let(:activated) { true }
|
59
|
+
|
60
|
+
subject { patch :update }
|
61
|
+
|
62
|
+
context 'when no token is present' do
|
63
|
+
it { is_expected.to have_http_status :unauthorized }
|
64
|
+
it { is_expected.not_to return_token }
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'when a refresh token is present' do
|
68
|
+
let(:token) { JWT::Auth::RefreshToken.new :subject => user }
|
69
|
+
|
70
|
+
before { @request.headers.merge! headers }
|
71
|
+
|
72
|
+
it { is_expected.to have_http_status :no_content }
|
73
|
+
it { is_expected.to return_token JWT::Auth::AccessToken }
|
74
|
+
|
75
|
+
context 'when the token is invalid' do
|
76
|
+
before { user.increment! :token_version }
|
77
|
+
|
78
|
+
it { is_expected.to have_http_status :unauthorized }
|
79
|
+
it { is_expected.not_to return_token }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'when an access token is present' do
|
84
|
+
let(:token) { JWT::Auth::AccessToken.new :subject => user }
|
85
|
+
|
86
|
+
before { @request.headers.merge! headers }
|
87
|
+
|
88
|
+
it { is_expected.to have_http_status :unauthorized }
|
89
|
+
it { is_expected.not_to return_token }
|
90
|
+
|
91
|
+
context 'when the token is invalid' do
|
92
|
+
before { user.increment! :token_version }
|
93
|
+
|
94
|
+
it { is_expected.to have_http_status :unauthorized }
|
95
|
+
it { is_expected.not_to return_token }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'when the user is not activated' do
|
100
|
+
let(:activated) { false }
|
101
|
+
|
102
|
+
context 'when no token is present' do
|
103
|
+
it { is_expected.to have_http_status :unauthorized }
|
104
|
+
it { is_expected.not_to return_token }
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'when a refresh token is present' do
|
108
|
+
let(:token) { JWT::Auth::RefreshToken.new :subject => user }
|
109
|
+
|
110
|
+
before { @request.headers.merge! headers }
|
111
|
+
|
112
|
+
it { is_expected.to have_http_status :unauthorized }
|
113
|
+
it { is_expected.not_to return_token }
|
114
|
+
|
115
|
+
context 'when the token is invalid' do
|
116
|
+
before { user.increment! :token_version }
|
117
|
+
|
118
|
+
it { is_expected.to have_http_status :unauthorized }
|
119
|
+
it { is_expected.not_to return_token }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'when an access token is present' do
|
124
|
+
let(:token) { JWT::Auth::AccessToken.new :subject => user }
|
125
|
+
|
126
|
+
before { @request.headers.merge! headers }
|
127
|
+
|
128
|
+
it { is_expected.to have_http_status :unauthorized }
|
129
|
+
it { is_expected.not_to return_token }
|
130
|
+
|
131
|
+
context 'when the token is invalid' do
|
132
|
+
before { user.increment! :token_version }
|
133
|
+
|
134
|
+
it { is_expected.to have_http_status :unauthorized }
|
135
|
+
it { is_expected.not_to return_token }
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
data/spec/dummy/Rakefile
CHANGED
@@ -1,10 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ApplicationController < ActionController::Base
|
2
|
-
protect_from_forgery with
|
4
|
+
protect_from_forgery :with => :exception
|
3
5
|
|
4
6
|
include JWT::Auth::Authentication
|
5
7
|
|
6
8
|
rescue_from JWT::Auth::UnauthorizedError, :with => :handle_unauthorized
|
7
9
|
|
10
|
+
# Validate validity of token (if present) on all routes
|
11
|
+
before_action :validate_token
|
12
|
+
|
8
13
|
protected
|
9
14
|
|
10
15
|
def handle_unauthorized
|