Module: CommandTower::Jwt::AuthorizationHelper

Defined in:
lib/command_tower/jwt/authorization_helper.rb

Constant Summary collapse

AUTHENTICATION_HEADER =
"Authorization"
AUTHENTICATION_EXPIRE_HEADER =
"X-Authorization-Expire"
AUTHENTICATION_WITH_RESET =
"X-Authorization-Reset"

Class Method Summary collapse

Class Method Details

Clears cookie by setting it to expire in the past

Parameters:

  • response (ActionDispatch::Response)

    The response object



109
110
111
112
113
114
115
116
117
# File 'lib/command_tower/jwt/authorization_helper.rb', line 109

def clear_cookie(response)
  return unless cookie_enabled?

  config = CommandTower.config.jwt.cookie
  options = cookie_options(expires_at: 1.year.ago)
  options[:value] = ""

  response.set_cookie(config.name, options)
end

.clear_token(response) ⇒ Object

Clears token from response (cookie only, headers are response-only)

Parameters:

  • response (ActionDispatch::Response)

    The response object



61
62
63
64
65
66
# File 'lib/command_tower/jwt/authorization_helper.rb', line 61

def clear_token(response)
  clear_cookie(response) if cookie_enabled?
  # Clear CSRF cookie on logout
  # Logout always clears CSRF cookie (not configurable - this is the only correct behavior)
  CommandTower::Jwt::CsrfHelper.clear_cookie(response) if CommandTower::Jwt::CsrfHelper.csrf_enabled?
end

Checks if cookie auth is enabled

Returns:

  • (Boolean)


131
132
133
# File 'lib/command_tower/jwt/authorization_helper.rb', line 131

def cookie_enabled?
  CommandTower.config.jwt.cookie.enabled?
end

Returns consistent cookie options hash (single source of truth)

Parameters:

  • expires_at (Time, ActiveSupport::TimeWithZone)

    Expiration time for the cookie

Returns:

  • (Hash)

    Cookie options hash with all attributes



80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/command_tower/jwt/authorization_helper.rb', line 80

def cookie_options(expires_at:)
  config = CommandTower.config.jwt.cookie
  secure_value = config.secure || Rails.env.production?
  options = {
    expires: expires_at,
    httponly: config.httponly,
    secure: secure_value,
    same_site: config.same_site,
    path: config.path
  }
  options[:domain] = config.domain if config.domain.present?
  options
end

.extract_token(request) ⇒ Hash

Validates and extracts token from Authorization header OR cookie (if enabled) Returns hash with :token and :source on success, or hash with :error and :message on failure

Parameters:

  • request (ActionDispatch::Request)

    The request object

Returns:

  • (Hash)

    Hash with :token and :source (:header, :cookie) on success, or :error and :message on failure



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/command_tower/jwt/authorization_helper.rb', line 16

def extract_token(request)
  # First try Authorization header
  raw_token = request.headers[AUTHENTICATION_HEADER]
  if raw_token && !raw_token.to_s.strip.empty?
    # Validate Bearer format with strict regex (case-insensitive)
    # Matches: \ABearer\s+(.+)\z
    # - \A: start of string
    # - Bearer: literal "Bearer" (case-insensitive with /i flag)
    # - \s+: one or more whitespace characters
    # - (.+): capture group for token (one or more characters)
    # - \z: end of string
    match = raw_token.match(/\ABearer\s+(.+)\z/i)
    if match && match[1] && !match[1].strip.empty?
      token = match[1].strip
      return { token:, source: :header }
    else
      return { error: :invalid_format, message: "Invalid Bearer token format" }
    end
  end

  # Fallback to cookie if header is missing/empty AND cookie enabled
  if cookie_enabled?
    cookie_token = read_cookie(request)
    return { token: cookie_token, source: :cookie } if cookie_token && !cookie_token.strip.empty?
  end

  # No token found
  { error: :missing, message: "Bearer token missing" }
end

.get_with_reset_flag(request) ⇒ Boolean?

Gets the with_reset flag from request headers

Parameters:

  • request (ActionDispatch::Request)

    The request object

Returns:

  • (Boolean, nil)

    The boolean value or nil



122
123
124
125
126
127
# File 'lib/command_tower/jwt/authorization_helper.rb', line 122

def get_with_reset_flag(request)
  value = request.headers[AUTHENTICATION_WITH_RESET]
  return nil unless [true, false, "true", "false", "0", "1", 0, 1].include?(value)

  ActiveModel::Type::Boolean.new.cast(value)
end

Reads cookie value from request

Parameters:

  • request (ActionDispatch::Request)

    The request object

Returns:

  • (String, nil)

    Cookie value or nil



71
72
73
74
75
# File 'lib/command_tower/jwt/authorization_helper.rb', line 71

def read_cookie(request)
  return nil unless cookie_enabled?

  request.cookies[CommandTower.config.jwt.cookie.name.downcase]
end

Sets cookie with configured options

Parameters:

  • response (ActionDispatch::Response)

    The response object

  • token (String)

    The JWT token to set



97
98
99
100
101
102
103
104
105
# File 'lib/command_tower/jwt/authorization_helper.rb', line 97

def set_cookie(response, token)
  return unless cookie_enabled?

  config = CommandTower.config.jwt.cookie
  options = cookie_options(expires_at: config.ttl.from_now)
  options[:value] = token

  response.set_cookie(config.name, options)
end

.set_token(response, token, expires_at: nil) ⇒ Object

Sets token in response (headers and optionally cookie)

Parameters:

  • response (ActionDispatch::Response)

    The response object

  • token (String)

    The JWT token to set

  • expires_at (String, nil) (defaults to: nil)

    Optional expiration time string



50
51
52
53
54
55
56
57
# File 'lib/command_tower/jwt/authorization_helper.rb', line 50

def set_token(response, token, expires_at: nil)
  # Always set headers
  response.set_header(AUTHENTICATION_WITH_RESET, token)
  response.set_header(AUTHENTICATION_EXPIRE_HEADER, expires_at) if expires_at && !expires_at.to_s.strip.empty?

  # Set cookie if enabled
  set_cookie(response, token) if cookie_enabled?
end