#1149·omniauth

Failure to complete auth flow when session has SameSite=Strict policy, please store tokens in SameSite=Lax cookies instead

Author: mildredCreated Sep 17, 2025Updated Sep 17, 2025

When the session cookie is protected by SameSite=Strict and the SSO is not on the same domain than the application itself, the browser is preventing the callback endpoint to access the session cookie on callbacks. This has been observed with an OpenID Connect SSO.

The error I get is:

Authentication failure! csrf_detected: OmniAuth::Strategies::OpenIDConnect::CallbackError, csrf_detected | Invalid 'state' parameter

This is because the state parameter passed in param to the callback does not agree with the state parameter in the session, because the session is empty on the callback.

To avoid depending on the session cookie being SameSite=Lax (which can be a security issue for the rest of the app), the persistent state should be stored in a separate cookie specific to OmniAuth that have SameSite=Lax or even SameSite=None depending on the auth flow in use.

Calls to OmniAuth::Strategy#session should be replaced by either a specific session store that is unique to OmniAuth or directly by cookies.

There is a possible workaround by creating a custom middleware that would create cookies from omniauth values in session and restore those values in session if it detects that the session is empty on the callback URL. However many values probably need saving and restoring this way and I found a solution that does not need this level of knowledge on inner behaviour of OmniAuth by performing a HTML redirect when it detects such situation:

rb
class OmniauthOidcFixStateMiddleware
  def initialize(app)
    @app = app
  end

  CALLBACK_PATH = '/users/auth/openid_connect/callback' # You need to customize this

  def call(env)
    session = env['rack.session']

    # This workd for an OpenID Connect flow because it sets omniauth.state in session, you may need to adjust depending on your auth flow
    return redirect env['REQUEST_URI'] if env['REQUEST_PATH'] == CALLBACK_PATH && session['omniauth.state'].blank?

    # Call the next middleware in the stack
    status, headers, response = @app.call(env)

    [status, headers, response]
  end

  def redirect(uri)
    # Perform a HTML redirect to ensure that SameSite=Strict session cookie is
    # included
    r = Rack::Response.new
    r.write(%(
      <html>
        <head>
          <!-- HTML redirect to include session cookie with SameSite=Strict -->
          <meta http-equiv="refresh" content="0;URL='#{uri}'"/>
        </head>
      </html>
    ))
    r.finish
  end
end

# Add this middleware before OmniAuth is initialized so the middleware appears before the OmniAuth strategy
# check using: rails middleware

Rails.application.config.middleware.use OmniauthOidcFixStateMiddleware