Module: Clavis::Security::CsrfProtection
- Defined in:
- lib/clavis/security/csrf_protection.rb
Constant Summary collapse
- STATE_HMAC_DELIMITER =
Delimiter used to separate state from HMAC
"::"
Class Method Summary collapse
-
.bind_state_to_session(controller, state) ⇒ String
Binds a state token to the session context for extra security.
-
.generate_nonce(length = 16) ⇒ String
Generates a nonce for OIDC requests.
-
.generate_state(length = 24) ⇒ String
Generates a secure random state token for CSRF protection.
-
.store_nonce_in_session(controller, expiry = nil, length = 16) ⇒ String
Stores a nonce in the Rails session.
-
.store_state_in_session(controller, expiry = nil, length = 24) ⇒ String
Stores a state token in the Rails session.
-
.validate_bound_state(controller, bound_state) ⇒ String
Validates a state token that was bound to the session.
-
.validate_nonce_from_session!(controller, id_token_nonce) ⇒ Object
Validates the nonce from the ID token against the one in the session.
-
.validate_state!(actual_state, expected_state) ⇒ Object
Validates that the actual state matches the expected state.
-
.validate_state_from_session!(controller, actual_state) ⇒ Object
Validates the state from the Rails session.
Class Method Details
.bind_state_to_session(controller, state) ⇒ String
Binds a state token to the session context for extra security
127 128 129 130 131 |
# File 'lib/clavis/security/csrf_protection.rb', line 127 def bind_state_to_session(controller, state) session_id = controller.request.session.id hmac = OpenSSL::HMAC.hexdigest("SHA256", session_id, state) "#{state}#{STATE_HMAC_DELIMITER}#{hmac}" end |
.generate_nonce(length = 16) ⇒ String
Generates a nonce for OIDC requests
75 76 77 |
# File 'lib/clavis/security/csrf_protection.rb', line 75 def generate_nonce(length = 16) SecureRandom.hex(length) end |
.generate_state(length = 24) ⇒ String
Generates a secure random state token for CSRF protection
15 16 17 |
# File 'lib/clavis/security/csrf_protection.rb', line 15 def generate_state(length = 24) SecureRandom.hex(length) end |
.store_nonce_in_session(controller, expiry = nil, length = 16) ⇒ String
Stores a nonce in the Rails session
84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/clavis/security/csrf_protection.rb', line 84 def store_nonce_in_session(controller, expiry = nil, length = 16) nonce = generate_nonce(length) controller.session[:oauth_nonce] = nonce # Store expiration if provided if expiry expiry_time = expiry.is_a?(Integer) ? Time.now.to_i + expiry : expiry.to_i controller.session[:oauth_nonce_expiry] = expiry_time end nonce end |
.store_state_in_session(controller, expiry = nil, length = 24) ⇒ String
Stores a state token in the Rails session
34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/clavis/security/csrf_protection.rb', line 34 def store_state_in_session(controller, expiry = nil, length = 24) state = generate_state(length) controller.session[:oauth_state] = state # Store expiration if provided if expiry expiry_time = expiry.is_a?(Integer) ? Time.now.to_i + expiry : expiry.to_i controller.session[:oauth_state_expiry] = expiry_time end state end |
.validate_bound_state(controller, bound_state) ⇒ String
Validates a state token that was bound to the session
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/clavis/security/csrf_protection.rb', line 138 def validate_bound_state(controller, bound_state) # Split using the delimiter - allows state to contain hyphens parts = bound_state.to_s.split(STATE_HMAC_DELIMITER) # We expect exactly 2 parts: state and hmac raise Clavis::InvalidState if parts.length != 2 state = parts[0] received_hmac = parts[1] # Basic validation raise Clavis::InvalidState if state.nil? || received_hmac.nil? || state.empty? || received_hmac.empty? # Verify HMAC session_id = controller.request.session.id expected_hmac = OpenSSL::HMAC.hexdigest("SHA256", session_id, state) raise Clavis::InvalidState unless received_hmac == expected_hmac # Return the original state if valid state end |
.validate_nonce_from_session!(controller, id_token_nonce) ⇒ Object
Validates the nonce from the ID token against the one in the session
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/clavis/security/csrf_protection.rb', line 103 def validate_nonce_from_session!(controller, id_token_nonce) expected_nonce = controller.session[:oauth_nonce] expiry = controller.session[:oauth_nonce_expiry] # Check for expiration if an expiry was set if expiry && Time.now.to_i > expiry # Clear expired nonce from session controller.session.delete(:oauth_nonce) controller.session.delete(:oauth_nonce_expiry) raise Clavis::ExpiredState end raise Clavis::MissingNonce if id_token_nonce.nil? || expected_nonce.nil? raise Clavis::InvalidNonce unless id_token_nonce == expected_nonce # Clear the nonce from the session after validation controller.session.delete(:oauth_nonce) controller.session.delete(:oauth_nonce_expiry) end |
.validate_state!(actual_state, expected_state) ⇒ Object
Validates that the actual state matches the expected state
24 25 26 27 |
# File 'lib/clavis/security/csrf_protection.rb', line 24 def validate_state!(actual_state, expected_state) raise Clavis::MissingState if actual_state.nil? || expected_state.nil? raise Clavis::InvalidState unless actual_state == expected_state end |
.validate_state_from_session!(controller, actual_state) ⇒ Object
Validates the state from the Rails session
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/clavis/security/csrf_protection.rb', line 53 def validate_state_from_session!(controller, actual_state) expected_state = controller.session[:oauth_state] expiry = controller.session[:oauth_state_expiry] # Check for expiration if an expiry was set if expiry && Time.now.to_i > expiry # Clear expired state from session controller.session.delete(:oauth_state) controller.session.delete(:oauth_state_expiry) raise Clavis::ExpiredState end validate_state!(actual_state, expected_state) # Clear the state from the session after validation controller.session.delete(:oauth_state) controller.session.delete(:oauth_state_expiry) end |