heartcombo/devise · error · ArgumentError
Mapping omniauth_callbacks on a resource that is not omniaut
Error message
Mapping omniauth_callbacks on a resource that is not omniauthable
Please add `devise :omniauthable` to the `#{mapping.class_name}` model What it means
When devise_for is given controllers: { omniauth_callbacks: "..." }, Devise validates at route-draw time that the mapped model actually includes the :omniauthable module (mapping.omniauthable?). The omniauth_callbacks controller only makes sense for an omniauthable resource — its routes (/users/auth/:provider, callback routes) are generated from the model's declared omniauth_providers. If the model lacks devise :omniauthable, route drawing raises ArgumentError immediately at boot with the model class name in the message.
Source
Thrown at lib/devise/rails/routes.rb:258
resources.each do |resource|
mapping = Devise.add_mapping(resource, options)
begin
raise_no_devise_method_error!(mapping.class_name) unless mapping.to.respond_to?(:devise)
rescue NameError => e
raise unless mapping.class_name == resource.to_s.classify
warn "[WARNING] You provided devise_for #{resource.inspect} but there is " \
"no model #{mapping.class_name} defined in your application"
next
rescue NoMethodError => e
raise unless e.message.include?("undefined method `devise'")
raise_no_devise_method_error!(mapping.class_name)
end
if options[:controllers] && options[:controllers][:omniauth_callbacks]
unless mapping.omniauthable?
raise ArgumentError, "Mapping omniauth_callbacks on a resource that is not omniauthable\n" \
"Please add `devise :omniauthable` to the `#{mapping.class_name}` model"
end
end
routes = mapping.used_routes
devise_scope mapping.name do
with_devise_exclusive_scope mapping.fullpath, mapping.name, options do
routes.each { |mod| send("devise_#{mod}", mapping, mapping.controllers) }
end
end
end
end
# Allow you to add authentication request from the router.
# Takes an optional scope and block to provide constraints
# on the model instance itself.
#View on GitHub (pinned to 372b295fe6)
Solutions
- Add the module to the model together with its providers: devise :omniauthable, omniauth_providers: [:google_oauth2] in app/models/user.rb (plus the matching config.omniauth entries and omniauth-* gems), then restart.
- If OAuth is not actually used, remove the omniauth_callbacks entry (or the whole controllers: hash if it is the only override) from devise_for in config/routes.rb.
- If you are mid-migration, comment out the controllers override until the model module and provider gems are in place, then restore it in the same commit.
- Verify after boot with rails routes | grep omniauth_callbacks that the provider routes now exist.
Example fix
# before
# config/routes.rb
devise_for :users, controllers: { omniauth_callbacks: "users/omniauth_callbacks" }
# app/models/user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable, :recoverable
# -> ArgumentError: resource is not omniauthable
end
# after
# app/models/user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable, :recoverable,
:omniauthable, omniauth_providers: [:google_oauth2]
end
# Gemfile (provider gem must also be present)
gem "omniauth-google-oauth2" Defensive patterns
Strategy: validation
Validate before calling
# Guard the route declaration against the model's actual modules (routes.rb):
devise_for :users,
controllers: (User.devise_modules.include?(:omniauthable) ?
{ omniauth_callbacks: "users/omniauth_callbacks" } : {})
# Or in a boot check after routes load:
Rails.application.config.after_initialize do
Devise.mappings.each_value do |m|
next unless m.controllers.key?(:omniauth_callbacks)
raise "#{m.class_name} needs devise :omniauthable" unless m.omniauthable?
end
end Type guard
def omniauthable_resource?(klass) klass.respond_to?(:devise_modules) && klass.devise_modules.include?(:omniauthable) end
Prevention
- Change the model (add devise :omniauthable, omniauth_providers: [...]) and the devise_for controllers override in the same commit.
- When removing Devise modules from a model, grep routes.rb for the matching controllers: entries in the same change.
- Run rails routes after any OAuth-related change — omniauth_callback routes appearing confirms model and routes agree.
- Keep OAuth setup scripted (one PR/commit: Gemfile + initializer + model + routes) so the pieces never drift.
When it happens
Trigger: config/routes.rb contains devise_for :users, controllers: { omniauth_callbacks: "users/omniauth_callbacks" } while app/models/user.rb only has devise :database_authenticatable, :registerable, :recoverable (no :omniauthable); the :omniauthable module was removed during a refactor but the controllers hash in routes.rb was left in place; routes were copied from an OAuth-enabled app into one whose model was never updated.
Common situations: Incrementally enabling OAuth (developer wires routes/controller first, model second); cleaning up unused Devise modules from a model without checking routes.rb; copying devise_for lines between apps or engines; merging branch A's routes with branch B's model.
Related errors
- #{name}
- Could not find devise mapping for path #{request.fullpath.in
- :route should be true, a Symbol or a Hash
- "Devise doesn't know how to sanitize parameters for '#{actio
- An ORM must be set to install Devise in your application. B
AI-assisted analysis of heartcombo/devise@372b295fe6 (2026-08-21).
Data as JSON: /api/errors/bb84bbfe710253b1.
Report an issue: GitHub.