Post-login Destination
These rules decide where a user who arrives at /session/new goes after they log in successfully. A single controller handles the regular-user, developer, and RP flows, so without understanding the branching logic you may wonder, "Why did I suddenly get bounced to a different host?"
One-line summary
| Condition | Destination |
|---|---|
return_to=/oauth/authorize?... (RP-initiated SSO) | same-host /oauth/authorize (resume the RP flow) |
return_to=/something-else (a stored protected page) | the same-host path |
No return_to (logged in after a direct visit) | cross-host handoff → https://start.1pass.dev/account |
Why a cross-host handoff
/account is host-locked to start.1pass.dev (console_host_constraint), while /session/new exists only on api.1pass.dev. Each host has its own independent session cookie (the cookie domain cannot be widened to .1pass.dev because of the embed.1pass.dev iframe isolation), so to view the same session as authenticated on both hosts you have to move the cookie across with a single-use handoff token (SessionHandoffToken).
Flow (production)
api.1pass.dev/session/new
│ (POST /session, no return_to)
▼
SessionsController#create
│
├─ SessionHandoffToken.issue!(target_host: "start.1pass.dev")
├─ cookies.delete(:session_id) # single auth artifact
▼
302 → https://start.1pass.dev/session/handoff?token=...&return_to=%2Faccount
│
▼
SessionHandoffsController#show
│
├─ SessionHandoffToken.consume!(token, host: "start.1pass.dev")
├─ cookies.signed[:session_id] = session.id # cookie on the start host
▼
302 → /account (start.1pass.dev)Flow (dev/test)
Outside production, console_host_constraint matches every host, and there is no per-host cookie separation. So the controller does a same-host redirect to the /account path without a handoff token. The controller branches automatically (Rails.env.production?).
The RP flow is never disrupted
When a user enters /oauth/authorize, the Authentication concern stores return_to and redirects to /session/new. After login, because return_to is present, the user does not fall into the cross-host handoff and instead returns to the same-host /oauth/authorize.
# sessions_controller.rb#complete_login (HTML format)
if (target = post_login_redirect_path) # explicit / stored return_to
redirect_to target # same-host
else
redirect_to handoff_to_account_url(...) # cross-host /account
endRegression-guard spec: spec/integrations/krx_listing_rp_integration_spec.rb
- "post-login fallback does NOT hijack RP flow (return_to is present)"
Developers are regular users too
User is a single model, and the role field determines developer? / admin?. Even when a developer goes directly to /session/new and logs in, they land on /account (everyone sees the same page). From there, the "Developer Console →" link in the top-right corner is the entry point to /developer.
→ This is why putting the post-login fallback at /account feels natural for both kinds of users.
Pitfalls
post_login_redirect_pathcan returnnil. There used to be a|| developer_root_pathfallback, but it was removed because it created inconsistent permission-gate behavior for regular users. The fallback branch is now invoked explicitly by the caller (complete_login) as a handoff call.- The cookie is deleted only on the production branch (
Rails.env.production?). Deleting it outside production would send the user to the same-host/account, drop their authentication, and create an infinite loop. SessionHandoffToken#consume!includes a host-match check — attempting to redeem anapitoken onstartis rejected.
Related code
app/controllers/sessions_controller.rb#complete_loginapp/controllers/sessions_controller.rb#handoff_to_account_urlapp/controllers/session_handoffs_controller.rbapp/models/session_handoff_token.rbapp/policies/handoff_policy.rbconfig/initializers/cross_host_handoff.rb
Related specs
spec/requests/sessions_spec.rb#"HTML fallback when no return_to"spec/requests/session_handoffs_spec.rbspec/policies/handoff_policy_spec.rbspec/integrations/krx_listing_rp_integration_spec.rb