Admin Authentication
Website Rebuild with Ruby on Rails
Chapter 9 · Admin Authentication
Laravel's own Chapter 9 found a clean, zero-configuration win: its default hasher already matched the legacy site's bcrypt hash exactly. Rails' has_secure_password also defaults to bcrypt — but the honest version of this chapter isn't a repeat of that same clean finding. There's a real, worth-verifying nuance underneath it.
has_secure_password
has_secure_password adds a virtual password/password_confirmation pair and an authenticate(password) method to the model, backed by a real password_digest column — expected to already hold the legacy site's existing bcrypt hash for its own admin row.
A Real, Verified Nuance: $2a$ vs. $2y$
bcrypt gem, when it generates a new hash, tags it with the $2a$ version prefix. PHP's password_hash() tags its own bcrypt output $2y$ — a distinct prefix PHP introduced to mark a historical fix for how certain implementations handled 8-bit characters in a password. In practice, modern bcrypt implementations — including Ruby's own bcrypt gem — treat $2a$, $2b$, and $2y$ as interchangeable for verification, and an ordinary alphanumeric admin password will authenticate correctly either way. But this is a real point worth checking deliberately when a hash crosses from one language's ecosystem into another — not something to assume automatically seamless the way Laravel's same-ecosystem match was.
Normalizing the Hash on Login
On a successful login, re-assigning password triggers has_secure_password's own setter, regenerating password_digest using Ruby's own bcrypt gem — tagged $2a$ going forward. This quietly normalizes the legacy PHP-generated hash to a Rails-native one over time, the same spirit as Django Rebuild 9's own automatic hash-upgrade-on-login, closing the version-tag question naturally rather than needing a permanent resolution.
Closing Chapter 8's Gap
before_action :require_admin is exactly the missing piece Chapter 8 flagged — a genuinely separate concern from Strong Parameters, added the way Rails' own idiom expects: as a callback, not a change to page_params itself.
Password Hashing, Four Frameworks
| Next.js | Django | Laravel | Rails | |
|---|---|---|---|---|
| Default algorithm | None — hand-rolled | PBKDF2 | Bcrypt | Bcrypt |
| Matches the legacy hash? | Only via manual code | No — needed reconfiguration | Yes — zero configuration | Yes, functionally — with a real version-tag nuance worth verifying |
| Session model | Signed JWT cookie | Server-side session | Server-side session | Server-side session |
Hands-On Exercises
Explain what has_secure_password provides, and name the exact database column it expects to already exist.
Explain the real, verified difference between the $2a$ and $2y$ bcrypt version tags, and explain why this chapter treats Rails' own bcrypt match differently from Laravel's clean zero-configuration finding.
Explain what before_action :require_admin does, and explain exactly which gap from Chapter 8 it closes.
Chapter 9 Quick Reference
has_secure_password— addsauthenticate, backed by apassword_digestcolumn- Verified nuance — Ruby's
bcryptgem tags new hashes$2a$, PHP tags them$2y$; functionally compatible, but a real cross-ecosystem detail worth checking - Hash normalization on login — re-assigning
passwordquietly upgrades the legacy hash to Rails' own tag over time before_action :require_admin— closes Chapter 8's own missing-filter gap- Next chapter: Admin CRUD Interface