How to switch from devise to your own authentication system.
Run the following generator to create a migration, which we need to store the user password.
bin/rails g migration add_password_digest_to_users password_digest:string
to add the relevant column to you User model (if you're authenticating by a User model).
Here your migration, for now leave it without the NOT NULL
constraint.
class AddPasswordDigestToUsers < ActiveRecord::Migration[8.0]
def change
add_column :users, :password_digest, :string#, null: false
end
end
8.0
and run the generatorOnly from Rails 8.0
onwards do we get the following generator, which you can now run:
bin/rails generate authentication
This will create all the necessary files (controllers, views, tests, etc.) for you to get going with your own authentication layer. Delete the migration file that creates a new user, since we already have that in place.
Remove from application_controller.rb
: before_action :authenticate_user!
Also, ensure you remove all devise callbacks, such as before_action :authenticate_user!
etc. in all the other controllers where you may have used them.
If you are using Pundit
for authorization, you must add the following method to your application controller:
def pundit_user
Current.user
end
If you had this one:
def skip_pundit?
devise_controller? || params[:controller] =~ /(^(rails_)?admin)|(^pages$)/
end
Switch this to this one (the authentication/
is because i've put my authentication controllers inside of an authentication/
module):
def skip_pundit?
params[:controller] == "authentication/sessions" || params[:controller] =~ /(^(rails_)?admin)|(^pages$)/
end
The generator command should have created a new sessions_controller.rb
. In there replace the current create
action:
def create
if user = User.authenticate_by(params.permit(:email_address, :password))
start_new_session_for user
redirect_to after_authentication_url
else
redirect_to new_session_path, alert: "Try another email address or password."
end
end
with this one:
def create
user = User.find_by(email: params[:email])
if user && user.password_digest.blank?
redirect_to new_password_path(email: params[:email]), alert: "Please request a new password."
elsif user = User.authenticate_by(params.permit(:email, :password))
start_new_session_for user
redirect_to after_authentication_url
else
redirect_to new_session_path, alert: "Try another email address or password."
end
end
It's maybe not the best of user flows, but this ensures that when your users come from the old devise system, they have to first create a new password, which is stored in the new password_digest
column. Btw, note that i've changed the email_address to email, to make it compatible with devises old way. You'll have to ensure that you're always targeting email instead of email_address in all the newly generated forms.
With this flow, you'll also have to ensure that your email system works, so that user can recreate the password.
And finally, remove the gem devise
from your gemfile and delete all the related initializers and views.