Skip to content

Commit fb99cfe

Browse files
camreevesclaude
andcommitted
fix(session): derive COOKIE_SESSION_SECRET from PLACE_SERVER_SECRET
When COOKIE_SESSION_SECRET is unset the service generated an ephemeral per-boot key, so sessions did not survive a restart and did not match across replicas — the PlaceOS platform does not provide that env var (the Ruby auth used SECRET_KEY_BASE), so a real deployment logs every user out on each redeploy and fails intermittently behind >1 replica. Fall back to a stable key derived (SHA-256, namespaced) from PLACE_SERVER_SECRET, which the platform always provides and which is identical across restarts and replicas — so sessions persist with no new deployment config. Ephemeral generation remains only when neither secret is set (a bare dev box). Verified in a local platform stack: a session issued before an auth container restart is still accepted afterwards. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 7883020 commit fb99cfe

1 file changed

Lines changed: 22 additions & 5 deletions

File tree

src/constants.cr

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require "digest/sha256"
2+
13
module PlaceOS::Auth
24
APP_NAME = "auth"
35
API_VERSION = "v2"
@@ -40,11 +42,26 @@ module PlaceOS::Auth
4042

4143
# Secret used by `ActionController::Session::MessageEncryptor` to
4244
# encrypt + sign the session cookie. Must be at least 32 bytes for
43-
# AES-256. In production this MUST be set; the dev fallback generates
44-
# a new key per boot, which deliberately invalidates existing cookies.
45-
COOKIE_SESSION_SECRET = ENV["COOKIE_SESSION_SECRET"]? || begin
46-
Log.warn { "COOKIE_SESSION_SECRET not set — generating an ephemeral key, sessions will not survive a restart" } unless PROD
47-
Random::Secure.hex(32)
45+
# AES-256.
46+
#
47+
# Resolution order:
48+
# 1. `COOKIE_SESSION_SECRET` if explicitly provided.
49+
# 2. Otherwise derive a stable key from `PLACE_SERVER_SECRET` — the
50+
# platform always provides it, it is stable across restarts and
51+
# identical across replicas, so sessions survive a redeploy and a
52+
# cookie issued by one instance is accepted by another without any
53+
# new deployment config. It is SHA-256'd (not used raw) so it is
54+
# namespaced away from its settings-encryption use and always the
55+
# right length.
56+
# 3. Only if neither is set (a bare dev box) do we fall back to an
57+
# ephemeral per-boot key, which deliberately invalidates cookies.
58+
COOKIE_SESSION_SECRET = ENV["COOKIE_SESSION_SECRET"]?.presence || begin
59+
if server_secret = ENV["PLACE_SERVER_SECRET"]?.presence
60+
Digest::SHA256.hexdigest("auth.cr/cookie-session/#{server_secret}")
61+
else
62+
Log.warn { "neither COOKIE_SESSION_SECRET nor PLACE_SERVER_SECRET set — generating an ephemeral key, sessions will not survive a restart" } unless PROD
63+
Random::Secure.hex(32)
64+
end
4865
end
4966

5067
# OIDC issuer claim. Kept as "POS" so existing services keep validating

0 commit comments

Comments
 (0)