From 9e33d1014f7479135e777a3a6bfe8e8fc46e943b Mon Sep 17 00:00:00 2001 From: Dmitrii Topornin Date: Mon, 5 Jun 2017 17:44:20 +0300 Subject: [PATCH 01/14] create JwtAuth submodules --- lib/sorcery.rb | 1 + lib/sorcery/controller/config.rb | 4 +- lib/sorcery/controller/submodules/jwt_auth.rb | 66 +++++++++++++++++++ sorcery.gemspec | 1 + 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 lib/sorcery/controller/submodules/jwt_auth.rb diff --git a/lib/sorcery.rb b/lib/sorcery.rb index e0df9312..ecac1e1d 100644 --- a/lib/sorcery.rb +++ b/lib/sorcery.rb @@ -32,6 +32,7 @@ module Submodules require 'sorcery/controller/submodules/http_basic_auth' require 'sorcery/controller/submodules/activity_logging' require 'sorcery/controller/submodules/external' + require 'sorcery/controller/submodules/jwt_auth' end end diff --git a/lib/sorcery/controller/config.rb b/lib/sorcery/controller/config.rb index 99e1f3fb..17e4846f 100644 --- a/lib/sorcery/controller/config.rb +++ b/lib/sorcery/controller/config.rb @@ -18,6 +18,7 @@ class << self attr_accessor :after_failed_login attr_accessor :before_logout attr_accessor :after_logout + attr_accessor :jwt_user_params def init! @defaults = { @@ -30,7 +31,8 @@ def init! :@before_logout => [], :@after_logout => [], :@save_return_to_url => true, - :@cookie_domain => nil + :@cookie_domain => nil, + :@jwt_user_params => [:id] } end diff --git a/lib/sorcery/controller/submodules/jwt_auth.rb b/lib/sorcery/controller/submodules/jwt_auth.rb new file mode 100644 index 00000000..f1ca0a3d --- /dev/null +++ b/lib/sorcery/controller/submodules/jwt_auth.rb @@ -0,0 +1,66 @@ +module Sorcery + module Controller + module Submodules + module JwtAuth + def self.included(base) + base.include(InstanceMethods) + end + + module InstanceMethods + def login_for_jwt(*credentials) + user = user_class.authenticate(*credentials) + if user + user_params = Config.jwt_user_params.each_with_object({}) do |val, acc| + acc[val] = user.public_send(val) + end + jwt_encode(user_params) + end + end + + def require_jwt_auth + authenticate_request! + end + + def authenticate_request! + not_authenticated && return unless user_id? + + @current_user = User.find(user_id) + rescue JWT::VerificationError, JWT::DecodeError + not_authenticated && return + end + + def http_token + @http_token ||= request.headers['Authorization']&.split(' ')&.last + end + + def auth_token + @auth_token ||= jwt_decode(http_token) + end + + def user_id + return if auth_token[:id].blank? + auth_token[:id].to_i + end + + def user_id? + http_token && auth_token && user_id + end + + def jwt_encode(payload) + JWT.encode(payload, Rails.application.secrets.secret_key_base) + end + + def jwt_decode(token) + HashWithIndifferentAccess.new( + JWT.decode(token, Rails.application.secrets.secret_key_base)[0] + ) + rescue JWT::DecodeError => e + e + end + + attr_reader :current_user + end + end + end + end +end \ No newline at end of file diff --git a/sorcery.gemspec b/sorcery.gemspec index 644ce0ce..ed264adc 100644 --- a/sorcery.gemspec +++ b/sorcery.gemspec @@ -23,6 +23,7 @@ Gem::Specification.new do |s| s.add_dependency 'oauth', '~> 0.4', '>= 0.4.4' s.add_dependency 'oauth2', '~> 1.0', '>= 0.8.0' s.add_dependency 'bcrypt', '~> 3.1' + s.add_dependency 'jwt', '~> 1.5' s.add_development_dependency 'yard', '~> 0.6.0' s.add_development_dependency 'timecop' From 5b3fe7cfca43ff8e14a8774b5a58d3d962f862f1 Mon Sep 17 00:00:00 2001 From: Dmitrii Topornin Date: Tue, 6 Jun 2017 12:48:03 +0300 Subject: [PATCH 02/14] refactor jwt_auth; add some params to config --- .../sorcery/templates/initializer.rb | 29 +++++++++++++- lib/sorcery/controller/config.rb | 12 +++++- lib/sorcery/controller/submodules/jwt_auth.rb | 38 ++++++++----------- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/lib/generators/sorcery/templates/initializer.rb b/lib/generators/sorcery/templates/initializer.rb index bb7cece2..e57921e1 100644 --- a/lib/generators/sorcery/templates/initializer.rb +++ b/lib/generators/sorcery/templates/initializer.rb @@ -1,7 +1,7 @@ # The first thing you need to configure is which modules you need in your app. # The default is nothing which will include only core features (password encryption, login/logout). # Available submodules are: :user_activation, :http_basic_auth, :remember_me, -# :reset_password, :session_timeout, :brute_force_protection, :activity_logging, :external +# :reset_password, :session_timeout, :brute_force_protection, :activity_logging, :external, :jwt_auth Rails.application.config.sorcery.submodules = [] # Here you can configure each submodule's features. @@ -442,6 +442,33 @@ # Default: `:uid` # # user.provider_uid_attribute_name = + + # -- jwt_auth -- + + # + # Default: [:id] + # + # config.jwt_user_params = + + # + # Default: `Authorization` + # + # config.jwt_headers_key = + + # + # Default: :user_data + # + # config.jwt_user_data_key = + + # + # Default: :auth_token + # + # config.jwt_auth_token_key = + + # + # Default: true + # + # config.jwt_set_user = end # This line must come after the 'user config' block. diff --git a/lib/sorcery/controller/config.rb b/lib/sorcery/controller/config.rb index 17e4846f..7e309229 100644 --- a/lib/sorcery/controller/config.rb +++ b/lib/sorcery/controller/config.rb @@ -19,6 +19,12 @@ class << self attr_accessor :before_logout attr_accessor :after_logout attr_accessor :jwt_user_params + attr_accessor :jwt_headers_key + attr_accessor :jwt_user_data_key + attr_accessor :jwt_auth_token_key + # If true, will set user by request to db. + # If false will use data from jwt_user_params without executing db requests. + attr_accessor :jwt_set_user def init! @defaults = { @@ -32,7 +38,11 @@ def init! :@after_logout => [], :@save_return_to_url => true, :@cookie_domain => nil, - :@jwt_user_params => [:id] + :@jwt_user_params => [:id], + :@jwt_headers_key => 'Authorization', + :@jwt_user_data_key => :user_data, + :@jwt_auth_token_key => :auth_token, + :@jwt_set_user => true } end diff --git a/lib/sorcery/controller/submodules/jwt_auth.rb b/lib/sorcery/controller/submodules/jwt_auth.rb index f1ca0a3d..4cd6d9eb 100644 --- a/lib/sorcery/controller/submodules/jwt_auth.rb +++ b/lib/sorcery/controller/submodules/jwt_auth.rb @@ -13,7 +13,8 @@ def login_for_jwt(*credentials) user_params = Config.jwt_user_params.each_with_object({}) do |val, acc| acc[val] = user.public_send(val) end - jwt_encode(user_params) + { Config.jwt_user_data_key => user_params, + Config.jwt_auth_token_key => jwt_encode(user_params) } end end @@ -22,30 +23,13 @@ def require_jwt_auth end def authenticate_request! - not_authenticated && return unless user_id? + not_authenticated && return unless user_id @current_user = User.find(user_id) rescue JWT::VerificationError, JWT::DecodeError not_authenticated && return end - def http_token - @http_token ||= request.headers['Authorization']&.split(' ')&.last - end - - def auth_token - @auth_token ||= jwt_decode(http_token) - end - - def user_id - return if auth_token[:id].blank? - auth_token[:id].to_i - end - - def user_id? - http_token && auth_token && user_id - end - def jwt_encode(payload) JWT.encode(payload, Rails.application.secrets.secret_key_base) end @@ -54,11 +38,21 @@ def jwt_decode(token) HashWithIndifferentAccess.new( JWT.decode(token, Rails.application.secrets.secret_key_base)[0] ) - rescue JWT::DecodeError => e - e + rescue JWT::DecodeError + nil + end + + def jwt_from_header + @header_token ||= request.headers[Config.jwt_headers_key] end - attr_reader :current_user + def user_data(token = jwt_from_header) + @user_data ||= jwt_decode(token) + end + + def user_id + user_data.try(:[], :id) + end end end end From 270ea1c2c93b62b220ad3f20326c77701ce2a410 Mon Sep 17 00:00:00 2001 From: Dmitrii Topornin Date: Wed, 7 Jun 2017 20:17:33 +0300 Subject: [PATCH 03/14] create spec --- spec/controllers/controller_jwt_auth_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 spec/controllers/controller_jwt_auth_spec.rb diff --git a/spec/controllers/controller_jwt_auth_spec.rb b/spec/controllers/controller_jwt_auth_spec.rb new file mode 100644 index 00000000..19c13c2d --- /dev/null +++ b/spec/controllers/controller_jwt_auth_spec.rb @@ -0,0 +1,4 @@ +require 'spec_helper' + +describe SorceryController, type: :controller do +end \ No newline at end of file From ef86e9cfc4cc38207b3e9f815501d992a4d790e1 Mon Sep 17 00:00:00 2001 From: Dmitrii Topornin Date: Thu, 8 Jun 2017 17:27:25 +0300 Subject: [PATCH 04/14] add some specs to jwt auth --- lib/sorcery/controller/submodules/jwt_auth.rb | 11 ++++- spec/controllers/controller_jwt_auth_spec.rb | 45 +++++++++++++++++++ .../app/controllers/sorcery_controller.rb | 10 +++++ spec/rails_app/config/routes.rb | 2 + 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/lib/sorcery/controller/submodules/jwt_auth.rb b/lib/sorcery/controller/submodules/jwt_auth.rb index 4cd6d9eb..19c9b14b 100644 --- a/lib/sorcery/controller/submodules/jwt_auth.rb +++ b/lib/sorcery/controller/submodules/jwt_auth.rb @@ -23,11 +23,11 @@ def require_jwt_auth end def authenticate_request! - not_authenticated && return unless user_id + jwt_not_authenticated && return unless user_id @current_user = User.find(user_id) rescue JWT::VerificationError, JWT::DecodeError - not_authenticated && return + jwt_not_authenticated && return end def jwt_encode(payload) @@ -53,6 +53,13 @@ def user_data(token = jwt_from_header) def user_id user_data.try(:[], :id) end + + def jwt_not_authenticated + respond_to do |format| + format.html { not_authenticated } + format.json { render json: { status: 401 }, status: 401 } + end + end end end end diff --git a/spec/controllers/controller_jwt_auth_spec.rb b/spec/controllers/controller_jwt_auth_spec.rb index 19c13c2d..ba3d90bc 100644 --- a/spec/controllers/controller_jwt_auth_spec.rb +++ b/spec/controllers/controller_jwt_auth_spec.rb @@ -1,4 +1,49 @@ require 'spec_helper' describe SorceryController, type: :controller do + let!(:user) { double('user', id: 42) } + + describe 'with jwt auth features' do + before(:all) do + sorcery_reload!([:jwt_auth]) + end + + describe '#login_for_jwt' do + context 'when success' do + let(:user_email) { 'test@test.test' } + let(:user_password) { 'testpass' } + let(:response_data) do + { + user_data: { id: user.id }, + auth_token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6NDJ9.mAd7vnXsLOwacr2AbfDAG6S0C-3pBHPrdYIoevtVRsw' + } + end + + before do + allow(User).to receive(:authenticate).with(user_email, user_password).and_return(user) + + post :test_jwt_auth, params: { email: user_email, password: user_password } + end + + it 'assigns user to @token variable' do + expect(assigns[:token]).to eq response_data + end + end + + context 'when fails' do + let(:user_email) { 'test@test.test' } + let(:user_password) { 'testpass' } + + before do + post :test_jwt_auth, params: { email: user_email, password: user_password } + end + + it 'require_login before_action' do + get :some_action_jwt, format: :json + + expect(response.status).to eq(401) + end + end + end + end end \ No newline at end of file diff --git a/spec/rails_app/app/controllers/sorcery_controller.rb b/spec/rails_app/app/controllers/sorcery_controller.rb index 1d916fe5..6e557a4b 100644 --- a/spec/rails_app/app/controllers/sorcery_controller.rb +++ b/spec/rails_app/app/controllers/sorcery_controller.rb @@ -5,6 +5,7 @@ class SorceryController < ActionController::Base before_action :require_login_from_http_basic, only: [:test_http_basic_auth] before_action :require_login, only: [:test_logout, :test_logout_with_force_forget_me, :test_should_be_logged_in, :some_action] + before_action :require_jwt_auth, only: [:some_action_jwt] def index; end @@ -367,4 +368,13 @@ def test_create_from_provider_with_block redirect_to 'blu', alert: 'Failed!' end end + + def test_jwt_auth + @token = login_for_jwt(params[:email], params[:password]) + head :ok + end + + def some_action_jwt + head :ok + end end diff --git a/spec/rails_app/config/routes.rb b/spec/rails_app/config/routes.rb index 712d7f6a..c278163e 100644 --- a/spec/rails_app/config/routes.rb +++ b/spec/rails_app/config/routes.rb @@ -60,5 +60,7 @@ post :test_login_with_remember get :test_create_from_provider_with_block get :login_at_test_with_state + post :test_jwt_auth + get :some_action_jwt end end From 9bb9443bac56b3398f1d2c1905b4c3936cdbd5f4 Mon Sep 17 00:00:00 2001 From: Dmitrii Topornin Date: Thu, 8 Jun 2017 18:08:39 +0300 Subject: [PATCH 05/14] change naming for jwt auth --- lib/sorcery/controller/submodules/jwt_auth.rb | 22 ++++++++----------- spec/controllers/controller_jwt_auth_spec.rb | 2 +- .../app/controllers/sorcery_controller.rb | 4 ++-- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/lib/sorcery/controller/submodules/jwt_auth.rb b/lib/sorcery/controller/submodules/jwt_auth.rb index 19c9b14b..fc6301d7 100644 --- a/lib/sorcery/controller/submodules/jwt_auth.rb +++ b/lib/sorcery/controller/submodules/jwt_auth.rb @@ -7,7 +7,7 @@ def self.included(base) end module InstanceMethods - def login_for_jwt(*credentials) + def jwt_login(*credentials) user = user_class.authenticate(*credentials) if user user_params = Config.jwt_user_params.each_with_object({}) do |val, acc| @@ -18,14 +18,10 @@ def login_for_jwt(*credentials) end end - def require_jwt_auth - authenticate_request! - end - - def authenticate_request! - jwt_not_authenticated && return unless user_id + def jwt_require_auth + jwt_not_authenticated && return unless jwt_user_id - @current_user = User.find(user_id) + @current_user = User.find jwt_user_id rescue JWT::VerificationError, JWT::DecodeError jwt_not_authenticated && return end @@ -43,15 +39,15 @@ def jwt_decode(token) end def jwt_from_header - @header_token ||= request.headers[Config.jwt_headers_key] + @jwt_header_token ||= request.headers[Config.jwt_headers_key] end - def user_data(token = jwt_from_header) - @user_data ||= jwt_decode(token) + def jwt_user_data(token = jwt_from_header) + @jwt_user_data ||= jwt_decode(token) end - def user_id - user_data.try(:[], :id) + def jwt_user_id + jwt_user_data.try(:[], :id) end def jwt_not_authenticated diff --git a/spec/controllers/controller_jwt_auth_spec.rb b/spec/controllers/controller_jwt_auth_spec.rb index ba3d90bc..ed2e9962 100644 --- a/spec/controllers/controller_jwt_auth_spec.rb +++ b/spec/controllers/controller_jwt_auth_spec.rb @@ -8,7 +8,7 @@ sorcery_reload!([:jwt_auth]) end - describe '#login_for_jwt' do + describe '#jwt_login' do context 'when success' do let(:user_email) { 'test@test.test' } let(:user_password) { 'testpass' } diff --git a/spec/rails_app/app/controllers/sorcery_controller.rb b/spec/rails_app/app/controllers/sorcery_controller.rb index 6e557a4b..34867abc 100644 --- a/spec/rails_app/app/controllers/sorcery_controller.rb +++ b/spec/rails_app/app/controllers/sorcery_controller.rb @@ -5,7 +5,7 @@ class SorceryController < ActionController::Base before_action :require_login_from_http_basic, only: [:test_http_basic_auth] before_action :require_login, only: [:test_logout, :test_logout_with_force_forget_me, :test_should_be_logged_in, :some_action] - before_action :require_jwt_auth, only: [:some_action_jwt] + before_action :jwt_require_auth, only: [:some_action_jwt] def index; end @@ -370,7 +370,7 @@ def test_create_from_provider_with_block end def test_jwt_auth - @token = login_for_jwt(params[:email], params[:password]) + @token = jwt_login(params[:email], params[:password]) head :ok end From a70144affc32bc32b606ee229486688c8d1d57f2 Mon Sep 17 00:00:00 2001 From: Dmitrii Topornin Date: Thu, 8 Jun 2017 18:27:20 +0300 Subject: [PATCH 06/14] add capability not to execute user find --- lib/sorcery/controller/submodules/jwt_auth.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sorcery/controller/submodules/jwt_auth.rb b/lib/sorcery/controller/submodules/jwt_auth.rb index fc6301d7..b7c38d7e 100644 --- a/lib/sorcery/controller/submodules/jwt_auth.rb +++ b/lib/sorcery/controller/submodules/jwt_auth.rb @@ -21,7 +21,7 @@ def jwt_login(*credentials) def jwt_require_auth jwt_not_authenticated && return unless jwt_user_id - @current_user = User.find jwt_user_id + @current_user = Config.jwt_set_user ? User.find(jwt_user_id) : jwt_user_data rescue JWT::VerificationError, JWT::DecodeError jwt_not_authenticated && return end From edd54830eade5751e62038ef384b5a8c2ba69825 Mon Sep 17 00:00:00 2001 From: Dmitrii Topornin Date: Fri, 9 Jun 2017 17:41:24 +0300 Subject: [PATCH 07/14] add generation of secret key for jwt --- lib/generators/sorcery/templates/initializer.rb | 5 +++++ lib/sorcery/controller/config.rb | 4 +++- lib/sorcery/controller/submodules/jwt_auth.rb | 4 ++-- spec/controllers/controller_jwt_auth_spec.rb | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/generators/sorcery/templates/initializer.rb b/lib/generators/sorcery/templates/initializer.rb index e57921e1..1d4faae5 100644 --- a/lib/generators/sorcery/templates/initializer.rb +++ b/lib/generators/sorcery/templates/initializer.rb @@ -469,6 +469,11 @@ # Default: true # # config.jwt_set_user = + + # + # Default: nil + # + # config.jwt_secret_key = '<%= SecureRandom.hex(64) %>' end # This line must come after the 'user config' block. diff --git a/lib/sorcery/controller/config.rb b/lib/sorcery/controller/config.rb index 7e309229..20ec7770 100644 --- a/lib/sorcery/controller/config.rb +++ b/lib/sorcery/controller/config.rb @@ -25,6 +25,7 @@ class << self # If true, will set user by request to db. # If false will use data from jwt_user_params without executing db requests. attr_accessor :jwt_set_user + attr_accessor :jwt_secret_key def init! @defaults = { @@ -42,7 +43,8 @@ def init! :@jwt_headers_key => 'Authorization', :@jwt_user_data_key => :user_data, :@jwt_auth_token_key => :auth_token, - :@jwt_set_user => true + :@jwt_set_user => true, + :@jwt_secret_key => '' } end diff --git a/lib/sorcery/controller/submodules/jwt_auth.rb b/lib/sorcery/controller/submodules/jwt_auth.rb index b7c38d7e..602a44c2 100644 --- a/lib/sorcery/controller/submodules/jwt_auth.rb +++ b/lib/sorcery/controller/submodules/jwt_auth.rb @@ -27,12 +27,12 @@ def jwt_require_auth end def jwt_encode(payload) - JWT.encode(payload, Rails.application.secrets.secret_key_base) + JWT.encode(payload, Config.jwt_secret_key) end def jwt_decode(token) HashWithIndifferentAccess.new( - JWT.decode(token, Rails.application.secrets.secret_key_base)[0] + JWT.decode(token, Config.jwt_secret_key)[0] ) rescue JWT::DecodeError nil diff --git a/spec/controllers/controller_jwt_auth_spec.rb b/spec/controllers/controller_jwt_auth_spec.rb index ed2e9962..37356865 100644 --- a/spec/controllers/controller_jwt_auth_spec.rb +++ b/spec/controllers/controller_jwt_auth_spec.rb @@ -15,7 +15,7 @@ let(:response_data) do { user_data: { id: user.id }, - auth_token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6NDJ9.mAd7vnXsLOwacr2AbfDAG6S0C-3pBHPrdYIoevtVRsw' + auth_token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6NDJ9.rrjj-sXvbIjT8y4MLGb88Cv7XvfpJXj-HEgaBimT_-0' } end From 7f12bd4055fb4e2b3a9f181de92d00489f85520d Mon Sep 17 00:00:00 2001 From: Dmitrii Topornin Date: Sat, 10 Jun 2017 19:53:59 +0300 Subject: [PATCH 08/14] add specs to test fail login, success auth --- spec/controllers/controller_jwt_auth_spec.rb | 52 +++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/spec/controllers/controller_jwt_auth_spec.rb b/spec/controllers/controller_jwt_auth_spec.rb index 37356865..01bbf7b8 100644 --- a/spec/controllers/controller_jwt_auth_spec.rb +++ b/spec/controllers/controller_jwt_auth_spec.rb @@ -4,21 +4,22 @@ let!(:user) { double('user', id: 42) } describe 'with jwt auth features' do + let(:user_email) { 'test@test.test' } + let(:user_password) { 'testpass' } + let(:auth_token) { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6NDJ9.rrjj-sXvbIjT8y4MLGb88Cv7XvfpJXj-HEgaBimT_-0' } + let(:response_data) do + { + user_data: { id: user.id }, + auth_token: auth_token + } + end + before(:all) do sorcery_reload!([:jwt_auth]) end describe '#jwt_login' do context 'when success' do - let(:user_email) { 'test@test.test' } - let(:user_password) { 'testpass' } - let(:response_data) do - { - user_data: { id: user.id }, - auth_token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6NDJ9.rrjj-sXvbIjT8y4MLGb88Cv7XvfpJXj-HEgaBimT_-0' - } - end - before do allow(User).to receive(:authenticate).with(user_email, user_password).and_return(user) @@ -31,14 +32,39 @@ end context 'when fails' do - let(:user_email) { 'test@test.test' } - let(:user_password) { 'testpass' } - before do + allow(User).to receive(:authenticate).with(user_email, user_password).and_return(nil) + post :test_jwt_auth, params: { email: user_email, password: user_password } end - it 'require_login before_action' do + it 'assigns user to @token variable' do + expect(assigns[:token]).to eq nil + end + end + end + + describe '#jwt_require_auth' do + context 'when success' do + before do + allow(User).to receive(:find).with(user.id).and_return(user) + allow(user).to receive(:set_last_activity_at) + end + + it 'does return 200' do + request.headers.merge! Authorization: auth_token + + get :some_action_jwt, format: :json + + expect(response.status).to eq(200) + end + end + + context 'when fails' do + let(:user_email) { 'test@test.test' } + let(:user_password) { 'testpass' } + + it 'does return 401' do get :some_action_jwt, format: :json expect(response.status).to eq(401) From 3366df15fb1dc63079c2ab065848c7d29ee575a9 Mon Sep 17 00:00:00 2001 From: Dmitrii Topornin Date: Mon, 12 Jun 2017 18:56:33 +0300 Subject: [PATCH 09/14] add description to jwt params in initializer file --- lib/generators/sorcery/templates/initializer.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/generators/sorcery/templates/initializer.rb b/lib/generators/sorcery/templates/initializer.rb index 1d4faae5..eb58741c 100644 --- a/lib/generators/sorcery/templates/initializer.rb +++ b/lib/generators/sorcery/templates/initializer.rb @@ -445,32 +445,32 @@ # -- jwt_auth -- - # + # Parameters passed for generating payload part of token # Default: [:id] # # config.jwt_user_params = - # + # Header name which will parsed # Default: `Authorization` # # config.jwt_headers_key = - # + # Key on which returned user data # Default: :user_data # # config.jwt_user_data_key = - # + # Key on which returned token # Default: :auth_token # # config.jwt_auth_token_key = - # + # A flag that specifies whether to perform a database query to set the current_user # Default: true # # config.jwt_set_user = - # + # Secret key for token generation # Default: nil # # config.jwt_secret_key = '<%= SecureRandom.hex(64) %>' From 31fcc44c083d5efe94c1e8dd1225022ab2c5d1b8 Mon Sep 17 00:00:00 2001 From: Dmitrii Topornin Date: Tue, 13 Jun 2017 22:01:33 +0300 Subject: [PATCH 10/14] add description to jwt auth methods --- lib/sorcery/controller/submodules/jwt_auth.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/sorcery/controller/submodules/jwt_auth.rb b/lib/sorcery/controller/submodules/jwt_auth.rb index 602a44c2..48f1ee19 100644 --- a/lib/sorcery/controller/submodules/jwt_auth.rb +++ b/lib/sorcery/controller/submodules/jwt_auth.rb @@ -7,6 +7,7 @@ def self.included(base) end module InstanceMethods + # This method return generated token if user can be authenticated def jwt_login(*credentials) user = user_class.authenticate(*credentials) if user @@ -18,6 +19,7 @@ def jwt_login(*credentials) end end + # To be used as a before_action. def jwt_require_auth jwt_not_authenticated && return unless jwt_user_id @@ -26,10 +28,13 @@ def jwt_require_auth jwt_not_authenticated && return end + # This method creating JWT token by payload def jwt_encode(payload) JWT.encode(payload, Config.jwt_secret_key) end + # This method decoding JWT token + # Return nil if token incorrect def jwt_decode(token) HashWithIndifferentAccess.new( JWT.decode(token, Config.jwt_secret_key)[0] @@ -38,18 +43,25 @@ def jwt_decode(token) nil end + # Take token from header, by key defined in config + # With memoization def jwt_from_header @jwt_header_token ||= request.headers[Config.jwt_headers_key] end + # Return user data which decoded from token + # With memoization def jwt_user_data(token = jwt_from_header) @jwt_user_data ||= jwt_decode(token) end + # Return user id from user data if id present. + # Else return nil def jwt_user_id jwt_user_data.try(:[], :id) end + # This method called if user not authenticated def jwt_not_authenticated respond_to do |format| format.html { not_authenticated } From 9f84e58d24769dade27ea9e4d12d2ad1adb38c23 Mon Sep 17 00:00:00 2001 From: Dmitrii Topornin Date: Wed, 14 Jun 2017 20:28:26 +0300 Subject: [PATCH 11/14] add specs --- spec/controllers/controller_jwt_auth_spec.rb | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/spec/controllers/controller_jwt_auth_spec.rb b/spec/controllers/controller_jwt_auth_spec.rb index 01bbf7b8..29c05b71 100644 --- a/spec/controllers/controller_jwt_auth_spec.rb +++ b/spec/controllers/controller_jwt_auth_spec.rb @@ -64,10 +64,24 @@ let(:user_email) { 'test@test.test' } let(:user_password) { 'testpass' } - it 'does return 401' do - get :some_action_jwt, format: :json + context 'without auth header' do + it 'does return 401' do + get :some_action_jwt, format: :json + + expect(response.status).to eq(401) + end + end + + context 'with incorrect auth header' do + let(:incorrect_header) { '123.123.123' } + + it 'does return 401' do + request.headers.merge! Authorization: incorrect_header + + get :some_action_jwt, format: :json - expect(response.status).to eq(401) + expect(response.status).to eq(401) + end end end end From e6a3d4e8f369f3fc5f7b1d0445ed4ca4797bbfb1 Mon Sep 17 00:00:00 2001 From: Dmitrii Topornin Date: Thu, 15 Jun 2017 17:15:54 +0300 Subject: [PATCH 12/14] rename methods --- lib/sorcery/controller/submodules/jwt_auth.rb | 2 +- spec/controllers/controller_jwt_auth_spec.rb | 2 +- spec/rails_app/app/controllers/sorcery_controller.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/sorcery/controller/submodules/jwt_auth.rb b/lib/sorcery/controller/submodules/jwt_auth.rb index 48f1ee19..cf9c98b1 100644 --- a/lib/sorcery/controller/submodules/jwt_auth.rb +++ b/lib/sorcery/controller/submodules/jwt_auth.rb @@ -8,7 +8,7 @@ def self.included(base) module InstanceMethods # This method return generated token if user can be authenticated - def jwt_login(*credentials) + def jwt_auth(*credentials) user = user_class.authenticate(*credentials) if user user_params = Config.jwt_user_params.each_with_object({}) do |val, acc| diff --git a/spec/controllers/controller_jwt_auth_spec.rb b/spec/controllers/controller_jwt_auth_spec.rb index 29c05b71..e7f1f847 100644 --- a/spec/controllers/controller_jwt_auth_spec.rb +++ b/spec/controllers/controller_jwt_auth_spec.rb @@ -18,7 +18,7 @@ sorcery_reload!([:jwt_auth]) end - describe '#jwt_login' do + describe '#jwt_auth' do context 'when success' do before do allow(User).to receive(:authenticate).with(user_email, user_password).and_return(user) diff --git a/spec/rails_app/app/controllers/sorcery_controller.rb b/spec/rails_app/app/controllers/sorcery_controller.rb index 34867abc..f9ea4c99 100644 --- a/spec/rails_app/app/controllers/sorcery_controller.rb +++ b/spec/rails_app/app/controllers/sorcery_controller.rb @@ -370,7 +370,7 @@ def test_create_from_provider_with_block end def test_jwt_auth - @token = jwt_login(params[:email], params[:password]) + @token = jwt_auth(params[:email], params[:password]) head :ok end From 87de4bff57d855546fbabce19d95e351c3915b9e Mon Sep 17 00:00:00 2001 From: Dmitrii Topornin Date: Thu, 15 Jun 2017 17:19:58 +0300 Subject: [PATCH 13/14] add readme --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 701bf364..1834df85 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,20 @@ login_from(provider) # Tries to login from the external provider's callback create_from(provider) # Create the user in the local app database ``` +### JWT authentication + +```ruby +jwt_require_auth # This is a before action +jwt_auth(email, password) # => return json web token +jwt_encode # This method creating JWT token by payload +jwt_decode # This method decoding JWT token +jwt_from_header # Take token from header, by key defined in config +jwt_user_data(token = jwt_from_header) # Return user data which decoded from token +jwt_user_id # Return user id from user data if id present. +jwt_not_authenticated # This method called if user not authenticated + +``` + ### Remember Me ```ruby From b06aa7a371cdca693d4d7877215e0ac39991bbdd Mon Sep 17 00:00:00 2001 From: Dmitrii Topornin Date: Mon, 19 Jun 2017 02:37:51 +0300 Subject: [PATCH 14/14] fix specs for some ruby/rails versions --- lib/sorcery/controller/submodules/jwt_auth.rb | 2 +- spec/controllers/controller_jwt_auth_spec.rb | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/sorcery/controller/submodules/jwt_auth.rb b/lib/sorcery/controller/submodules/jwt_auth.rb index cf9c98b1..1efe4539 100644 --- a/lib/sorcery/controller/submodules/jwt_auth.rb +++ b/lib/sorcery/controller/submodules/jwt_auth.rb @@ -3,7 +3,7 @@ module Controller module Submodules module JwtAuth def self.included(base) - base.include(InstanceMethods) + base.send(:include, InstanceMethods) end module InstanceMethods diff --git a/spec/controllers/controller_jwt_auth_spec.rb b/spec/controllers/controller_jwt_auth_spec.rb index e7f1f847..88be451c 100644 --- a/spec/controllers/controller_jwt_auth_spec.rb +++ b/spec/controllers/controller_jwt_auth_spec.rb @@ -2,6 +2,9 @@ describe SorceryController, type: :controller do let!(:user) { double('user', id: 42) } + before(:each) do + request.env['HTTP_ACCEPT'] = "application/json" if ::Rails.version < '5.0.0' + end describe 'with jwt auth features' do let(:user_email) { 'test@test.test' }