Devise to Custom Auth: It’s Not Just `has_secure_password
Source: Dev.to

The Itch to Switch
Devise is the 800‑pound gorilla of Rails authentication. It’s reliable, secure, and does everything.
But eventually you hit “the wall.” You might want a password‑less login flow, be tired of overriding dozens of controller methods just to change a redirect, or simply want to understand how your own app logs people in.
So you decide: “I’m going to rip out Devise and use Rails’ built‑in has_secure_password.”
I’ve done this migration. It leads to a cleaner, faster codebase—but the path there is filled with landmines. Here are the biggest challenges you will face.
Challenge 1: The Database Schema Mismatch
Devise is opinionated about database columns.
- Devise uses:
encrypted_password - Rails (
has_secure_password) expects:password_digest
You have two options, each with trade‑offs.
Option A: The Alias (Quick & Dirty)
# app/models/user.rb
class User < ApplicationRecord
has_secure_password :password, validations: false
alias_attribute :password_digest, :encrypted_password
end
Risk: It feels hacky. Future gems or tools that expect the standard Rails convention might stumble.
Option B: The Migration (Clean & Risky)
# db/migrate/xxxx_rename_encrypted_password.rb
class RenameEncryptedPassword < ActiveRecord::Migration[6.1]
def change
rename_column :users, :encrypted_password, :password_digest
end
end
Risk: Ensure zero downtime during deployment. Rolling back can become messy quickly.
Challenge 2: The “Recoverable” Module
Devise gave you “Forgot Password” for free. After removing it, no one can reset their password unless you implement it yourself.
A secure password‑reset flow must:
- Generate a unique, high‑entropy token.
- Store a digest of that token (never store the raw token).
- Set an expiration (e.g., 2 hours).
- Handle email delivery.
- Crucial: Invalidate the token after use to prevent replay attacks.
Devise handled timing‑attack prevention and token hashing for you; now you own that security liability.
Challenge 3: Cookie Security & “Remember Me”
session[:user_id] works for a basic login, but “Remember Me” is more complex. Devise’s Rememberable module manages a persistent cookie, token rotation, and secure comparison.
If you roll your own:
- Sign/encrypt the cookie.
- Guard against session hijacking (a stolen cookie grants long‑term access).
- Provide a way to “invalidate all sessions” (e.g., when a user changes their password).
Challenge 4: The View & Controller Cleanup
Devise helpers are pervasive. You’ll need to replace them across the codebase:
authenticate_user!→ your ownrequire_loginfilteruser_signed_in?→logged_in?current_user→ custom helper inApplicationControllerdevise_error_messages!→ your own error partials
Pro Tip: Don’t delete the gem immediately. Create a shim that maps old Devise method names to your new logic, letting you migrate gradually.
# app/controllers/concerns/auth_shim.rb
module AuthShim
extend ActiveSupport::Concern
included do
helper_method :current_user, :logged_in?
end
def authenticate_user!
redirect_to login_path unless logged_in?
end
def user_signed_in?
logged_in?
end
def logged_in?
!!current_user
end
def current_user
@current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
end
end
Include AuthShim in ApplicationController and gradually replace calls.
Challenge 5: Legacy Password Compatibility
Both Devise and has_secure_password use BCrypt by default, so they usually interoperate. However, older Rails apps (e.g., migrated from Rails 3/4) might have Devise configured with a legacy hashing strategy (SHA‑1 with a salt). has_secure_password cannot authenticate those hashes.
Fix: Implement a custom authentication method that:
- Detects whether the stored password is a BCrypt hash.
- If not, applies the legacy verification logic.
- Upon successful legacy login, re‑hashes the password with BCrypt and updates the record.
The Verdict: Is It Worth It?
Don’t do it if:
- You’re building an MVP for a client (speed matters).
- You rely heavily on OmniAuth (Devise smooths OAuth integration).
- You’re not comfortable with security concepts like session fixation or timing attacks.
Do it if:
- You’re building a monolithic app intended to last 5+ years.
- You need a specialized flow (OTP only, magic links, multi‑step onboarding).
- You want to reduce memory footprint and dependency bloat.
Removing Devise is one of the most educational things you can do in Rails. Just bring a flashlight—it’s dark down in the plumbing.