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

Published: (February 1, 2026 at 06:04 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Cover image for Devise to Custom Auth: It’s Not Just has_secure_password

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:

  1. Generate a unique, high‑entropy token.
  2. Store a digest of that token (never store the raw token).
  3. Set an expiration (e.g., 2 hours).
  4. Handle email delivery.
  5. 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.

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 own require_login filter
  • user_signed_in?logged_in?
  • current_user → custom helper in ApplicationController
  • devise_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:

  1. Detects whether the stored password is a BCrypt hash.
  2. If not, applies the legacy verification logic.
  3. 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.

Back to Blog

Related posts

Read more »