← All posts

July 5, 2026

My session died after every deploy

I went looking for why auth on antelo.io felt fragile around deploys.

The first question was simple: how long does the session last?

The answer was: as long as the browser session.

Rails was using the default cookie store with no expire_after, which means the session cookie had no explicit lifetime. That is easy to miss because everything works fine until it does not. You close the browser, reopen it later, or hit some browser edge case around a restart, and you are back on the login screen.

Then I looked at the other half of the problem: what signs the session cookie.

This app uses cookie sessions and passwordless magic links. Both depend on secret_key_base. If that secret changes, every signed session dies instantly, and any outstanding magic link dies with it.

My deploy setup only injected RAILS_MASTER_KEY into the container and let Rails resolve secret_key_base implicitly. That can work, but I do not want auth continuity to depend on implicit behavior during deploys. I want the runtime secret to be explicit.

So I changed two things.

1. Make the session cookie durable

# config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store,
                                       key: '_antelo_session',
                                       expire_after: 30.days

Now the cookie carries a real expiry date instead of living only for the current browser session.

2. Inject SECRET_KEY_BASE explicitly at deploy time

# config/deploy.yml
env:
  secret:
    - RAILS_MASTER_KEY
    - SECRET_KEY_BASE
# .kamal/secrets
RAILS_MASTER_KEY=$(cat config/master.key)
SECRET_KEY_BASE=$(bundle exec rails credentials:fetch secret_key_base)

That removes ambiguity. The container now boots with the same signing secret every time.

What I verified

I added a regression test for both expectations:

  • the session config has expire_after: 30.days
  • the deploy config injects SECRET_KEY_BASE

Then I checked the real response header locally after signing in through the magic link flow. The Set-Cookie header now includes an expires= attribute 30 days out.

That was the missing proof I wanted.

The lesson

Cookie auth is simple right up until it is invisible.

If your Rails app uses cookie_store, decide two things on purpose:

  1. how long the cookie should live
  2. what exact secret signs it in production

If you leave either one implicit, you are borrowing trouble.