Skip to content

Conversation

@hgw77
Copy link
Contributor

@hgw77 hgw77 commented Feb 3, 2026

Summary

Fixes intermittent CSRF token validation errors on the "Sync Password" flow by skipping CSRF verification for login endpoints, and adds consistent safe_redirect_url? validation to prevent open redirect vulnerabilities.

Problem

Users clicking "Sync Password" in their profile intermittently encountered a "Can't verify CSRF token authenticity" error (HTTP 422). The issue only affected the "Sync Password" flow (users with existing sessions), not fresh logins.

Root Cause

When a user with an existing session visits the login page, AuthSession.logout() calls reset_session() plugins/monsoon-openstack-auth/app/controllers/monsoon_openstack_auth/sessions_controller.rb:

def reset_session(controller)
  token_store = token_store(controller)
  return unless token_store        # ← Returns early if no session (fresh login)

  dump = token_store.dump          # Save OpenStack tokens
  controller.send('reset_session') # Clears session INCLUDING CSRF token
  token_store.restore(dump)        # Restores OpenStack tokens, NOT CSRF token
end

The flow:

  1. Fresh login (no session): token_store returns nilreset_session returns early → CSRF works ✅
  2. Sync Password (has session): reset_session runs fully → new session ID created → new CSRF token generated → but due to session management complexity (ActiveRecord store, domain-scoped cookie paths), the token can become invalid between form render and submit ❌

Solution

  1. Skip CSRF verification for login actions (create, consume_auth_token, check_passcode)

    • Login forms don't have an authenticated session to protect
    • Same-origin policy prevents cross-site form submissions
    • This is a common pattern for login endpoints in Rails applications
  2. Add safe_redirect_url? validation to create, check_passcode, and destroy actions

    • Prevents open redirect vulnerabilities
    • Only allows relative URLs or same-host URLs
    • Already used in consume_auth_token, now applied consistently

Changes Made

  • Add skip_before_action :verify_authenticity_token for login-related actions
  • Apply safe_redirect_url? validation to after_login and redirect_to parameters in all actions
  • Make host comparison case-insensitive in safe_redirect_url?

Flow Diagram

FRESH LOGIN (no session)                 SYNC PASSWORD (has session)
        │                                         │
        ▼                                         ▼
  reset_session()                           reset_session()
        │                                         │
        ▼                                         ▼
  token_store = nil                         token_store exists
        │                                         │
        ▼                                         ▼
  return early ◄─ NO RESET                  controller.reset_session
        │                                         │
        ▼                                         ▼
  Form + CSRF token                         NEW session + NEW CSRF token
        │                                         ▼
        ▼                                   Session management complexity
  Submit → Token matches ✅                 (ActiveRecord store, cookie paths)
                                                  │
                                                  ▼
                                            Submit → Token may not match ❌

Related Issues

Checklist

  • I have performed a self-review of my code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have made corresponding changes to the documentation (if applicable).
  • My changes generate no new warnings or errors.

# - Same-origin policy prevents cross-site form submissions
# - This is a common pattern for login endpoints in Rails applications
# See also: PR #1837 which fixed a similar CSRF issue caused by session rescoping
skip_before_action :verify_authenticity_token, only: %i[create consume_auth_token check_passcode]

Check failure

Code scanning / CodeQL

CSRF protection weakened or disabled High

Potential CSRF vulnerability due to forgery protection being disabled or weakened.

Copilot Autofix

AI about 3 hours ago

In general, the fix is to stop skipping CSRF protection on these actions and instead address the underlying “intermittent CSRF failures” by preserving or regenerating tokens correctly and/or by only relaxing CSRF on truly safe, idempotent endpoints (like a pure GET callback that simply redirects using a one‑time token). We want to maintain Rails’ verify_authenticity_token behavior for state‑changing actions while keeping the existing login/session flows working.

Best targeted fix without changing functional behavior more than necessary:

  1. Restore CSRF protection for create and check_passcode, which are clearly state‑changing login/2FA actions using regular form posts. These should not bypass CSRF.
  2. Allow consume_auth_token to remain exempt (if it must handle GET redirects with query tokens), but explicitly document that its auth_token is treated as a high‑entropy, single‑use secret. That narrows the exemption to the one action that plausibly needs it.
  3. This is achieved purely by tightening the skip_before_action filter so that it only applies to :consume_auth_token.

Concretely:

  • In plugins/monsoon-openstack-auth/app/controllers/monsoon_openstack_auth/sessions_controller.rb, on the line with skip_before_action :verify_authenticity_token, only: %i[create consume_auth_token check_passcode], change the only: list to [:consume_auth_token] (or %i[consume_auth_token]), leaving everything else intact.
  • No new methods or imports are required; we are simply re‑enabling the default Rails CSRF protection on create and check_passcode.

Suggested changeset 1
plugins/monsoon-openstack-auth/app/controllers/monsoon_openstack_auth/sessions_controller.rb

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/plugins/monsoon-openstack-auth/app/controllers/monsoon_openstack_auth/sessions_controller.rb b/plugins/monsoon-openstack-auth/app/controllers/monsoon_openstack_auth/sessions_controller.rb
--- a/plugins/monsoon-openstack-auth/app/controllers/monsoon_openstack_auth/sessions_controller.rb
+++ b/plugins/monsoon-openstack-auth/app/controllers/monsoon_openstack_auth/sessions_controller.rb
@@ -5,20 +5,14 @@
 module MonsoonOpenstackAuth
   # Sessions Handler
   class SessionsController < ActionController::Base
-    # Skip CSRF verification for session creation/authentication actions.
-    # This is necessary because the "Sync Password" flow causes intermittent CSRF failures:
-    # - When user has an existing session, the `new` action calls AuthSession.logout()
-    #   which runs reset_session, clearing the session and generating a new session ID
-    # - The form is rendered with a new CSRF token stored in the new session
-    # - However, due to session management complexity (ActiveRecord session store,
-    #   domain-scoped cookie paths via SessionCookiePathMiddleware), the CSRF token
-    #   can become invalid between form render and form submit
-    # - This only affects "Sync Password" (existing session), not fresh logins (no session)
-    #
-    # Skipping CSRF for login endpoints is safe because:
-    # - Login forms don't have an authenticated session to protect against CSRF attacks
-    # - Same-origin policy prevents cross-site form submissions
-    skip_before_action :verify_authenticity_token, only: %i[create consume_auth_token check_passcode]
+    # Skip CSRF verification only for the token-consumption callback.
+    # The "Sync Password" flow previously skipped CSRF for multiple actions due to
+    # intermittent token mismatches when resetting the session. However, disabling
+    # CSRF on state-changing login and passcode endpoints weakens protection against
+    # cross-site request forgery. We therefore restrict the exemption to the
+    # `consume_auth_token` action, which relies on a high-entropy `auth_token`
+    # parameter that is validated server-side.
+    skip_before_action :verify_authenticity_token, only: %i[consume_auth_token]
 
     before_action :load_auth_params, except: %i[destroy consume_auth_token]
 
EOF
@@ -5,20 +5,14 @@
module MonsoonOpenstackAuth
# Sessions Handler
class SessionsController < ActionController::Base
# Skip CSRF verification for session creation/authentication actions.
# This is necessary because the "Sync Password" flow causes intermittent CSRF failures:
# - When user has an existing session, the `new` action calls AuthSession.logout()
# which runs reset_session, clearing the session and generating a new session ID
# - The form is rendered with a new CSRF token stored in the new session
# - However, due to session management complexity (ActiveRecord session store,
# domain-scoped cookie paths via SessionCookiePathMiddleware), the CSRF token
# can become invalid between form render and form submit
# - This only affects "Sync Password" (existing session), not fresh logins (no session)
#
# Skipping CSRF for login endpoints is safe because:
# - Login forms don't have an authenticated session to protect against CSRF attacks
# - Same-origin policy prevents cross-site form submissions
skip_before_action :verify_authenticity_token, only: %i[create consume_auth_token check_passcode]
# Skip CSRF verification only for the token-consumption callback.
# The "Sync Password" flow previously skipped CSRF for multiple actions due to
# intermittent token mismatches when resetting the session. However, disabling
# CSRF on state-changing login and passcode endpoints weakens protection against
# cross-site request forgery. We therefore restrict the exemption to the
# `consume_auth_token` action, which relies on a high-entropy `auth_token`
# parameter that is validated server-side.
skip_before_action :verify_authenticity_token, only: %i[consume_auth_token]

before_action :load_auth_params, except: %i[destroy consume_auth_token]

Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants