diff --git a/app/controllers/jwt_authentication/confirmations_controller.rb b/app/controllers/jwt_authentication/confirmations_controller.rb index ee7c8f1..00a51b3 100644 --- a/app/controllers/jwt_authentication/confirmations_controller.rb +++ b/app/controllers/jwt_authentication/confirmations_controller.rb @@ -11,7 +11,9 @@ def show self.resource = resource_class.confirm_by_token(params[:confirmation_token]) yield resource if block_given? if resource.errors.empty? - render json: { auth_token: resource.jwt_token } + token, expires = resource.jwt_token_and_expires + send(:"set_jwt_cookie_for_#{resource_name}", token, expires) + render json: { auth_token: token } else render_errors resource.errors end diff --git a/app/controllers/jwt_authentication/passwords_controller.rb b/app/controllers/jwt_authentication/passwords_controller.rb index bd1c208..4615a00 100644 --- a/app/controllers/jwt_authentication/passwords_controller.rb +++ b/app/controllers/jwt_authentication/passwords_controller.rb @@ -17,7 +17,9 @@ def update if resource.errors.empty? resource.unlock_access! if unlockable?(resource) sign_in(resource_name, resource) - render json: { auth_token: resource.jwt_token } + token, expires = resource.jwt_token_and_expires + send(:"set_jwt_cookie_for_#{resource_name}", token, expires) + render json: { auth_token: token } else render_errors resource.errors end diff --git a/app/controllers/jwt_authentication/registrations_controller.rb b/app/controllers/jwt_authentication/registrations_controller.rb index e81eb77..38a0226 100644 --- a/app/controllers/jwt_authentication/registrations_controller.rb +++ b/app/controllers/jwt_authentication/registrations_controller.rb @@ -8,8 +8,10 @@ def create if resource_saved if resource.active_for_authentication? sign_in(resource_name, resource, store: false) + token, expires = resource.jwt_token_and_expires + send(:"set_jwt_cookie_for_#{resource_name}", token, expires) render json: { - auth_token: resource.jwt_token, + auth_token: token, resource_name => resource } return diff --git a/app/controllers/jwt_authentication/sessions_controller.rb b/app/controllers/jwt_authentication/sessions_controller.rb index 968c03c..b64d3c9 100644 --- a/app/controllers/jwt_authentication/sessions_controller.rb +++ b/app/controllers/jwt_authentication/sessions_controller.rb @@ -5,7 +5,10 @@ def create self.resource = warden.authenticate!({ scope: resource_name, recall: "#{controller_path}#new", store: false }) sign_in(resource_name, resource) yield resource if block_given? - render json: { auth_token: resource.jwt_token(sign_in_params[:remember_me]) } + + token, expires = resource.jwt_token_and_expires(sign_in_params[:remember_me]) + send(:"set_jwt_cookie_for_#{resource_name}", token, expires) + render json: { auth_token: token } end def destroy diff --git a/lib/generators/templates/jwt_authentication.rb b/lib/generators/templates/jwt_authentication.rb index 4a702eb..87c9223 100644 --- a/lib/generators/templates/jwt_authentication.rb +++ b/lib/generators/templates/jwt_authentication.rb @@ -4,11 +4,13 @@ # # Note: specified model should have `authentication_token` attribute (Model should "act as jwt authenticatable") # # header_name - name of header to search auth_token in request # # param_name - name of parameters to search auth_token in request + # # cookie_name - name of cookie to search auth_token in request # # sign_in - method to be executed if authentication success, possible values: :devise, :simplified # # if :devise selected, devises method sign_in() will be called at success authentication, # # if :simplified selected, instance variable with name of resource will be set (@user or @terminal) # config.models = {user: {header_name: 'X-User-Token', # param_name: 'user_token', + # # cookie_name: 'user_token', # sign_in: :devise}} # # # Configure mark of jwt timeout verification diff --git a/lib/jwt_authentication.rb b/lib/jwt_authentication.rb index 9555a95..3101853 100644 --- a/lib/jwt_authentication.rb +++ b/lib/jwt_authentication.rb @@ -1,14 +1,12 @@ require 'jwt_authentication/acts_as_jwt_authenticatable' require 'jwt_authentication/acts_as_jwt_authentication_handler' require 'jwt_authentication/configuration' +require 'jwt_authentication/engine' require 'jwt' module JwtAuthentication extend Configuration - class Engine < ::Rails::Engine - end - NoAdapterAvailableError = Class.new(LoadError) private diff --git a/lib/jwt_authentication/acts_as_jwt_authenticatable.rb b/lib/jwt_authentication/acts_as_jwt_authenticatable.rb index 1892a73..16bb0e1 100644 --- a/lib/jwt_authentication/acts_as_jwt_authenticatable.rb +++ b/lib/jwt_authentication/acts_as_jwt_authenticatable.rb @@ -9,6 +9,7 @@ module ActsAsJwtAuthenticatable private :generate_authentication_token private :token_suitable? private :token_generator + private :token_expires_and_data end def ensure_authentication_token @@ -36,13 +37,21 @@ def token_generator @token_generator ||= TokenGenerator.new end - def jwt_token(remember = false) + def token_expires_and_data(remember = false) data = self.class.jwt_key_fields.inject({}) { |hash, field| hash[field] = self.send field; hash } - payload = { - exp: (Time.now + jwt_session_duration(remember)).to_i, - self.class.name.underscore => data - } - JWT.encode(payload, self.authentication_token) + + [Time.now + jwt_session_duration(remember), data] + end + + def jwt_token_and_expires(remember = false) + expires, data = token_expires_and_data(remember) + payload = {exp: expires.to_i, self.class.name.underscore => data} + + [JWT.encode(payload, self.authentication_token), expires] + end + + def jwt_token(remember = false) + jwt_token_and_expires(remember).first end def jwt_session_duration(remember = false) diff --git a/lib/jwt_authentication/engine.rb b/lib/jwt_authentication/engine.rb new file mode 100644 index 0000000..b9811b0 --- /dev/null +++ b/lib/jwt_authentication/engine.rb @@ -0,0 +1,15 @@ +module JwtAuthentication + class Engine < ::Rails::Engine + initializer 'jwt-autentication' do |app| + cookies_required = JwtAuthentication.models.any? { |key, value| value.is_a?(Hash) && value.has_key?(:cookie_name) } + + if cookies_required + app.middleware.use ::ActionDispatch::Cookies + + JwtAuthentication::JwtAuthenticationHandler.module_exec do + include ::ActionController::Cookies + end + end + end + end +end diff --git a/lib/jwt_authentication/entity.rb b/lib/jwt_authentication/entity.rb index 561a3e5..d2620c9 100644 --- a/lib/jwt_authentication/entity.rb +++ b/lib/jwt_authentication/entity.rb @@ -25,14 +25,26 @@ def token_param_name(controller) controller.jwt_models[name_underscore.to_sym][:param_name] || "#{name_underscore}_token" end - def get_token_from_params_or_headers(controller) - (controller.request.headers[token_header_name(controller)] || controller.params[token_param_name(controller)]).to_s + def token_cookie_name(controller) + controller.jwt_models[name_underscore.to_sym][:cookie_name] + end + + def cookie_enabled?(controller) + token_cookie_name(controller).present? + end + + def get_token_from_cookie(controller) + cookie_enabled?(controller) ? controller.send(:cookies).signed[token_cookie_name(controller)] : nil + end + + def get_token(controller) + (get_token_from_cookie(controller) || controller.request.headers[token_header_name(controller)] || controller.params[token_param_name(controller)]).to_s end def get_entity(controller) begin - token = get_token_from_params_or_headers controller - payload = JWT.decode(token, nil, false)[0] # get payload; decode can riase: JWT::DecodeError + token = get_token controller + payload = JWT.decode(token, nil, false)[0] # get payload; decode can raise: JWT::DecodeError keys = model.jwt_key_fields.inject({}) do |hash, field| hash[field] = payload[name_underscore][field.to_s] hash diff --git a/lib/jwt_authentication/jwt_authentication_handler.rb b/lib/jwt_authentication/jwt_authentication_handler.rb index 7cf7f5a..ba2f315 100644 --- a/lib/jwt_authentication/jwt_authentication_handler.rb +++ b/lib/jwt_authentication/jwt_authentication_handler.rb @@ -15,6 +15,8 @@ module JwtAuthenticationHandler private :authenticate_entity_by_jwt! private :sign_in_handler private :raise_error! + private :set_jwt_to_cookie + end def authenticate_entity_by_jwt!(entity) @@ -33,6 +35,17 @@ def raise_error! raise JwtAuthentication::NotAuthenticated.new('Not authenticated') end + def set_jwt_to_cookie(entity, token, expires) + return unless entity.cookie_enabled? self + + cookie_name = entity.token_cookie_name(self) + if JwtAuthentication.jwt_timeout_verify + cookies.signed[cookie_name] = {value: token, expires: expires} + else + cookies.permanent.signed[cookie_name] = token + end + end + def valid_entity_name?(entity) jwt_models.has_key? entity.name_underscore.to_sym end @@ -96,6 +109,12 @@ def define_jwt_authentication_helpers_for(entity) raise_error! unless authenticate_entity_by_jwt!(_entity) end.call(entity) end + + define_method "set_jwt_cookie_for_#{entity.name_underscore}".to_sym do |token, expires| + lambda do |_entity| + set_jwt_to_cookie(_entity, token, expires) + end.call(entity) + end end end end