Skip to content

Fix after_omniauth_failure_path_for for engine-mounted Devise#6

Merged
Fivell merged 2 commits into
mainfrom
fix-omniauth-failure-path-engine-aware
Jun 2, 2026
Merged

Fix after_omniauth_failure_path_for for engine-mounted Devise#6
Fivell merged 2 commits into
mainfrom
fix-omniauth-failure-path-engine-aware

Conversation

@Fivell

@Fivell Fivell commented Jun 2, 2026

Copy link
Copy Markdown
Member

Summary

Fixes after_omniauth_failure_path_for for hosts that mount Devise inside a Rails engine and pin URL helpers to it via Devise.router_name = :<engine_name>.

Why it was broken

after_omniauth_failure_path_for(scope) was calling public_send(:"new_#{scope}_session_path") directly on the controller. That works when the gem's session route is mounted on Rails.application.routes (the default), because controllers include the main_app url helpers. But for engine-mounted hosts the gem mounts the route inside <Engine>.routes, so the concrete helper lives on the engine proxy (e.g. console_engine.new_admin_user_session_path), not directly on the controller — calling it raised:

NoMethodError: undefined method `new_admin_user_session_path' for an instance of ActiveAdmin::Oidc::Devise::OmniauthCallbacksController

Devise's own new_session_path(scope) doesn't help either — it's only generated when :database_authenticatable is in Devise::Mapping#used_helpers, and OIDC-only models never load that module.

Fix

Replicate Devise's URL-helper dispatch (see devise/controllers/url_helpers.rb#generate_helpers!) — look up the mapping's router_name, send it on the controller to get the engine proxy (or fall back to self for main_app), then public_send the concrete session helper on that context:

def after_omniauth_failure_path_for(scope)
  router_name = ::Devise.mappings[scope].router_name
  context     = router_name ? send(router_name) : self
  context.public_send(:"new_#{scope}_session_path")
end

Test plan

  • rake spec:all is green — covers default, engine-mounted, and isolated-engine setups.
  • Verify on a host with Devise.router_name = :<engine> that an OmniAuth failure redirects to the SSO landing page instead of 500-ing.

Fivell added 2 commits June 2, 2026 10:40
Devise's own new_session_path(scope) helper is only generated when :database_authenticatable is in the mapping's used_helpers — OIDC-only models never get it. The engine mounts new_<scope>_session_path itself, but the helper lives on whichever route set the mapping's router_name points at: Rails.application.routes by default, or <engine_name> routes for hosts that mount Devise inside an engine.

Replicates Devise's URL-helper dispatch in the OmniauthCallbacks failure handler: look up the mapping's router_name, send it on the controller to get the engine proxy (or fall back to self for main_app), then public_send the concrete session helper on that context.

Without this fix the failure handler raised NoMethodError on hosts with Devise.router_name set (e.g. mount Devise inside a Console/AdminPanel engine).
Both suites previously only tested the happy path (URL helpers resolve, GET /admin/login renders the SSO button). The failure handler in OmniauthCallbacksController#after_omniauth_failure_path_for was only exercised by spec/requests/omniauth_callback_spec.rb, which boots the default dummy where Devise.router_name is unset — so the engine-mounted regression that the previous commit fixes was uncovered by CI.

Adds an :invalid_credentials Capybara scenario in each engine dummy: visit /admin/login, click the SSO button, expect to land back on /admin/login. Both fail without the controller's router_name-aware dispatch and pass with it.
@Fivell Fivell merged commit d561140 into main Jun 2, 2026
6 checks passed
@Fivell Fivell deleted the fix-omniauth-failure-path-engine-aware branch June 2, 2026 09:34
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.

1 participant