diff --git a/app/controllers/api_guard/authentication_controller.rb b/app/controllers/api_guard/authentication_controller.rb index 5bb66b2..d1e74d5 100644 --- a/app/controllers/api_guard/authentication_controller.rb +++ b/app/controllers/api_guard/authentication_controller.rb @@ -9,8 +9,8 @@ class AuthenticationController < ApplicationController def create if resource.authenticate(params[:password]) - create_token_and_set_header(resource, resource_name) - render_success(message: I18n.t('api_guard.authentication.signed_in')) + create_and_set_token_pair(resource, resource_name) + render_success(data: resource, message: I18n.t('api_guard.authentication.signed_in')) else render_error(422, message: I18n.t('api_guard.authentication.invalid_login_credentials')) end @@ -18,6 +18,7 @@ def create def destroy blacklist_token + remove_tokens_from_cookies render_success(message: I18n.t('api_guard.authentication.signed_out')) end diff --git a/app/controllers/api_guard/passwords_controller.rb b/app/controllers/api_guard/passwords_controller.rb index 2b82830..4c2b5e7 100644 --- a/app/controllers/api_guard/passwords_controller.rb +++ b/app/controllers/api_guard/passwords_controller.rb @@ -13,7 +13,7 @@ def update blacklist_token unless ApiGuard.invalidate_old_tokens_on_password_change destroy_all_refresh_tokens(current_resource) - create_token_and_set_header(current_resource, resource_name) + create_and_set_token_pair(current_resource, resource_name) render_success(message: I18n.t('api_guard.password.changed')) else render_error(422, object: current_resource) diff --git a/app/controllers/api_guard/registration_controller.rb b/app/controllers/api_guard/registration_controller.rb index 56f4f69..2d71264 100644 --- a/app/controllers/api_guard/registration_controller.rb +++ b/app/controllers/api_guard/registration_controller.rb @@ -9,8 +9,8 @@ class RegistrationController < ApplicationController def create init_resource(sign_up_params) if resource.save - create_token_and_set_header(resource, resource_name) - render_success(message: I18n.t('api_guard.registration.signed_up')) + create_and_set_token_pair(resource, resource_name) + render_success(data: resource, message: I18n.t('api_guard.registration.signed_up')) else render_error(422, object: resource) end @@ -18,7 +18,8 @@ def create def destroy current_resource.destroy - render_success(message: I18n.t('api_guard.registration.account_deleted')) + remove_tokens_from_cookies + render_success(data: nil, message: I18n.t('api_guard.registration.account_deleted')) end private diff --git a/app/controllers/api_guard/tokens_controller.rb b/app/controllers/api_guard/tokens_controller.rb index 8c621fe..1f728ef 100644 --- a/app/controllers/api_guard/tokens_controller.rb +++ b/app/controllers/api_guard/tokens_controller.rb @@ -8,7 +8,7 @@ class TokensController < ApplicationController before_action :find_refresh_token, only: [:create] def create - create_token_and_set_header(current_resource, resource_name) + create_and_set_token_pair(current_resource, resource_name) @refresh_token.destroy blacklist_token if ApiGuard.blacklist_token_after_refreshing @@ -19,10 +19,14 @@ def create private def find_refresh_token - refresh_token_from_header = request.headers['Refresh-Token'] - - if refresh_token_from_header - @refresh_token = find_refresh_token_of(current_resource, refresh_token_from_header) + refresh_token_from_request = if ApiGuard.enable_tokens_in_cookies + request.cookies['refresh_token'] + else + request.headers['Refresh-Token'] + end + + if refresh_token_from_request + @refresh_token = find_refresh_token_of(current_resource, refresh_token_from_request) return render_error(401, message: I18n.t('api_guard.refresh_token.invalid')) unless @refresh_token else render_error(401, message: I18n.t('api_guard.refresh_token.missing')) diff --git a/lib/api_guard.rb b/lib/api_guard.rb index 043b0cd..5c4ebec 100644 --- a/lib/api_guard.rb +++ b/lib/api_guard.rb @@ -33,6 +33,9 @@ module Test {} end + mattr_accessor :enable_tokens_in_cookies + self.enable_tokens_in_cookies = false + def self.setup yield self end diff --git a/lib/api_guard/jwt_auth/authentication.rb b/lib/api_guard/jwt_auth/authentication.rb index 0593f07..8b792e7 100644 --- a/lib/api_guard/jwt_auth/authentication.rb +++ b/lib/api_guard/jwt_auth/authentication.rb @@ -24,7 +24,12 @@ def respond_to_missing?(method_name, include_private = false) def authenticate_and_set_resources(resource_names) @resource_names = resource_names - @token = request.headers['Authorization']&.split('Bearer ')&.last + @token = if ApiGuard.enable_tokens_in_cookies + request.cookies['access_token'] + else + request.headers['Authorization']&.split('Bearer ')&.last + end + return render_error(401, message: I18n.t('api_guard.access_token.missing')) unless @token authenticate_token diff --git a/lib/api_guard/jwt_auth/json_web_token.rb b/lib/api_guard/jwt_auth/json_web_token.rb index 0af33f9..71889a0 100644 --- a/lib/api_guard/jwt_auth/json_web_token.rb +++ b/lib/api_guard/jwt_auth/json_web_token.rb @@ -10,8 +10,12 @@ def current_time @current_time ||= Time.now.utc end - def token_expire_at - @token_expire_at ||= (current_time + ApiGuard.token_validity).to_i + def access_token_expire_at + @token_expire_at ||= (current_time + ApiGuard.token_validity) + end + + def refresh_token_expire_at + @refresh_token_expire_at_date ||= (Time.now.utc + ApiGuard.refresh_token_validity) end def token_issued_at @@ -38,7 +42,7 @@ def decode(token, verify = true) def jwt_and_refresh_token(resource, resource_name, expired_token = false, expired_refresh_token = false) payload = { "#{resource_name}_id": resource.id, - exp: expired_token ? token_issued_at : token_expire_at, + exp: expired_token ? token_issued_at : access_token_expire_at.to_i, iat: token_issued_at } @@ -48,17 +52,58 @@ def jwt_and_refresh_token(resource, resource_name, expired_token = false, expire [encode(payload), new_refresh_token(resource, expired_refresh_token)] end - # Create tokens and set response headers - def create_token_and_set_header(resource, resource_name) + # Create tokens and set response headers and cookies + def create_and_set_token_pair(resource, resource_name) access_token, refresh_token = jwt_and_refresh_token(resource, resource_name) - set_token_headers(access_token, refresh_token) + + if ApiGuard.enable_tokens_in_cookies + set_token_cookies(access_token, refresh_token) + else + set_token_headers(access_token, refresh_token) + end end # Set token details in response headers def set_token_headers(token, refresh_token = nil) response.headers['Access-Token'] = token response.headers['Refresh-Token'] = refresh_token if refresh_token - response.headers['Expire-At'] = token_expire_at.to_s + response.headers['Expire-At'] = access_token_expire_at.to_i.to_s + end + + def set_token_cookies(access_token, refresh_token) + response.set_cookie( + 'access_token', + { + value: access_token, + http_only: true, + expires: refresh_token_expire_at, + path: '/' + } + ) + response.set_cookie( + 'refresh_token', + { + value: refresh_token, + http_only: true, + expires: refresh_token_expire_at, + path: '/' + } + ) + end + + def remove_tokens_from_cookies + response.delete_cookie( + 'access_token', + { + path: '/' + } + ) + response.delete_cookie( + 'refresh_token', + { + path: '/' + } + ) end # Set token issued at to current timestamp