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