How to use Devise with Hotwire & Turbo.js Discussion
Hey Chris - If we are still using Webpacker, we just need to change data-turbolinks-track to data-turbo-track in our layout files? In your episode repo, you still have it as turbolinks.
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbo-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbo-track': 'reload' %>
<%= turbo_include_tags %>
<%= stimulus_include_tags %> if we want to use stimulus in webpacker?
Hey Chris, this is super amazing. I'm using hotwire-rails and there's 2 errors that need your expertise:
- The TurboController gives an error
undefined local variable or method
turbo_include_tags'` - When the redirect on incorrect password happens, it doesn't show 'notice'
turbo_include_tags is not necessary. for the second issue, make sure that you have config.navigational_formats = ['*/*', :html, :turbo_stream]
on your devise.rb
I also ran in to this issue. If you could add this line to the source code it would be much better for people trying to follow along.
Matias, thanks for saving me! :)
Your video arrived just in time, thanks !
But I have a blocking issue implementing this solution:
undefined local variable or method
turbo_include_tags'`
I had the same issue. Loading issue concerning application controller.
I put the turbo controller in the controllers dir in turbo_controller.rb and issue was resolved
Also confirmable not longer works when you remove <%= turbo_include_tags %> from the header. And the “turbo_frame_tag” form will no longer show when you run a frame on a page. And you get routing error when you try to log out a session.
No route matches [GET] "/users/sign_out"
I can also confirm that creating a ./app/controllers/turbo_controller.rb
instead of adding it to the top of the devise.rb
initializers fixed my undefined local variable or method 'turbo_include_tags'
error.
This also worked for me. Thanks Jay.
Hi Chris, seems you forgot to include the line about config.navigational_formats
in the snippet which was shown in the screencast
Correct me if i'm wrong but the one of disadvantages of this method is that on new session page if user typed wrong email and submits the form the email will not be preserved due to this lines:
if request_format == :turbo_stream
redirect
I found more appropriate solution for me how to fix this.
- do not override
respond
method. - add
data-turbo="false"
attribute to sign in form (which you should copy in advance)
Check this commit for this new feature:
For the login form, now I use Turbo instead of Turbolinks I have 401 response (because of TURBO_STREAM type ?)
Processing by Devise::SessionsController#create as TURBO_STREAM
Parameters: {"authenticity_token"=>"[FILTERED]", "user"=>{"username"=>"alex", "password"=>"[FILTERED]", "remember_me"=>"0"}, "commit"=>"Log in"}
Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms | Allocations: 372)
I'm getting this same error on the login form. The registration form works correctly. I'm using "@hotwired/turbo-rails": "^7.0.0-beta.5",
From what I can tell, Chris has also updated the source code for this episode to use beta.3
. Not sure what I'm doing wrong here.
Thank you Chris! I can't make works errors render, notice works well. Any ideas? 🤔
Take a look at my comment https://gorails.com/forum/how-to-use-devise-with-hotwire-turbo-js-discussion#forum_post_17983
I fixed it adding the id to the form and customizing devise controllers
Seems to me a like hacky ... will wait to implement this in a production project, login form handling have to be very basic stuff.
Any easy way to get this to work with the 'hotwire-rails' gem?
upgraded to beta .3 and its working
I've got a few devise controllers I've inherited from ( to add some breadcrumbs, etc). They don't like the turbocontroller.
Do I also need to inherit that as well?
Has anyone tried to destroy a user on devise via account edit user page?
I get undefined method `users_url' for #Devise::RegistrationsController:0x0000000000ca58
on line --> redirect_to navigation_location
whenever I try to do so.
same issue here :/
I had the same error and I solved with this
config.navigational_formats = ['/', :html, :turbo_stream] in devise.rb
Thanks Gonzaa, I had exactly this problem and that line solves it.
I tried adding the turbo navigational format to devise and unfortunately that did not fix the problem on user destroy for me. What did end up fixing it was modifying the TurboController to match closer to what the responders gem does for other formats.
Look in lib/action_controller/responder.rb#L236-L244 of the responders gem for more context. My fix:
class DeviseTurboController < ApplicationController
class Responder < ActionController::Responder
def to_turbo_stream
if @default_response
@default_response.call(options.merge(formats: :html))
else
controller.render(options.merge(formats: :html))
end
rescue ActionView::MissingTemplate => error
if get?
raise error
elsif has_errors? && default_action
render rendering_options.merge(formats: :html, status: :unprocessable_entity)
else
navigation_behavior error
end
end
end
self.responder = Responder
respond_to :html, :turbo_stream
end
The changes are centered around the default response section. This is where it was getting off the rails on user session destroy for me.
If you're getting a
ActionView::Template::Error (undefined method `turbo_frame_tag' ...
in Production, make sure to set
config.parent_controller = 'Users::DeviseController'
and extract the
class TurboController < ApplicationController
...
end
into app/controllers/users/devise_controller.rb.
See also the hotwire-devise Repo for the source code!
I'm getting an error undefined method
then' for "messages":Stringon my
<%= turbo_stream_from "messages" do %>` line in an app I'm trying to build this into. Any ideas why?
when one logs into to a rails app using devise & turbo. how does the csrf token get updated on the page. I get Can't verify CSRF token authenticity. error when I send a from with turbo after a turbo login
If, like me, you rushed through this video and are now are seeing the error from warden Invalid strategy some_external_strategy
... it could be because you uncommented the entire block in devise.rb
initializer file. I didn't want to post this comment because I might look silly but... in case someone else makes my mistake :D
config.warden do |manager|
manager.failure_app = TurboFailureApp
# manager.intercept_401 = false
# manager.default_strategies(scope: :user).unshift :some_external_strategy
end
and not
config.warden do |manager|
manager.failure_app = TurboFailureApp
manager.intercept_401 = false
manager.default_strategies(scope: :user).unshift :some_external_strategy
end
Hello.
Has anyone managed to use the "current_user" method from Devise in broadcasted partial views?
I have merged the 2 applications "hotwire-devise" & "hotwire-twitter-clone",
and when I add a call to current_user
(like for instance <%= link_to "Edit", edit_tweet_path(tweet) if current_user.present? %> in the _tweet.html.erb file),
I get an error because the method is not available in this context:
Started POST "/tweets" for 127.0.0.1 at 2021-01-29 14:45:53 +0100
Processing by TweetsController#create as TURBO_STREAM
Parameters: {"authenticity_token"=>"[FILTERED]", "tweet"=>{"body"=>"TEST"}, "commit"=>"Create Tweet"}
Tweet Create (0.8ms) INSERT INTO "tweets" ("body", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["body", "TEST 003"], ["created_at", "2021-01-29 13:45:53.776383"], ["updated_at", "2021-01-29 13:45:53.776383"]]
Rendered tweets/_tweet.html.erb (Duration: 4.3ms | Allocations: 2000)
Completed 500 Internal Server Error in 30ms (ActiveRecord: 19.6ms | Allocations: 5713)
ActionView::Template::Error (Devise could not find the `Warden::Proxy` instance on your request environment.
Make sure that your application is loading Devise and Warden as expected and that the `Warden::Manager` middleware is present in your middleware stack.
If you are seeing this on one of your tests, ensure that your tests are either executing the Rails middleware stack or that your tests are using the `Devise::Test::ControllerHelpers` module to inject the `request.env['warden']` object for you.):
6: <%= button_to "Likes (#{tweet.likes_count})", tweet_like_path(tweet), method: :post %>
7: <%= button_to "Retweets (#{tweet.retweets_count})", tweet_retweet_path(tweet), method: :post %>
8:
9: <%= link_to "Edit", edit_tweet_path(tweet) if current_user.present? %>
10: </div>
11: </div>
12: <% end %>
DHH replied into that here: https://discuss.hotwire.dev/t/authentication-and-devise-with-broadcasts/1752
Shortly, you can't do that.
consider to separate the TurboController and TurboFailureApp to its own class since put the class inside Devise config could give many much anomalies especially on production because somehow it could ruin your gemfile load order
There's another issue that seems to exist after the migration. When a user tries to change their password the original password always returns as invalid. Has anyone been able to fix this issue?
Warden also throws an error if you have users credentials expire after an inactivity period.
app/channels/application_cable/connection.rb:13:in `find_verified_user'
There was an exception - UncaughtThrowError(uncaught throw :warden)
If you receive warning about
DEPRECATION WARNING: Initialization autoloaded the constants ApplicationHelper, FormHelper, Stimulus::StimulusHelper, DeviseHelper, ApplicationController, and ActionText::ContentHelper.
Being able to do this is deprecated. Autoloading during initialization is going
to be an error condition in future versions of Rails.
Reloading does not reboot the application, and therefore code executed during
initialization does not run again. So, if you reload ApplicationHelper, for example,
the expected changes won't be reflected in that stale Module object.
These autoloaded constants have been unloaded.
In order to autoload safely at boot time, please wrap your code in a reloader
callback this way:
Rails.application.reloader.to_prepare do
# Autoload classes and modules needed at boot time here.
end
That block runs when the application boots, and every time there is a reload.
For historical reasons, it may run twice, so it has to be idempotent.
Check the "Autoloading and Reloading Constants" guide to learn more about how
Rails autoloads and reloads.
(called from <main> at /home/orlovic/rails/gofordesi-webapp/config/environment.rb:5)
than simply wrap the code inside Rails.application.reloader.to_prepare do
like
Rails.application.reloader.to_prepare do
class TurboFailureApp < Devise::FailureApp
def respond
...
end
class TurboController < ApplicationController
...
end
end
great timing!
Thank you , that was the solution to the deprecation warning
I was recieving an error undefined constant ApplicationController and putting those modules inside of this to_prepare block fixed it thank you :))
I can not log out on Devise!
No route matches [GET] "/users/logout"
routes.rb
devise_for :users, path: 'users',
path_names: {sign_up: 'signup', sign_in: 'login', sign_out: 'logout'},
controllers: {
confirmations: 'users/confirmations',
sessions: 'users/sessions',
registrations: 'users/registrations'
}
I've matched what's in https://github.com/gorails-screencasts/hotwire-devise/ and I'm still not getting 422 Unprocessable Entity's rendered. When submitting an erroneous register form, the server renders the page with the form errors but the client just sits and does nothing. I've inserted a pry into the parent controller and I know that's working well. I've matched versions to "@hotwired/turbo-rails": "7.0.0-beta.3"
. I'm not sure where the error is or what next to check.
I still don't know what the problem was but I've followed this comment and got rid of the customizations and rendering a 422 is now working, but rendering a redirect is not. https://github.com/heartcombo/devise/pull/5340#issuecomment-833840004
Can anybody help? How to deal with hotwire/devise/cancancan - partial problem? If I have a "if can?" in broadcasted partial, I get warden proxy error. "can?" method uses current_user which is not broadcasted.
https://blog.cloud66.com/making-hotwire-and-devise-play-nicely-with-viewcomponents/ this is a good solution.
Hey!
I have another problem - when i destroy my account from devise's Account page i see following error:
NoMethodError in Users::RegistrationsController#destroy
undefined method `users_url' for #Users::RegistrationsController:0x0000000002f058
Did you mean?
user_session_url
Rails.root: /Users/alec/Code/Internal/cosmoport
Application Trace | Framework Trace | Full Trace
config/initializers/devise.rb:27:in rescue in to_turbo_stream'
to_turbo_stream'
config/initializers/devise.rb:19:in
app/controllers/application_controller.rb:85:in `configure_time_zone'
Exception Causes
ActionView::MissingTemplate: Missing template users/registrations/destroy, devise/registrations/destroy, devise/destroy, turbo/destroy, application/destroy with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :jbuilder]}. Searched in: * "/Users/alec/Code/Internal/cosmoport/app/views" * "/Users/alec/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/blazer-2.4.3/app/views" * "/Users/alec/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/devise-i18n-1.10.0/app/views" * "/Users/alec/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/bundler/gems/devise-c82e4cf47b02/app/views" * "/Users/alec/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/view_component-2.38.0/app/views" * "/Users/alec/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/actiontext-6.1.4.1/app/views" * "/Users/alec/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/actionmailbox-6.1.4.1/app/views"
Any idea how to fix it?
Maybe not the best way, but solving this problem : you can add a new route for users_path
:
# config/routes
get '/', to: 'user#index', as: 'users'
Devise is waiting on the Responders gem to update before having something standard. It pains me, but I've just disabled turbo on devise forms until there's something standard in a released version.
I had to just disable turbo on the Devise forms as well. I was getting errors and the time spent on figuring those out was too much.
How did you successfully disable it on your Devise forms? I've tried adding data: { turbo: false }
to my registrations/new.html.erb
and it doesn't seem to stop processing as turbo_stream.
<%= form_for(resource, as: resource_name, url: session_path(resource_name), html: {'data-turbo' => "false"}) do |f| %>
Or just disabling Turbo on all Devise forms and sign out links.
https://turbo.hotwired.dev/handbook/drive#disabling-turbo-drive-on-specific-links-or-forms
I've got a few devise controllers I've inherited from. They don't like the turbocontroller. Do I also need to inherit that as well?
Just wanted to mention that for Rails 7, I also needed to change the logout link from using link_to
to button_to
to make the DELETE request instead of GET.
You don't have to change the link_to. In devise.rb you change
config.sign_out_via = :delete
to
config.sign_out_via = :get
then in your link_to
<%= link_to destroy_user_session_path, method: :delete %>
or specify the turbo method like so:
<%= link_to destroy_user_session_path, data: { turbo_method: :delete } %>
What Tony Serkis suggested really worked for me and my flash messages are being shown correctly on the sign-out action. I have only one question because changing the method to GET request, is there any security concern?
And for what you suggest Peter, it works, but I'm getting unexpected redirects I think, becuase I see the flash message "You need to sign in or sign up before continue" and that is because it is redirecting me to a controller that is under authentication, maybe I'm doing something wrong:
Started DELETE "/users/sign_out" for ::1 at 2021-12-28 12:09:59 -0600
Processing by Devise::SessionsController#destroy as TURBO_STREAM
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
Redirected to http://localhost:3000/
Completed 302 Found in 10ms (ActiveRecord: 0.4ms | Allocations: 5677)
Started DELETE "/" for ::1 at 2021-12-28 12:09:59 -0600
ActionController::RoutingError (No route matches [DELETE] "/"):
Started GET "/dashboard/index" for ::1 at 2021-12-28 12:09:59 -0600
Processing by DashboardController#index as HTML
Completed 401 Unauthorized in 3ms (ActiveRecord: 0.0ms | Allocations: 469)
Started GET "/users/sign_in" for ::1 at 2021-12-28 12:09:59 -0600
Processing by Devise::SessionsController#new as HTML
Rendering layout layouts/landing.html.erb
Rendering devise/sessions/new.html.erb within layouts/landing
Rendered devise/shared/_links.html.erb (Duration: 2.9ms | Allocations: 1300)
Rendered devise/sessions/new.html.erb within layouts/landing (Duration: 10.9ms | Allocations: 4552)
Rendered shared/_notifications.html.erb (Duration: 5.5ms | Allocations: 2748)
Rendered layout layouts/landing.html.erb (Duration: 27.3ms | Allocations: 14853)
Completed 200 OK in 31ms (Views: 29.2ms | ActiveRecord: 0.0ms | Allocations: 16426)
I had similar issue and it was puzzling me, every time I signup i got redirected to /users
which it didn't had any authentication restrictions. Until i notice that I had method: :put
for form_for
:
<%= form_for(resource, as: resource_name, url: registration_path(resource_name),html: { method: :put, data: { turbo: false } }) do |f| %>
Once I removed method: :put, everything worked nicely.
FYI, after upgrading to Rails 7 I had to move Chris's TurboController
code in this video out of initializers/devise.rb
into a normal rails controller to prevent an error when starting the server. I put this code into app/controllers/turbo_devise_controller.rb
and then in the devise initializer changed this line to config.parent_controller = 'TurboDeviseController'
and everything worked as expected. Thanks to Nick Francisci's article on this here: https://medium.com/@nickfrancisci/devise-auth-setup-in-rails-7-44240aaed4be
Hey Chris, Im following this tutorial, but having a simple "uninitialized constant ApplicationController (NameError)" for the constant used in your code within devise.rb even though my application_controller is set up. Any ideas what must be the problem. Using Rails 7.
I recently ran into this as well with Rails 7. Hopefully this will be fixed in Devise soon. For the time being, I just created a new controller that I call in config/initializers/devise.rb
like this config.parent_controller = 'TurboDeviseController'
.
Here's full devise initializer -> https://gist.github.com/leemcalilly/9d73e1f549d9aa8f2973f5a63004ea32
And here's the new controller (app/controllers/turbo_devise_controller.rb) -> https://gist.github.com/leemcalilly/ab04e30dd8d53429939d7845b5691b83
Hey Lee! Thanks for the response. I've actually tried that solution, but the errors are still not appearing on failed authentication.
Actually on signup error messages are working but not on log in. If I type in an incorrect password, no error will appear to the user.
Found the error, it was how I was using the devide_error_messages template instead of the flash to notify the error. Working already. Thanks Lee
Hi @Santiago Rodriguez, could you explain better how is this change of devise_error_template
to flash
to notify the error on the Login page? I am with the same issue.
Hey,
i have a problem with sign_in. (Rails7). after users signs in, i get already_authenticated flash message instead of signed_in: "Signed in successfully." any ideas why it happens?
ok i solved my problem by telling sign_in form to not use turbo :)
<%= form_for(resource, as: resource_name, url: session_path(resource_name), data: { turbo: false }) do |f| %>
It is a year after this video was made and Devise still does not work with Rails 7. Is there any chance that this will be fixed or do we still need to do all this workaround?
Basically add the data: { turbo: false }
<%= form_with model: resource, as: resource_name, url: registration_path(resource_name), data: { turbo: false } do |f| %>
The code in the article worked for me with Rails 7, but I had to do extra steps resolved this issue by adding config.navigational_formats = ['*/*', :html, :turbo_stream]
to the initialize/devise.rb and also putting the TurboController in the file app path (app/controller/turbo_controller.rb).
This post is extremely helpful for me. I really appreciate your kindness in sharing this with me and everyone else!
Thank you very helpful,
I had this error uninitialized constant users devise and seen in the "Source code for this episode"
app/controllers/Users/devise_controller.rb
This has worked quite well for me, with one exception. When changing a password, it ends up calling redirect_to navigation_location
but navigation_location
is nil at this point.
The solutions works well. Thank you Chris
The only issue that I'm getting is when I press Enter/Return onKeypress I'm getting this error in console
OnClick event for login or sign out is fine no error in the console.
Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
As of Dec31 2022, Rails 7.0.4
"@hotwired/turbo-rails": "7.2.0" seems to work ok straight out of the box, so no need for "@hotwired/turbo":"https://github.com/hotwired/turbo/archive/dev-builds/latest.tar.gz",
However, I have to move the
class DeviseTurboController < ApplicationController ...
Out of the devise initialiser and into its own app/controller/devise_turbo-controller.rb file