Class: JwtAuthCognito::JwtValidator
- Inherits:
-
Object
- Object
- JwtAuthCognito::JwtValidator
- Defined in:
- lib/jwt_auth_cognito/jwt_validator.rb
Class Method Summary collapse
-
.create_cognito_validator(config = nil) ⇒ Object
Create a convenience factory method.
Instance Method Summary collapse
-
#calculate_secret_hash(identifier) ⇒ Object
Calculate secret hash for Cognito operations (when client secret is configured).
-
#check_permissions(user_id, app_id, org_id, permissions_to_check) ⇒ Object
Bulk permission check.
-
#check_resource_permission?(user_id, app_id, org_id, resource_id, permission) ⇒ Boolean
Verify if the user has a specific permission over a concrete resource instance.
- #decode_token(token) ⇒ Object
- #extract_api_key_from_header(api_key_header) ⇒ Object
- #extract_api_key_from_headers(headers) ⇒ Object
-
#extract_token_from_header(authorization_header) ⇒ Object
Utility methods inspired by Node.js package.
-
#get_resource_permissions(user_id, app_id, org_id, resource_type, resource_id) ⇒ Object
Get the permissions the user has over a specific resource instance (ReBAC).
- #get_time_to_expiry(token) ⇒ Object
- #get_token_info(token) ⇒ Object
-
#has_client_secret? ⇒ Boolean
Check if client secret is configured.
-
#has_permission?(user_id, app_id, org_id, permission) ⇒ Boolean
Returns true if the user has the given permission in the specified app/org context.
-
#has_permission_from_token?(token, app_id, org_id, permission) ⇒ Boolean
Validates a Bearer token and checks a single permission in one call.
-
#initialize(config = JwtAuthCognito.configuration) ⇒ JwtValidator
constructor
A new instance of JwtValidator.
- #initialize! ⇒ Object
- #is_token_expired?(token) ⇒ Boolean
-
#resolve_effective_permissions_for(user_id, app_id, org_id) ⇒ Object
Exposes effective permissions via the user data service.
- #revoke_token(token, user_id: nil) ⇒ Object
- #revoke_user_tokens(user_id) ⇒ Object
-
#validate(token, options = {}) ⇒ Object
Main validation method - use this for most cases Intelligently validates tokens with all features: - JWT validation (basic or secure) - API key validation (if provided) - Blacklist checking - Automatic appId verification - User data enrichment (if enabled).
- #validate_access_token(token) ⇒ Object
-
#validate_enriched(token, api_key = nil, options = {}) ⇒ Object
Get enriched validation (user data included) Use this when you need user permissions, organizations, apps.
- #validate_id_token(token) ⇒ Object
- #validate_multiple_tokens(tokens) ⇒ Object
-
#validate_token(token, options = {}) ⇒ Object
Quick validation for simple use cases Just validates the JWT token (includes blacklist check).
-
#validate_with_api_key(token, api_key, options = {}) ⇒ Object
Validate with API key (automatic appId verification) Use this when you have an API key and want automatic security.
-
#validate_with_app_access(token, api_key, options = {}) ⇒ Object
Validate with strict appId requirement Use this when you MUST ensure user has access to a specific app.
Constructor Details
#initialize(config = JwtAuthCognito.configuration) ⇒ JwtValidator
Returns a new instance of JwtValidator.
7 8 9 10 11 12 13 14 15 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 7 def initialize(config = JwtAuthCognito.configuration) @config = config @jwks_service = JwksService.new(config) @blacklist_service = TokenBlacklistService.new(config) @api_key_validator = config.enable_api_key_validation ? ApiKeyValidator.new(config) : nil @user_data_service = config.enable_user_data_retrieval ? UserDataService.new(nil, config.user_data_config) : nil @cognito_identity_service = config.enable_user_identity_enrichment ? CognitoIdentityService.new(config) : nil @initialized = false end |
Class Method Details
.create_cognito_validator(config = nil) ⇒ Object
Create a convenience factory method
330 331 332 333 334 335 336 337 338 339 340 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 330 def self.create_cognito_validator(config = nil) if config old_config = JwtAuthCognito.configuration JwtAuthCognito.configure { |_c| config } validator = new JwtAuthCognito.instance_variable_set(:@configuration, old_config) validator else new end end |
Instance Method Details
#calculate_secret_hash(identifier) ⇒ Object
Calculate secret hash for Cognito operations (when client secret is configured)
231 232 233 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 231 def calculate_secret_hash(identifier) @config.calculate_secret_hash(identifier) end |
#check_permissions(user_id, app_id, org_id, permissions_to_check) ⇒ Object
Bulk permission check. Returns a hash with :allowed, :denied, :has_all, :has_any.
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 251 def (user_id, app_id, org_id, ) effective = (user_id, app_id, org_id) || [] allowed = [] denied = [] Array().each do |perm| if PermissionChecker.(perm, effective) allowed << perm else denied << perm end end { allowed: allowed, denied: denied, has_all: denied.empty?, has_any: allowed.any? } end |
#check_resource_permission?(user_id, app_id, org_id, resource_id, permission) ⇒ Boolean
Verify if the user has a specific permission over a concrete resource instance.
299 300 301 302 303 304 305 306 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 299 def (user_id, app_id, org_id, resource_id, ) return false unless @user_data_service result = @user_data_service.(user_id, app_id, org_id, resource_id) return false unless result PermissionChecker.(, result[:permissions]) end |
#decode_token(token) ⇒ Object
206 207 208 209 210 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 206 def decode_token(token) JWT.decode(token, nil, false).first rescue JWT::DecodeError => e { error: "Failed to decode token: #{e.}" } end |
#extract_api_key_from_header(api_key_header) ⇒ Object
183 184 185 186 187 188 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 183 def extract_api_key_from_header(api_key_header) # Support common API key header formats return nil unless api_key_header api_key_header.strip end |
#extract_api_key_from_headers(headers) ⇒ Object
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 190 def extract_api_key_from_headers(headers) # Check various common API key header names (case insensitive) api_key_headers = %w[x-api-key X-API-Key X-API-KEY X-Api-Key] api_key_headers.each do |header_name| # Convert headers to a case-insensitive hash for lookup header_key = headers.keys.find { |key| key.downcase == header_name.downcase } next unless header_key value = headers[header_key] return extract_api_key_from_header(value) if value end nil end |
#extract_token_from_header(authorization_header) ⇒ Object
Utility methods inspired by Node.js package
176 177 178 179 180 181 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 176 def extract_token_from_header() return nil unless match = .match(/\ABearer (.+)\z/) match ? match[1] : nil end |
#get_resource_permissions(user_id, app_id, org_id, resource_type, resource_id) ⇒ Object
Get the permissions the user has over a specific resource instance (ReBAC). Returns { resource_type:, resource_id:, permissions:, mode: } or nil.
288 289 290 291 292 293 294 295 296 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 288 def (user_id, app_id, org_id, resource_type, resource_id) return nil unless @user_data_service result = @user_data_service.(user_id, app_id, org_id, resource_id) return nil unless result { resource_type: resource_type, resource_id: resource_id, permissions: result[:permissions], mode: result[:mode] } end |
#get_time_to_expiry(token) ⇒ Object
318 319 320 321 322 323 324 325 326 327 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 318 def get_time_to_expiry(token) payload = decode_token(token) return nil if payload.is_a?(Hash) && payload[:error] exp = payload['exp'] return nil unless exp seconds = exp - Time.now.to_i seconds.positive? ? seconds : 0 end |
#get_token_info(token) ⇒ Object
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 212 def get_token_info(token) payload = decode_token(token) return payload if payload.is_a?(Hash) && payload[:error] { sub: payload['sub'], username: payload['cognito:username'] || payload['username'], email: payload['email'], token_use: payload['token_use'], client_id: payload['aud'], issued_at: payload['iat'] ? Time.at(payload['iat']) : nil, expires_at: payload['exp'] ? Time.at(payload['exp']) : nil, not_before: payload['nbf'] ? Time.at(payload['nbf']) : nil, jti: payload['jti'], has_client_secret: @config.has_client_secret? } end |
#has_client_secret? ⇒ Boolean
Check if client secret is configured
236 237 238 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 236 def has_client_secret? @config.has_client_secret? end |
#has_permission?(user_id, app_id, org_id, permission) ⇒ Boolean
Returns true if the user has the given permission in the specified app/org context.
243 244 245 246 247 248 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 243 def (user_id, app_id, org_id, ) = (user_id, app_id, org_id) return false unless PermissionChecker.(, ) end |
#has_permission_from_token?(token, app_id, org_id, permission) ⇒ Boolean
Validates a Bearer token and checks a single permission in one call.
269 270 271 272 273 274 275 276 277 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 269 def (token, app_id, org_id, ) result = validate_token(token) return false unless result[:valid] user_id = result[:payload]&.dig('sub') return false unless user_id (user_id, app_id, org_id, ) end |
#initialize! ⇒ Object
17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 17 def initialize! return if @initialized begin @jwks_service.initialize! @blacklist_service.initialize! if @blacklist_service.respond_to?(:initialize!) @user_data_service&.initialize! @initialized = true rescue StandardError => e ErrorUtils.log_error(e, 'JWT Validator initialization failed') raise JwtAuthCognito::ConfigurationError, ErrorUtils::JWT_ERROR_MESSAGES['INITIALIZATION_FAILED'] end end |
#is_token_expired?(token) ⇒ Boolean
308 309 310 311 312 313 314 315 316 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 308 def is_token_expired?(token) payload = decode_token(token) return true if payload.is_a?(Hash) && payload[:error] exp = payload['exp'] return false unless exp Time.now.to_i >= exp end |
#resolve_effective_permissions_for(user_id, app_id, org_id) ⇒ Object
Exposes effective permissions via the user data service.
280 281 282 283 284 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 280 def (user_id, app_id, org_id) return nil unless @user_data_service @user_data_service.(user_id, app_id, org_id) end |
#revoke_token(token, user_id: nil) ⇒ Object
167 168 169 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 167 def revoke_token(token, user_id: nil) @blacklist_service.add_to_blacklist(token, user_id: user_id) end |
#revoke_user_tokens(user_id) ⇒ Object
171 172 173 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 171 def revoke_user_tokens(user_id) @blacklist_service.invalidate_user_tokens(user_id) end |
#validate(token, options = {}) ⇒ Object
Main validation method - use this for most cases Intelligently validates tokens with all features:
-
JWT validation (basic or secure)
-
API key validation (if provided)
-
Blacklist checking
-
Automatic appId verification
-
User data enrichment (if enabled)
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 40 def validate(token, = {}) @config.validate! api_key = [:api_key] force_secure = [:force_secure] || false enrich_user_data = .fetch(:enrich_user_data, true) require_app_access = [:require_app_access] || false # Step 1: Validate API key if provided api_key_data = nil if api_key && @config.enable_api_key_validation && @api_key_validator api_key_result = @api_key_validator.validate_api_key(api_key) return { valid: false, error: api_key_result[:error] || 'API key validation failed' } unless api_key_result[:valid] api_key_data = api_key_result[:key_data] end # Step 2: Check blacklist first return { valid: false, error: 'Token has been revoked' } if @blacklist_service.is_blacklisted?(token) # Step 3: Validate JWT token validation_mode = force_secure ? :secure : @config.validation_mode token_result = case validation_mode when :secure validate_token_secure(token, ) when :basic validate_token_basic(token, ) else raise ConfigurationError, "Invalid validation_mode: #{validation_mode}" end return token_result unless token_result[:valid] && token_result[:payload] # Step 4: Verify appId access if API key has one if api_key_data app_validation = verify_app_access(api_key_data, token_result[:payload], require_app_access) return app_validation unless app_validation[:valid] end # Step 5: Enrich with user data if requested enriched_result = token_result.dup enriched_result[:api_key] = api_key_data if api_key_data if enrich_user_data && @config.enable_user_data_retrieval && @user_data_service user_id = token_result[:payload]['sub'] if user_id begin user_data = @user_data_service.get_comprehensive_user_data(user_id) enriched_result[:user_permissions] = user_data['permissions'] enriched_result[:user_organizations] = user_data['organizations'] enriched_result[:applications] = user_data['applications'] rescue StandardError => e ErrorUtils.log_error(e, 'User data retrieval failed') # Continue with basic validation if user data service fails end end end # Step 6: Enrich token claims with Cognito identity attributes (email, name, ...) # Cognito ACCESS tokens do NOT carry email (only ID tokens do). Fetch the user's # attributes via GetUser (authorized by the access token's own scope) and merge # them into the payload so consumers get payload['email'] transparently. if enrich_user_data && @config.enable_user_identity_enrichment && @cognito_identity_service payload = enriched_result[:payload] if payload && payload['token_use'] == 'access' && payload['email'].nil? attrs = @cognito_identity_service.get_identity_attributes(token, payload['sub']) apply_identity_attributes(payload, attrs) if attrs end end enriched_result end |
#validate_access_token(token) ⇒ Object
147 148 149 150 151 152 153 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 147 def validate_access_token(token) result = validate_token(token) return { valid: false, error: 'Token is not an access token' } if result[:valid] && result[:payload]['token_use'] != 'access' result end |
#validate_enriched(token, api_key = nil, options = {}) ⇒ Object
Get enriched validation (user data included) Use this when you need user permissions, organizations, apps
143 144 145 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 143 def validate_enriched(token, api_key = nil, = {}) validate(token, .merge(api_key: api_key, enrich_user_data: true)) end |
#validate_id_token(token) ⇒ Object
155 156 157 158 159 160 161 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 155 def validate_id_token(token) result = validate_token(token) return { valid: false, error: 'Token is not an ID token' } if result[:valid] && result[:payload]['token_use'] != 'id' result end |
#validate_multiple_tokens(tokens) ⇒ Object
163 164 165 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 163 def validate_multiple_tokens(tokens) tokens.map { |token| validate_token(token) } end |
#validate_token(token, options = {}) ⇒ Object
Quick validation for simple use cases Just validates the JWT token (includes blacklist check)
117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 117 def validate_token(token, = {}) result = validate(token, .merge(enrich_user_data: false)) { valid: result[:valid], payload: result[:payload], sub: result[:sub], username: result[:username], token_use: result[:token_use], error: result[:error] } end |
#validate_with_api_key(token, api_key, options = {}) ⇒ Object
Validate with API key (automatic appId verification) Use this when you have an API key and want automatic security
131 132 133 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 131 def validate_with_api_key(token, api_key, = {}) validate(token, .merge(api_key: api_key)) end |
#validate_with_app_access(token, api_key, options = {}) ⇒ Object
Validate with strict appId requirement Use this when you MUST ensure user has access to a specific app
137 138 139 |
# File 'lib/jwt_auth_cognito/jwt_validator.rb', line 137 def validate_with_app_access(token, api_key, = {}) validate(token, .merge(api_key: api_key, require_app_access: true)) end |