A Rails admin panel
A founder console gated by HTTP Basic Auth instead of user accounts, seventeen controllers on their own layout, and a setup wizard that writes the credentials the gate reads. What it costs to build, and where it stops.
Articles on this topic
-
Editing the pricing table from the admin, without a deploy
A plan lives in three places at once: a jsonb column you can edit, locale files and credentials underneath it, and immutable Price objects in Stripe. What each one owns, and the bug that came from forgetting the third.
-
A first-run setup wizard that refuses to run in production
The console writes encrypted credentials per environment, and every action is gated to local. Why a deployed wizard is a liability rather than a convenience, and what a blank field has to mean.
-
Gating a Rails admin panel with HTTP Basic Auth
Nine lines put a console behind a password without an admin user model. What secure_compare is for, why the two checks are joined with & rather than &&, and why an unconfigured console answers 404 instead of asking.
Every application grows a back office. Somebody has to read the support tickets, refund the customer who asked twice, flip the feature flag before the demo and find out why that signup never confirmed. The question is not whether you build one, it is what it authenticates against.
What the console has to answer to
The obvious answer is the application's own login, with a
rolecolumn and anadmin?check. It is also the answer that drags the console through every gate the application has: the session, the email confirmation, the onboarding flow a user must finish before reaching anything. Each of those is a place where the founder can get locked out of their own back office, and each is a place where a bug in the user-facing app becomes a bug in the tool you fix it with.Admin::BaseControllertakes the other road. It declaresallow_unauthenticated_accessandallow_unonboarded_access, which switch off both of the application's gates, and then installs one of its own in abefore_action. The console stops being a privileged corner of the product and becomes a separate thing that happens to share a database.The gate itself
HTTP Basic Auth, with the credentials read from the Rails credentials file under
admin:rather than from any table. There is no admin user model, no password reset, no session. The browser holds the credentials and sends them on every request.That is a real trade and the article on gating the console with HTTP Basic Auth is where it gets argued properly: what the comparison has to do to be safe, why the two checks are joined with a single ampersand, and the point at which a growing team makes this the wrong choice.
An unconfigured console returns 404
A fresh clone has no admin credentials, and the wizard that writes them lives inside the console.
authenticate_adminresolves that in two directions. In local environments it returns early and lets the founder straight through, so the setup wizard is reachable on a machine where nothing has been configured yet. In production it answershead :not_found.The 404 is the interesting half. Challenging for a password that does not exist would tell a stranger that a console is there and simply unconfigured, which is an invitation. Answering the way every unused path answers says nothing at all.
Seventeen controllers, one layout
The Rails admin dashboard here is not a generic CRUD scaffold pointed at the schema. Each screen was written because something had to be done by hand:
users,leadsandcommentsfor the people,support_ticketsfor the inbox,quiz_answersfor content that lives in the database,pricingandfeaturesfor what the landing page sells,appearanceandsettingsfor how it looks,analyticsandreferralsfor what happened,aiandai_templatesfor the assistant, andsetupfor the wizard that got you here.They all inherit
Admin::BaseControllerand all renderlayout "admin", so the gate and the chrome are decided once.Two of them are worth their own page because the decision inside them is not obvious. The first-run setup wizard writes encrypted credentials and is gated to local environments, which is a stranger choice than it sounds until you consider what a deployed secrets form can be read for. And editing the pricing table touches a number that exists in three places at once, only one of which charges anybody.
What this is not
Not ActiveAdmin, not Avo, not a CMS. There is no DSL, no engine mounted in
routes.rb, and no generated screen for a model you have not thought about. That is deliberate at this size: a generated back office gives you every table immediately and then resists the first screen that does not look like a table.It is also not multi-user. One credential pair, shared. A second person with their own login, an audit trail of who deleted what, permissions per screen: none of that is here, and all of it is the reason a growing team eventually moves the gate onto real accounts.