Skip to content

Incident: login loop after a cookie change

The cookie is in the jar; Strict keeps it off every hop of a trip another site's page started.

Predict first

Before you read: predict

PAGE · 08:40, ECON-201 opens at 09:00 · every candidate who signs in with the university account ends on Chrome's "redirected you too many times" page · examiners who sign in with an ExamHall password get in

Release 2026.10.19-2 went out at 08:15. Its whole diff is two Set-Cookie lines:

- Set-Cookie: oauth_state=…; Path=/auth; Secure; HttpOnly; SameSite=Lax
+ Set-Cookie: oauth_state=…; Path=/auth; Secure; HttpOnly; SameSite=Strict
- Set-Cookie: __Host-session=…; Path=/; Secure; HttpOnly; SameSite=Lax
+ Set-Cookie: __Host-session=…; Path=/; Secure; HttpOnly; SameSite=Strict

/exam without a session sends the browser to /auth/start, which sets oauth_state and sends it on to the university's sign-in at https://sso.uni.test. Candidates are already signed in there, so it answers at once with a 302 back to https://exam.examhall.test/auth/callback. The callback compares the returned state with oauth_state: on a match it sets __Host-session and answers 302 to /exam, otherwise 302 to /auth/start. Candidates open the exam from the link on their course page at https://lms.uni.test.

The on-call engineer logs the cookie names on each /auth/callback request and, when the first one arrives, reads one candidate's cookie jar in DevTools (Application → Cookies).

What does she find?

Pick one

The 30-second version

A login loop right after a cookie change is usually SameSite. Strict withholds cookies from a navigation another site's page started, and redirects keep that start, even back on your host. Read the callback's withheld cookies, not Sec-Fetch-Site. Give oauth_state Lax (None; Secure for form_post), then let the session survive the callback's redirect.

The cookie is in the jar; Strict keeps it off every hop of a trip another site's page started.

How it works

Step 1 of 13

Split the symptom. SSO users loop while password users get in, so the break sits on hops where another site sends the browser to you.
In the kitchen
  • staff who never leave the paddock walk straight in → password sign-in (step 1)

Analogy: A race circuit's paddock gate

A mechanic arriving at the paddock gets a numbered gate slip from the gate marshal and is sent across the access road to the series office for a stamped pass. A new team memo says: "show your slip only at a gate an errand from inside the paddock led you to."

At the circuit In the incident
the numbered gate slip in her pocket oauth_state in the jar (step 7)
the series office across the road, stamping passes the IdP (step 4)
"only at a gate an errand from inside the paddock led you to" Strict: who started the trip (step 5)
the office says "go back to your gate", so the errand is now the office's the 302 back keeps the IdP's start (step 5)
the marshal finds no slip, sends her back to the office, which stamps at once a failed state check restarting the flow; auto-SSO (step 4)
she gives up after twenty trips the browser's redirect limit
staff who never leave the paddock walk straight in password sign-in (step 1)
the memo amended: "show it at any gate you walk up to" Lax on oauth_state (step 9)
the marshal walks her to her garage himself the callback's own page that moves on (step 12)

Breaks down when: a mechanic sent round twice would ask what is wrong; a browser applies the rule to every hop and stops only at its redirect limit, or never, when each return is a fresh page.

Contrasting case: a release that sets SameSite=None without Secure. The same loop appears, but the browser refused oauth_state at Set-Cookie: the jar never lists it and nothing is reported withheld at the callback.

In real terms: for each request the browser takes the cookies it keeps for the URL; when another site's page started the navigation, it drops the Strict ones, and the Lax ones too unless the hop is a top-level GET.

Examples, run for real

4.1 ExamHall EXH-3020: the console at 08:40

Release 2026.10.19-2 sets both cookies Strict. One candidate, already signed in at the university, opens ECON-201 from her course page; then the same candidate on the release before it:

4.1 Chromium 153.0.8010.12 · release 2026.10.19-2: oauth_state Path=/auth; Secure; HttpOnly; SameSite=Strict · __Host-session Path=/; Secure; HttpOnly; SameSite=Strict · a candidate signed in at sso.uni.test clicks the exam link on lms.uni.test
4.1 hop 1 · GET exam.examhall.test/exam · Sec-Fetch-Site cross-site · carries none · no session
4.1 hop 2 · GET exam.examhall.test/auth/start · Sec-Fetch-Site cross-site · carries none · issued st-1
4.1 hop 3 · GET sso.uni.test/authorize · Sec-Fetch-Site cross-site · carries idp_sid · auto-SSO
4.1 hop 4 · GET exam.examhall.test/auth/callback · Sec-Fetch-Site cross-site · carries none · state cookie missing
4.1 hop 5 · GET exam.examhall.test/auth/start · Sec-Fetch-Site cross-site · carries none · issued st-2
4.1 DevTools protocol, first /auth/callback request: sent none · withheld oauth_state (SchemefulSameSiteStrict)
4.1 cookie jar for exam.examhall.test/auth/callback when the first callback arrives: oauth_state
4.1 ended: net::ERR_TOO_MANY_REDIRECTS after 6 callbacks, none carrying oauth_state
4.1 rollback to 2026.10.19-1 (oauth_state Path=/auth; Secure; HttpOnly; SameSite=Lax · __Host-session Path=/; Secure; HttpOnly; SameSite=Lax), same candidate: signed in on callback 1, which carried oauth_state; /exam then carried __Host-session

The LMS page started the navigation, so every hop back on ExamHall's host is cross-site to Strict (step 5). The jar keeps oauth_state, and Chromium reports it withheld with its reason (steps 7–8). Rolling back ends the loop (step 13).

4.2 Chat MR !3320: the test that would have caught it

A chat app shipped the same change in release 7.0. MR !3320 adds a browser test that starts where users start, on another site's page, and runs it against both builds:

4.2 MR !3320 adds the test "signs in from another site's link": a fresh browser signed in at login.chatid.test clicks the chat link on portal.acmecorp.test; it passes when /rooms answers with sid after one callback
4.2 on release 7.0 (oauth_state Path=/auth; Secure; HttpOnly; SameSite=Strict · sid Path=/; Secure; HttpOnly; SameSite=Strict): FAIL · ended too many redirects · callbacks 6, carrying none
4.2 on MR !3320 (oauth_state Path=/auth; Secure; HttpOnly; SameSite=Lax · sid Path=/; Secure; HttpOnly; SameSite=Lax): PASS · ended signed in · callbacks 1, carrying oauth_state

The test fails on the release and passes on the fix, because it starts the trip from another site's link (step 13).

Try it

Answer first, then check. Nothing is saved.

Question 1 of 7

A chat app at https://app.chatco.test signs users in through https://login.chatid.test, which answers at once with a 302 for a user already signed in there. Release 7.0 changed two lines:

- Set-Cookie: oauth_state=…; Path=/auth; Secure; HttpOnly; SameSite=Lax
+ Set-Cookie: oauth_state=…; Path=/auth; Secure; HttpOnly; SameSite=Strict
- Set-Cookie: sid=…; Path=/; Secure; HttpOnly; SameSite=Lax
+ Set-Cookie: sid=…; Path=/; Secure; HttpOnly; SameSite=Strict

/rooms without sid sends the browser to /auth/start, and a failed state check does the same. Users who open the chat from the link on their company portal, https://portal.acmecorp.test, now end on "redirected you too many times". To reproduce it, the on-call engineer, already signed in at login.chatid.test, types https://app.chatco.test/rooms into her own browser's address bar.

What happens on her own browser?

Pick one

Question 2 of 7

A streaming player at https://watch.streamco.test signs viewers in through https://auth.streamid.test, which answers at once with a 302 for a viewer already signed in there. Release 3.4 set SameSite=Strict on both of its cookies, and viewers who opened the player from the newsletter page https://news.streamletter.test looped. Hotfix 3.4.1 changes one line back:

- Set-Cookie: oauth_state=…; Path=/auth; Secure; HttpOnly; SameSite=Strict
+ Set-Cookie: oauth_state=…; Path=/auth; Secure; HttpOnly; SameSite=Lax

sid=…; Path=/; Secure; HttpOnly; SameSite=Strict is unchanged. On a matching state the callback sets sid and answers 302 to /watch; /watch without sid answers 302 to /auth/start. A viewer signed in at the IdP opens the player from the newsletter link.

Where does her sign-in end?

Pick one

Question 3 of 7

A ticketing site at https://tix.ticketco.test signs fans in through https://sso.ticketid.test with response_mode=form_post: for a fan already signed in there, the IdP's page posts code and state straight back to /auth/callback. After a loop incident the team ships:

Set-Cookie: oauth_state=…; Path=/auth; Secure; HttpOnly; SameSite=None
Set-Cookie: sid=…; Path=/; Secure; HttpOnly; SameSite=Lax

On a matching state the callback sets sid and answers 302 to /tickets; /tickets without sid answers 302 to /auth/start. A fan signed in at the IdP clicks the "My tickets" link on the fan forum https://forum.fanzone.test.

What does she get?

Pick one

Question 4 of 7

A payments dashboard at https://pay.ledgerco.test signs merchants in through https://idp.ledgerid.test, which answers at once with a 302 for a merchant already signed in there. After a release that changed one line, the Set-Cookie line of oauth_state, merchants who open the dashboard from the link in their webmail, https://mail.webmail.test, loop on "redirected you too many times", and every /auth/callback logs "state cookie missing". On a looping merchant's browser, the on-call engineer reads DevTools when the first callback arrives:

Application → Cookies, pay.ledgerco.test: oauth_state
Network → /auth/callback → Headers: Sec-Fetch-Site: cross-site · Cookie: (no cookie)
Network → /auth/callback → Cookies, filtered out: oauth_state (NotOnPath)

Which hypothesis does it support?

Pick one

Question 5 of 7

In the interview you are given EXH-3020's page and asked: "SSO users are stuck in a redirect loop after a release. Password login works.

Why?" What do you answer, in at most 150 words?

Question 6 of 7

For next term the LMS team plans to put ExamHall's password form on its staff page, https://lms.uni.test. The form posts the examiner's password straight to https://exam.examhall.test/password-login, which sets __Host-session and answers 302 to /exam. With release 2026.10.19-2's two cookie lines, an examiner submits that form.

What does the /exam request after the post carry?

Pick one

Question 7 of 7

The chat app at https://app.chatco.test signs users in through https://login.chatid.test with response_mode=form_post: for a user already signed in there, its page posts code and state straight back to /auth/callback. It sets oauth_state=…; Path=/auth; Secure; HttpOnly; SameSite=Lax, and security requires sid=…; Path=/; Secure; HttpOnly; SameSite=Strict to stay as it is. On a matching state the callback sets sid and answers 302 to /rooms; /rooms without sid and a failed check send the browser to /auth/start. Users who open the chat from the link on https://portal.acmecorp.test loop, every callback logging "state cookie missing". At 09:10 a hotfix sets oauth_state=…; Path=/auth; Secure; HttpOnly; SameSite=None. At 09:30 a second one makes the callback answer 200 with its own page, which refreshes to /rooms.

What do users who start from the portal's link see in each window?

Pick one

Go deeper

Pro tips
  1. Open the callback's cookies in DevTools. Select the /auth/callback request in the Network panel and open its Cookies tab; a withheld oauth_state names the cause (curriculum row's own tip). When: the first minutes of a loop, before reading code (steps 7–8). Status: EXECUTED (DevTools protocol, headless); the panel itself is DOC, from Chrome DevTools' Network features reference.
  2. Log cookie names, never values, at the callback. One line per callback with the names it carries and the state check's result ("state ok", "state cookie missing") turns a loop into a count you can alert on. When: step 13, before the next release. Verify: every script here logs only names and the result. Status: EXECUTED.
Source

Browser cookie behaviour changes between versions: the facts below are as of 2026-10-06, and every executed fact is Chromium 153.0.8010.12's. No item is keyed on a source; every key is executed. Valid as of 2026-10-06.

  • IETF, Cookies: HTTP State Management Mechanism, draft-ietf-httpbis-rfc6265bis-22 (December 2025, the latest revision on the datatracker page): https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-22.txt (rfc6265bis-22.txt), from the curriculum row's source https://datatracker.ietf.org/doc/draft-ietf-httpbis-rfc6265bis/ (datatracker-rfc6265bis.txt). Quoted, §5.6.7.1: "Same-site cookies in "Strict" enforcement mode will not be sent along with top-level navigations which are triggered from a cross-site document context."; "a request's "safe"ness is determined based on the method of the current redirect hop." §5.6.7.2: "All cookies, regardless of SameSite enforcement mode, may be set for top-level navigations, regardless of HTTP request method". §8.8.2: "Developers can avoid this confusion by adopting a session management system that relies on not one, but two cookies: one conceptually granting "read" access, another granting "write" access." Supports steps 5, 7, 11 and 12.
  • web.dev, SameSite cookies explained: https://web.dev/articles/samesite-cookies-explained (webdev-samesite.txt), the row's first source. Quoted: "if the user follows a link into your site from another one, the cookie isn't sent on that initial request."; "Cookies with SameSite=None must also specify Secure". Supports steps 5 and 9 and the contrasting case.
  • IETF, RFC 9700, Best Current Practice for OAuth 2.0 Security: https://www.rfc-editor.org/rfc/rfc9700.txt (rfc9700.txt), the row's third source. Quoted: "The state value SHOULD be invalidated by the client after its first use at the redirection endpoint."; "Use the form post response mode instead of a redirect for the authorization response". Supports the lab's callback (it clears oauth_state after a match) and why an IdP returns with form_post (step 9).
  • OpenID, OAuth 2.0 Form Post Response Mode: https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html (oidc-form-post.txt). Quoted: "Authorization Response parameters are encoded as HTML form values that are auto-submitted in the User Agent, and thus are transmitted via the HTTP POST method to the Client". Supports step 9 and the lab's form_post page.
  • Quoted: "If that response redirects all the way back to https://example.com/ , the final request's Sec-Fetch-Site value would still be cross-site". Supports step 8.
  • Chrome DevTools, Network features reference: https://developer.chrome.com/docs/devtools/network/reference (devtools-network-reference.txt), named by the row's tip. Quoted: "To view the cookies sent in a request's HTTP header: Select a request under the Name column in the Requests table. Open the Cookies tab." Supports pro tip 1.

Checked against the tools and docs as of 2026-10-06.