---
title: One-Click Demo Sign-In Button (Devise)
slug: one-click-demo-sign-in
category: Auth
summary: 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.
tags: [devise, auth, demo, onboarding, sales]
status: stable
visibility: public
source_project: leo-tozeb.leo.llamapress.ai
layers: [view]
related:
  - title: Password Show/Hide Toggle
    url: /cookbook/password-show-hide-toggle
    summary: Another small Devise sign-in page upgrade, if you keep a real credential form alongside the demo button.
---

# 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.

```ruby
# 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.

```erb
<%# 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.
