Skip to content

fix(auth): keep the session across a server action redirect - #604

Merged
alukach merged 1 commit into
mainfrom
fix/sa-logout
Oct 1, 2026
Merged

alukach merged 1 commit into
mainfrom
fix/sa-logout

Conversation

@alukach

@alukach alukach commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Problem

After creating a service account (and after creating a product, #296), the page you land on shows "Log In / Register" as if you had been signed out. Reloading fixes it.

Cause

When a server action calls redirect(), Next.js doesn't send a bare redirect: it renders the target page itself with an internal fetch and streams that back (createRedirectRenderResult in next/dist/server/app-render/action-handler.js). The cookie header for that fetch is rebuilt by getForwardedHeaders through RequestCookies.toString(), which runs every value through encodeURIComponent. Ory's session cookie is padded base64, so its = becomes %3D, whoami rejects the cookie, getPageSession() returns null, and the header and the settings layout render signed out.

Fix

The middleware runs on that internal fetch too, so it now decodes Ory's session cookies (ory_*session*) before anything downstream reads them. Ory's values are base64url and never contain %, so decoding restores the original exactly; other cookies are left as they are. Fixing it in one place covers getServerSession, the step-up guard and the proxy-credentials reader without touching their test mocks.

Testing

  • npx jest src/middleware.test.ts: new cases for the encoded and unencoded headers pass.
  • npm run type-check passes.
  • Not yet tried in a browser. The local dev server wasn't running, so I worked from Next's source rather than reproducing it. To confirm: create a service account on the preview and check that the page you land on still shows your account menu.

Docs and ADRs

No user-facing flow or platform decision changes. Checked docs/using-source/ and the data-proxy ADRs; none describe this.

Fixes #296

🤖 Generated with Claude Code

When a server action calls redirect(), Next.js renders the target page with an internal fetch whose cookie header it rebuilds through RequestCookies.toString(), which percent-encodes every value. The base64 padding in Ory's session cookie becomes %3D, Ory rejects the cookie, and the page the action lands on renders signed out — after creating a service account, and after creating a product (#296). The middleware now decodes Ory's session cookies before anything reads them; their values are base64url, so the decode is lossless.

Fixes #296

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
source-coop-ui Ready Ready Preview Oct 1, 2026 5:13am UTC
source-cooperative Ready Ready Preview Oct 1, 2026 5:13am UTC

Request Review

@claude

claude Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @alukach's task in 19s —— View job


✅ No blocking issues — safe to merge.

The fix sits at the right choke point. The middleware runs on Next's internal redirect fetch, so getServerSession and the other cookie readers all see the repaired header. The tests cover both the encoded and unencoded cases. I haven't run the code or the tests. I only read the diff.

Non-blocking notes

  • src/middleware.ts:122: decodeURIComponent throws URIError on a malformed escape, for example a cookie ory_session_x=%zz. The middleware would then return a 500 on every matched page until the cookie is cleared. A bad cookie can only come from the client's own browser, so the impact is small. A guard is cheap:
    (_, name: string, value: string) => {
      try { return name + decodeURIComponent(value); } catch { return name + value; }
    }
  • src/middleware.ts:120: \bory_ also matches inside a name like my-ory_session=…, because - counts as a word boundary. Anchoring on (^|;\s*) would match only real cookie names.
  • src/middleware.ts:158: the handleProductAnalyticsTab early return skips the repair. A server action that redirects to /products/...?tab=analytics would still land signed out. This is an unlikely path, but moving the repair above that call would cover it.
  • The PR description says the fix hasn't been tried in a browser. Worth confirming on the preview before merging.

Simplify (ponytail)

  • Nothing to cut. This is a small, single-purpose diff.

Docs

  • The description names the docs and ADRs it checked and says why they still hold. This change touches neither a documented user flow nor an ADR decision. Nothing further is needed.

💰 Estimated review cost: $0.12 · 0m18s · 6 turns

@alukach
alukach marked this pull request as ready for review October 1, 2026 05:15
@alukach
alukach merged commit 2572d85 into main Oct 1, 2026
11 checks passed
@alukach
alukach deleted the fix/sa-logout branch October 1, 2026 05:16

This branch was successfully deployed

2 active deployments
Preview – source-cooperative — e2374acd Deployed Oct 1, 2026 by vercel[bot]
Preview – source-coop-ui — e2374acd Deployed Oct 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] UI indicates user is logged out after creating a product

1 participant