Class: RackJwtAegis::Middleware

Inherits:
Object
  • Object
show all
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

Examples:

Basic usage

use RackJwtAegis::Middleware, jwt_secret: ENV['JWT_SECRET']

Advanced usage

use RackJwtAegis::Middleware, {
  jwt_secret: ENV['JWT_SECRET'],
  validate_tenant_id: true,
  validate_pathname_slug: true,
  validate_subdomain: true,
  rbac_enabled: true,
  cache_store: :redis,
  skip_routes: [{ path: '/health' }, { path: '/api/v1/sessions', verbs: [:post] }]
}

Author:

  • Ken Camajalan Demanawa

Since:

  • 0.1.0

Instance Method Summary collapse

Methods included from DebugLogger

#debug_log

Constructor Details

#initialize(app, options = {}) ⇒ Middleware

Initialize the middleware

Parameters:

  • app (#call)

    the Rack application

  • options (Hash) (defaults to: {})

    configuration options (see Configuration#initialize)

Since:

  • 0.1.0



36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/rack_jwt_aegis/middleware.rb', line 36

def initialize(app, options = {})
  @app = app
  @config = Configuration.new(options)

  # 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)
  @circuit_breaker = build_circuit_breaker

  debug_log("Middleware initialized with features: #{enabled_features}")
end

Instance Method Details

#call(env) ⇒ Array

Process the Rack request

Parameters:

  • env (Hash)

    the Rack environment

Returns:

  • (Array)

    Rack response array [status, headers, body]

Raises:

Since:

  • 0.1.0



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
120
121
122
123
124
125
126
# File 'lib/rack_jwt_aegis/middleware.rb', line 57

def call(env)
  request = Rack::Request.new(env)

  debug_log("Processing request: #{request.request_method} #{request.path}")

  unless circuit_allows_request?
    debug_log('Circuit breaker open; failing fast')
    return @response_builder.error_response('Circuit breaker open', 503)
  end

  # Step 1: Check if route should be skipped
  if @config.skip_request?(request.path, request.request_method)
    debug_log("Skipping authentication for route: #{request.request_method} #{request.path}")
    return call_app(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.authorize(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
    call_app(env)
  rescue AuthenticationError => e
    debug_log("Authentication failed: #{e.message}")
    @response_builder.unauthorized_response(e.message)
  rescue AuthorizationError => e
    debug_log("Authorization failed: #{e.message}")
    @response_builder.forbidden_response(e.message)
  rescue StandardError => e
    record_circuit_failure
    debug_log("Unexpected error: #{e.message}")
    if @config.debug_mode?
      @response_builder.error_response("Internal error: #{e.message}", 500)
    else
      @response_builder.error_response('Internal server error', 500)
    end
  end
end