Module: ApiGuard::JwtAuth::JsonWebToken
- Included in:
- Test::ControllerHelper
- Defined in:
- lib/api_guard/jwt_auth/json_web_token.rb
Overview
Common module for JWT operations
Class Method Summary collapse
-
.authenticate_and_set_resource(resource_name) ⇒ Object
Authenticate the JWT token and set resource.
-
.authenticate_token ⇒ Object
Authenticate the resource with the ‘{resource_name}_id’ in the decoded JWT token and also, check for valid issued at time and not blacklisted.
-
.blacklist_token ⇒ Object
Blacklist the current JWT token from future access.
-
.blacklisted?(resource) ⇒ Boolean
Returns whether the JWT token is blacklisted or not.
-
.blacklisted_token_association(resource) ⇒ Object
blacklisted ======================================================.
- .blacklisted_tokens_for(resource) ⇒ Object
-
.create_token_and_set_header(resource, resource_name) ⇒ Object
Create tokens and set response headers.
- .current_resource ⇒ Object
- .current_time ⇒ Object
-
.decode(token, verify = true) ⇒ Object
Decode the JWT token and return the payload.
-
.decode_token ⇒ Object
Decode the JWT token and don’t verify token expiry for refresh token API request.
-
.define_current_resource_accessors(resource) ⇒ Object
Defines “current_{resource_name}” method and “@current_{resource_name}” instance variable that returns “resource” value.
- .destroy_all_refresh_tokens(resource) ⇒ Object
-
.encode(payload) ⇒ Object
Encode the payload with the secret key and return the JWT token.
- .find_refresh_token_of(resource, refresh_token) ⇒ Object
- .find_resource_from_token(resource_class) ⇒ Object
-
.invalidate_old_jwt_tokens(resource) ⇒ Object
Set token issued at to current timestamp to restrict access to old access(JWT) tokens.
-
.jwt_and_refresh_token(resource, resource_name, expired_token = false) ⇒ Object
Create a JWT token with resource detail in payload.
- .method_missing(name, *args) ⇒ Object
-
.new_refresh_token(resource) ⇒ Object
Create a new refresh_token for the current resource.
-
.refresh_token_association(resource) ⇒ Object
refresh token code=======================================.
- .refresh_token_enabled?(resource) ⇒ Boolean
- .refresh_tokens_for(resource) ⇒ Object
- .respond_to_missing?(method_name, include_private = false) ⇒ Boolean
-
.set_token_headers(token, refresh_token = nil) ⇒ Object
Set token details in response headers.
- .token_blacklisting_enabled?(resource) ⇒ Boolean
- .token_expire_at ⇒ Object
- .token_issued_at ⇒ Object
-
.uniq_refresh_token(resource) ⇒ Object
Generate and return unique refresh token for the resource.
-
.valid_issued_at?(resource) ⇒ Boolean
Returns whether the JWT token is issued after the last password change Returns true if password hasn’t changed by the user.
Class Method Details
.authenticate_and_set_resource(resource_name) ⇒ Object
Authenticate the JWT token and set resource
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 156 def self.authenticate_and_set_resource(resource_name) @resource_name = resource_name @token = request.headers['Authorization']&.split('Bearer ')&.last return render_error(401, message: I18n.t('api_guard.access_token.missing')) unless @token authenticate_token # Render error response only if no resource found and no previous render happened render_error(401, message: I18n.t('api_guard.access_token.invalid')) if !current_resource && !performed? rescue JWT::DecodeError => e if e. == 'Signature has expired' render_error(401, message: I18n.t('api_guard.access_token.expired')) else render_error(401, message: I18n.t('api_guard.access_token.invalid')) end end |
.authenticate_token ⇒ Object
Authenticate the resource with the ‘{resource_name}_id’ in the decoded JWT token and also, check for valid issued at time and not blacklisted
Also, set “current_{resource_name}” method and “@current_{resource_name}” instance variable for accessing the authenticated resource
204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 204 def self.authenticate_token return unless decode_token resource = find_resource_from_token(@resource_name.classify.constantize) if resource && valid_issued_at?(resource) && !blacklisted?(resource) define_current_resource_accessors(resource) else render_error(401, message: I18n.t('api_guard.access_token.invalid')) end end |
.blacklist_token ⇒ Object
Blacklist the current JWT token from future access
133 134 135 136 137 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 133 def self.blacklist_token return unless token_blacklisting_enabled?(current_resource) blacklisted_tokens_for(current_resource).create(token: @token, expire_at: Time.at(@decoded_token[:exp]).utc) end |
.blacklisted?(resource) ⇒ Boolean
Returns whether the JWT token is blacklisted or not
126 127 128 129 130 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 126 def self.blacklisted?(resource) return false unless token_blacklisting_enabled?(resource) blacklisted_tokens_for(resource).exists?(token: @token) end |
.blacklisted_token_association(resource) ⇒ Object
blacklisted ======================================================
112 113 114 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 112 def self.blacklisted_token_association(resource) resource.class.blacklisted_token_association end |
.blacklisted_tokens_for(resource) ⇒ Object
120 121 122 123 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 120 def self.blacklisted_tokens_for(resource) blacklisted_token_association = blacklisted_token_association(resource) resource.send(blacklisted_token_association) end |
.create_token_and_set_header(resource, resource_name) ⇒ Object
Create tokens and set response headers
51 52 53 54 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 51 def self.create_token_and_set_header(resource, resource_name) access_token, refresh_token = jwt_and_refresh_token(resource, resource_name) set_token_headers(access_token, refresh_token) end |
.current_resource ⇒ Object
223 224 225 226 227 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 223 def self.current_resource return unless respond_to?("current_#{@resource_name}") public_send("current_#{@resource_name}") end |
.current_time ⇒ Object
10 11 12 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 10 def self.current_time @current_time ||= Time.now.utc end |
.decode(token, verify = true) ⇒ Object
Decode the JWT token and return the payload
28 29 30 31 32 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 28 def self.decode(token, verify = true) HashWithIndifferentAccess.new( JWT.decode(token, ApiGuard.token_signing_secret, verify, verify_iat: true)[0] ) end |
.decode_token ⇒ Object
Decode the JWT token and don’t verify token expiry for refresh token API request
176 177 178 179 180 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 176 def self.decode_token # TODO: Set token refresh controller dynamic verify_token = (controller_name != 'tokens' || action_name != 'create') @decoded_token = decode(@token, verify_token) end |
.define_current_resource_accessors(resource) ⇒ Object
Defines “current_{resource_name}” method and “@current_{resource_name}” instance variable that returns “resource” value
192 193 194 195 196 197 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 192 def self.define_current_resource_accessors(resource) define_singleton_method("current_#{@resource_name}") do instance_variable_get("@current_#{@resource_name}") || instance_variable_set("@current_#{@resource_name}", resource) end end |
.destroy_all_refresh_tokens(resource) ⇒ Object
105 106 107 108 109 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 105 def self.destroy_all_refresh_tokens(resource) return unless refresh_token_enabled?(resource) refresh_tokens_for(resource).destroy_all end |
.encode(payload) ⇒ Object
Encode the payload with the secret key and return the JWT token
23 24 25 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 23 def self.encode(payload) JWT.encode(payload, ApiGuard.token_signing_secret) end |
.find_refresh_token_of(resource, refresh_token) ⇒ Object
86 87 88 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 86 def self.find_refresh_token_of(resource, refresh_token) refresh_tokens_for(resource).find_by_token(refresh_token) end |
.find_resource_from_token(resource_class) ⇒ Object
216 217 218 219 220 221 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 216 def self.find_resource_from_token(resource_class) resource_id = @decoded_token[:"#{@resource_name}_id"] return if resource_id.blank? resource_class.find_by(id: resource_id) end |
.invalidate_old_jwt_tokens(resource) ⇒ Object
Set token issued at to current timestamp to restrict access to old access(JWT) tokens
65 66 67 68 69 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 65 def self.invalidate_old_jwt_tokens(resource) return unless ApiGuard.invalidate_old_tokens_on_password_change resource.token_issued_at = Time.at(token_issued_at).utc end |
.jwt_and_refresh_token(resource, resource_name, expired_token = false) ⇒ Object
Create a JWT token with resource detail in payload. Also, create refresh token if enabled for the resource.
This creates expired JWT token if the argument ‘expired_token’ is true which can be used for testing.
38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 38 def self.jwt_and_refresh_token(resource, resource_name, expired_token = false) payload = { "#{resource_name}_id": resource.id, exp: expired_token ? token_issued_at : token_expire_at, iat: token_issued_at } # Add custom data in the JWT token payload payload.merge!(resource.jwt_token_payload) if resource.respond_to?(:jwt_token_payload) [self.encode(payload), self.new_refresh_token(resource)] end |
.method_missing(name, *args) ⇒ Object
140 141 142 143 144 145 146 147 148 149 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 140 def self.method_missing(name, *args) method_name = name.to_s if method_name.start_with?('authenticate_and_set_') resource_name = method_name.split('authenticate_and_set_')[1] authenticate_and_set_resource(resource_name) else super end end |
.new_refresh_token(resource) ⇒ Object
Create a new refresh_token for the current resource
99 100 101 102 103 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 99 def self.new_refresh_token(resource) return unless refresh_token_enabled?(resource) refresh_tokens_for(resource).create(token: uniq_refresh_token(resource)).token end |
.refresh_token_association(resource) ⇒ Object
refresh token code=======================================
73 74 75 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 73 def self.refresh_token_association(resource) resource.class.refresh_token_association end |
.refresh_token_enabled?(resource) ⇒ Boolean
77 78 79 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 77 def self.refresh_token_enabled?(resource) refresh_token_association(resource).present? end |
.refresh_tokens_for(resource) ⇒ Object
81 82 83 84 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 81 def self.refresh_tokens_for(resource) refresh_token_association = refresh_token_association(resource) resource.send(refresh_token_association) end |
.respond_to_missing?(method_name, include_private = false) ⇒ Boolean
151 152 153 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 151 def self.respond_to_missing?(method_name, include_private = false) method_name.to_s.start_with?('authenticate_and_set_') || super end |
.set_token_headers(token, refresh_token = nil) ⇒ Object
Set token details in response headers
57 58 59 60 61 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 57 def self.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 end |
.token_blacklisting_enabled?(resource) ⇒ Boolean
116 117 118 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 116 def self.token_blacklisting_enabled?(resource) blacklisted_token_association(resource).present? end |
.token_expire_at ⇒ Object
14 15 16 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 14 def self.token_expire_at @token_expire_at ||= (current_time + ApiGuard.token_validity).to_i end |
.token_issued_at ⇒ Object
18 19 20 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 18 def self.token_issued_at @token_issued_at ||= current_time.to_i end |
.uniq_refresh_token(resource) ⇒ Object
Generate and return unique refresh token for the resource
91 92 93 94 95 96 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 91 def self.uniq_refresh_token(resource) loop do random_token = SecureRandom.urlsafe_base64 return random_token unless refresh_tokens_for(resource).exists?(token: random_token) end end |
.valid_issued_at?(resource) ⇒ Boolean
Returns whether the JWT token is issued after the last password change Returns true if password hasn’t changed by the user
184 185 186 187 188 |
# File 'lib/api_guard/jwt_auth/json_web_token.rb', line 184 def self.valid_issued_at?(resource) return true unless ApiGuard.invalidate_old_tokens_on_password_change !resource.token_issued_at || @decoded_token[:iat] >= resource.token_issued_at.to_i end |