One-Click Demo Sign-In Button (Devise) | LlamaPress Cookbook
LlamaPress
Cookbook
Auth Stable

One-Click Demo Sign-In Button (Devise)

A "Sign into Demo" button on the Devise sign-in page that logs a visitor into a shared demo account with one click — a hidden pre-filled sign-in form, no routes or controller changes.

Proven on leo-tozeb.leo.llamapress.ai view

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

  1. Create (or pick) a demo user whose data is safe to show anyone.
  2. 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).
  3. In that view, render a form_for pointed at the normal Devise session_path, with hidden_field :email and hidden_field :password pre-filled with the demo credentials, and one styled submit button.
  4. Keep render "devise/shared/links" below it so real sign-in, sign-up, and password reset still work.
  5. Done — Devise's sessions#create handles 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_to the session path (GET) 404s or redirects; a hand-rolled <form> without the authenticity token gets rejected. form_for handles both — use it.
  • The param nesting matters. sessions#create reads params[:user][:email]. form_for(resource, as: resource_name, ...) nests the hidden fields correctly; form_tag with bare email/password fields 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 :lockable can 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 for locked_at being set.
  • View override needs no generator. Creating the one file at app/views/devise/sessions/new.html.erb shadows the gem's view. Do not run rails generate devise:views just 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 = :get or a custom after_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

  1. Swap demo@example.com / 123456 for your demo account's real credentials — remember both places must match (seed + hidden fields).
  2. 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.
  3. Branding panel is optional dressing — the pattern is only the form_for block. For a minimal version, drop the two-column layout and keep the form plus devise/shared/links.
  4. Want several demo roles (e.g. "View as manager" / "View as staff")? Repeat the form_for block once per role, each with its own demo account's credentials.
  5. To retire the demo button later, delete the override file — Devise's stock sign-in page comes back on its own.

Related