Module: CommandTower::Jwt::CsrfHelper
- Defined in:
- lib/command_tower/jwt/csrf_helper.rb
Class Method Summary collapse
-
.clear_cookie(response) ⇒ Object
Clear CSRF cookie.
-
.csrf_cookie_options(expires_at:) ⇒ Object
Get cookie options (single source of truth) Uses explicit nil checks (not ||) for true nil-coalescing of inheritable fields.
-
.csrf_enabled? ⇒ Boolean
Check if CSRF is enabled CSRF can only be enabled when cookie auth is also enabled.
-
.ensure_cookie(request, response, should_rotate: false) ⇒ Object
Ensure CSRF cookie exists (rotate-or-ensure model) - If should_rotate is true → generate + set new CSRF cookie - Else if existing cookie is blank → generate + set new CSRF cookie - Else do nothing (cookie already exists).
-
.generate_token ⇒ Object
Generate a new CSRF token.
-
.read_cookie(request) ⇒ Object
Read CSRF token from cookie.
-
.read_header(request) ⇒ Object
Read CSRF token from header.
-
.set_cookie(response, token) ⇒ Object
Set CSRF cookie in response.
-
.validate(request) ⇒ Object
Validate CSRF token (compare cookie to header) Uses constant-time comparison for security hardening.
Class Method Details
.clear_cookie(response) ⇒ Object
Clear CSRF cookie
27 28 29 30 31 32 33 34 35 36 |
# File 'lib/command_tower/jwt/csrf_helper.rb', line 27 def (response) return unless csrf_enabled? config = CommandTower.config.jwt..csrf = (expires_at: 1.year.ago) [:value] = "" [:max_age] = 0 response.(config., ) end |
.csrf_cookie_options(expires_at:) ⇒ Object
Get cookie options (single source of truth) Uses explicit nil checks (not ||) for true nil-coalescing of inheritable fields
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/command_tower/jwt/csrf_helper.rb', line 114 def (expires_at:) config = CommandTower.config.jwt..csrf jwt_config = CommandTower.config.jwt. # Boolean handling for secure (already fixed) secure_value = config.secure.nil? ? (jwt_config.secure.nil? ? Rails.env.production? : jwt_config.secure) : config.secure # Explicit nil checks for same_site, path, domain (not || to respect explicit false/nil) same_site_value = config.same_site.nil? ? jwt_config.same_site : config.same_site path_value = config.path.nil? ? jwt_config.path : config.path domain_value = config.domain.nil? ? jwt_config.domain : config.domain = { expires: expires_at, httponly: false, # CRITICAL: Must be readable by JavaScript (defined once) secure: secure_value, same_site: same_site_value, path: path_value } [:domain] = domain_value if domain_value.present? end |
.csrf_enabled? ⇒ Boolean
Check if CSRF is enabled CSRF can only be enabled when cookie auth is also enabled
108 109 110 |
# File 'lib/command_tower/jwt/csrf_helper.rb', line 108 def csrf_enabled? CommandTower::Jwt::AuthorizationHelper. && CommandTower.config.jwt..csrf.enabled? end |
.ensure_cookie(request, response, should_rotate: false) ⇒ Object
Ensure CSRF cookie exists (rotate-or-ensure model)
- If should_rotate is true → generate + set new CSRF cookie
- Else if existing cookie is blank → generate + set new CSRF cookie
- Else do nothing (cookie already exists)
42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/command_tower/jwt/csrf_helper.rb', line 42 def (request, response, should_rotate: false) return unless csrf_enabled? # Read cookie once into local variable = (request) = .nil? || .strip.empty? if should_rotate || # Generate and set new CSRF cookie token = generate_token (response, token) end # Else: cookie exists and we're not rotating, do nothing end |
.generate_token ⇒ Object
Generate a new CSRF token
11 12 13 |
# File 'lib/command_tower/jwt/csrf_helper.rb', line 11 def generate_token SecureRandom.hex(32) end |
.read_cookie(request) ⇒ Object
Read CSRF token from cookie
58 59 60 61 62 63 |
# File 'lib/command_tower/jwt/csrf_helper.rb', line 58 def (request) return nil unless csrf_enabled? config = CommandTower.config.jwt..csrf request.[config.] end |
.read_header(request) ⇒ Object
Read CSRF token from header
66 67 68 69 70 71 |
# File 'lib/command_tower/jwt/csrf_helper.rb', line 66 def read_header(request) return nil unless csrf_enabled? config = CommandTower.config.jwt..csrf request.headers[config.header_name] end |
.set_cookie(response, token) ⇒ Object
Set CSRF cookie in response
16 17 18 19 20 21 22 23 24 |
# File 'lib/command_tower/jwt/csrf_helper.rb', line 16 def (response, token) return unless csrf_enabled? config = CommandTower.config.jwt..csrf = (expires_at: config.ttl.from_now) [:value] = token response.(config., ) end |
.validate(request) ⇒ Object
Validate CSRF token (compare cookie to header) Uses constant-time comparison for security hardening
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 |
# File 'lib/command_tower/jwt/csrf_helper.rb', line 75 def validate(request) return { valid: true } unless csrf_enabled? # read_cookie and read_header also check csrf_enabled?, but that's fine for redundancy = (request) header_token = read_header(request) if .nil? || .strip.empty? return { valid: false, error: :csrf_missing, message: "csrf_missing" } end if header_token.nil? || header_token.strip.empty? return { valid: false, error: :csrf_mismatch, message: "csrf_missing" } end # Normalize tokens = .to_s.strip header_normalized = header_token.to_s.strip # Use constant-time comparison to prevent timing attacks if .length != header_normalized.length return { valid: false, error: :csrf_mismatch, message: "csrf_mismatch" } end unless ActiveSupport::SecurityUtils.secure_compare(, header_normalized) return { valid: false, error: :csrf_mismatch, message: "csrf_mismatch" } end { valid: true } end |