One-Click Demo Sign-In Button (Devise)
⚠️ Cookbook example — not live code. (KEEP THIS CALLOUT.) Every code block below is an example snippet, not part of the llamapress.ai codebase, and not running on this server. This is a reference recipe for a Leo instance (an AI coding agent) to implement in its own app — read it to understand the pattern, then recreate it there.
You are demoing an app to a prospect. You do not want to read an email and password over a call, and you do not want them to give up at the sign-in wall. This pattern puts one big "Sign into Demo" button on the sign-in page. One click signs the visitor into a shared demo account and drops them on the dashboard.
The trick: the button is a normal Devise sign-in form. The email and password are
hidden fields, pre-filled with the demo account's credentials. Clicking the button
POSTs them through Devise's standard sessions#create. No new route, no new
controller, no custom auth code — the whole feature is one view file.
When to use: demo boxes, prospect walkthroughs, internal previews — any app where everyone who reaches the sign-in page is allowed to see the demo data. When not to: any app with real user data. The credentials sit in the page's HTML source, so this button is a public door. Never point it at an admin or real account on a production app.
The 80/20 in one breath
- Create (or pick) a demo user whose data is safe to show anyone.
- Override the Devise sign-in view: create
app/views/devise/sessions/new.html.erb(a file at this path shadows the gem's built-in view — no generator needed). - In that view, render a
form_forpointed at the normal Devisesession_path, withhidden_field :emailandhidden_field :passwordpre-filled with the demo credentials, and one styled submit button. - Keep
render "devise/shared/links"below it so real sign-in, sign-up, and password reset still work. - Done — Devise's
sessions#createhandles the POST like any other sign-in.
Layer 1 — The demo user (seed)
Devise's default minimum password length is 6 characters. Seed idempotently so re-running seeds never errors or resets the account.
# db/seeds.rb
demo = User.find_or_initialize_by(email: "demo@example.com")
if demo.new_record?
demo.password = "123456" # must match the hidden field in the view, exactly
demo.save!
end
Layer 2 — The View (the whole feature)
Create the file — its presence alone overrides the gem's sign-in page. This is the proven layout: branding panel on the left, the demo button on the right, standard Devise links kept underneath.
<%# app/views/devise/sessions/new.html.erb %>
<div class="flex min-h-screen">
<%# ─── Left: branding panel ─── %>
<div class="flex-1 bg-slate-900 flex flex-col items-center justify-center p-12">
<h1 class="text-white text-4xl font-bold tracking-wide">YOUR APP</h1>
<p class="text-white/40 text-sm mt-6 text-center max-w-xs leading-relaxed">
One line about what the app does.
</p>
</div>
<%# ─── Right: sign-in ─── %>
<div class="flex-1 bg-white flex flex-col justify-center p-12">
<div class="max-w-sm mx-auto w-full">
<h2 class="text-2xl font-semibold text-gray-900">Welcome back</h2>
<p class="text-sm text-gray-500 mt-1 mb-6">Sign in to your account</p>
<!-- One-click demo button: a real Devise sign-in form with hidden,
pre-filled credentials. Clicking it POSTs to sessions#create. -->
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<%= f.hidden_field :email, value: "demo@example.com" %>
<%= f.hidden_field :password, value: "123456" %>
<%= f.submit "Sign into Demo",
class: "w-full rounded-lg py-3 px-4 text-sm font-semibold text-white
bg-sky-600 shadow-md hover:shadow-lg active:scale-[0.98]
transition duration-150 focus:outline-none focus:ring-2
focus:ring-offset-2" %>
<% end %>
<div class="relative my-6">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-200"></div>
</div>
</div>
<!-- Keep the standard Devise paths (real sign-in, sign-up, forgot password) -->
<div class="mt-6 text-sm text-center leading-relaxed">
<%= render "devise/shared/links" %>
</div>
</div>
</div>
</div>
That is the entire feature. resource, resource_name, and session_path are Devise
helpers that already exist on this page — form_for(resource, as: resource_name, ...)
produces exactly the user[email] / user[password] params sessions#create expects,
plus the CSRF token.
Gotchas (the hard-won stuff)
- The credentials are public. They are plain text in the page source — "hidden field" means visually hidden, not secret. Treat the demo account as anonymous public access: give it only demo data, never admin rights, never a real person's account.
- It must be a POST form, not a link. Devise sign-in only accepts POST with a CSRF
token. A
link_tothe session path (GET) 404s or redirects; a hand-rolled<form>without the authenticity token gets rejected.form_forhandles both — use it. - The param nesting matters.
sessions#createreadsparams[:user][:email].form_for(resource, as: resource_name, ...)nests the hidden fields correctly;form_tagwith bareemail/passwordfields silently fails with "Invalid email or password." - Password drift fails silently. If anyone changes the demo user's password, the button starts returning "Invalid email or password" with no other symptom. The hidden-field value and the database password must match exactly — check this first when the button "stops working."
- Devise
:lockablecan brick the button. With lockable on, a few mismatched attempts (see password drift above) lock the demo account for everyone. Either keep lockable off the demo user or watch forlocked_atbeing set. - View override needs no generator. Creating the one file at
app/views/devise/sessions/new.html.erbshadows the gem's view. Do not runrails generate devise:viewsjust for this — it dumps every Devise view into the app and you then own all of them. - Sign-out lands back here. After sign-out, Devise redirects to this page — which
is fine, that is the demo loop working. But if you set
config.sign_out_via = :getor a customafter_sign_out_path_for, re-test the loop.
Files this pattern touches
app/views/devise/sessions/new.html.erb # the whole feature (created; overrides the gem view)
db/seeds.rb # idempotent demo-user seed
How to adapt to your schema
- Swap
demo@example.com/123456for your demo account's real credentials — remember both places must match (seed + hidden fields). - If your Devise model is not
User(e.g.Admin), nothing changes —resource/resource_name/session_path(resource_name)resolve to the right scope on that scope's sign-in page. - Branding panel is optional dressing — the pattern is only the
form_forblock. For a minimal version, drop the two-column layout and keep the form plusdevise/shared/links. - Want several demo roles (e.g. "View as manager" / "View as staff")? Repeat the
form_forblock once per role, each with its own demo account's credentials. - To retire the demo button later, delete the override file — Devise's stock sign-in page comes back on its own.