Class: RackJwtAegis::Middleware
- Inherits:
-
Object
- Object
- RackJwtAegis::Middleware
- Includes:
- DebugLogger
- Defined in:
- lib/rack_jwt_aegis/middleware.rb
Overview
Main Rack middleware for JWT authentication and authorization
This middleware handles the complete JWT authentication flow including:
- JWT token extraction and validation
- Multi-tenant validation (subdomain/pathname slug)
- RBAC permission checking
- Custom payload validation
- Request context setting
Instance Method Summary collapse
-
#call(env) ⇒ Array
Process the Rack request.
-
#enabled_features ⇒ String
private
Generate a string describing enabled features for logging.
-
#extract_jwt_token(request) ⇒ String
private
Extract JWT token from the Authorization header.
-
#extract_user_roles(payload) ⇒ Array
private
Extract user roles from JWT payload for RBAC authorization.
-
#initialize(app, options = {}) ⇒ Middleware
constructor
Initialize the middleware.
-
#multi_tenant_enabled? ⇒ Boolean
private
Check if multi-tenant validation is enabled.
Methods included from DebugLogger
Constructor Details
#initialize(app, options = {}) ⇒ Middleware
Initialize the middleware
36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/rack_jwt_aegis/middleware.rb', line 36 def initialize(app, = {}) @app = app @config = Configuration.new() # Initialize components @jwt_validator = JwtValidator.new(@config) @multi_tenant_validator = MultiTenantValidator.new(@config) if multi_tenant_enabled? @rbac_manager = RbacManager.new(@config) if @config.rbac_enabled? @response_builder = ResponseBuilder.new(@config) @request_context = RequestContext.new(@config) debug_log("Middleware initialized with features: #{enabled_features}") end |
Instance Method Details
#call(env) ⇒ Array
Process the Rack request
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 112 113 114 115 116 117 118 119 |
# File 'lib/rack_jwt_aegis/middleware.rb', line 56 def call(env) request = Rack::Request.new(env) debug_log("Processing request: #{request.request_method} #{request.path}") # Step 1: Check if path should be skipped if @config.skip_path?(request.path) debug_log("Skipping authentication for path: #{request.path}") return @app.call(env) end begin # Step 2: Extract and validate JWT token token = extract_jwt_token(request) payload = @jwt_validator.validate(token) debug_log("JWT validation successful for user: #{payload[@config.payload_key(:user_id).to_s]}") # Step 3: Multi-tenant validation (if enabled) if multi_tenant_enabled? @multi_tenant_validator.validate(request, payload) debug_log('Multi-tenant validation successful') end # Step 4: RBAC permission check (if enabled) if @config.rbac_enabled? # Extract and store user roles in request environment for RBAC manager user_roles = extract_user_roles(payload) request.env['rack_jwt_aegis.user_roles'] = user_roles @rbac_manager.(request, payload) debug_log('RBAC authorization successful') end # Step 5: Custom payload validation (if configured) if @config.custom_payload_validator unless @config.custom_payload_validator.call(payload, request) debug_log('Custom payload validation failed') raise AuthorizationError, 'Custom validation failed' end debug_log('Custom payload validation successful') end # Step 6: Set request context for application @request_context.set_context(env, payload) debug_log('Request context set successfully') # Continue to application @app.call(env) rescue AuthenticationError => e debug_log("Authentication failed: #{e.}") @response_builder.(e.) rescue AuthorizationError => e debug_log("Authorization failed: #{e.}") @response_builder.forbidden_response(e.) rescue StandardError => e debug_log("Unexpected error: #{e.}") if @config.debug_mode? @response_builder.error_response("Internal error: #{e.}", 500) else @response_builder.error_response('Internal server error', 500) end end end |
#enabled_features ⇒ String (private)
Generate a string describing enabled features for logging
153 154 155 156 157 158 159 160 |
# File 'lib/rack_jwt_aegis/middleware.rb', line 153 def enabled_features features = ['JWT'] features << 'TenantId' if @config.validate_tenant_id? features << 'Subdomain' if @config.validate_subdomain? features << 'PathnameSlug' if @config.validate_pathname_slug? features << 'RBAC' if @config.rbac_enabled? features.join(', ') end |
#extract_jwt_token(request) ⇒ String (private)
Extract JWT token from the Authorization header
128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/rack_jwt_aegis/middleware.rb', line 128 def extract_jwt_token(request) auth_header = request.get_header('HTTP_AUTHORIZATION') raise AuthenticationError, 'Authorization header missing' if auth_header.nil? || auth_header.empty? # Extract Bearer token match = auth_header.match(/\ABearer\s+(.+)\z/) raise AuthenticationError, 'Invalid authorization header format' if match.nil? token = match[1] raise AuthenticationError, 'JWT token missing' if token.nil? || token.empty? token end |
#extract_user_roles(payload) ⇒ Array (private)
Extract user roles from JWT payload for RBAC authorization
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/rack_jwt_aegis/middleware.rb', line 166 def extract_user_roles(payload) # Use configured payload mapping for role_ids, with fallback to common field names role_key = @config.payload_key(:role_ids).to_s roles = payload[role_key] # If mapped key doesn't exist, try common fallback field names roles = payload['roles'] || payload['role'] || payload['user_roles'] || payload['role_ids'] if roles.nil? case roles when Array roles.map(&:to_s) # Ensure all roles are strings for consistent lookup when String, Integer [roles.to_s] # Single role as array else debug_log("Warning: No valid roles found in JWT payload. Looking for '#{role_key}' field. \ Available fields: #{payload.keys}".squeeze) [] end end |
#multi_tenant_enabled? ⇒ Boolean (private)
Check if multi-tenant validation is enabled
146 147 148 |
# File 'lib/rack_jwt_aegis/middleware.rb', line 146 def multi_tenant_enabled? @config.validate_tenant_id? || @config.validate_subdomain? || @config.validate_pathname_slug? end |