fix: let an SSO service refresh its session through its provider's frame - #17
Open
marcioviniciusspiridigliozzi-dot wants to merge 1 commit into
Conversation
Microsoft Teams cannot be signed into from inside Chorus. Its own "you need to sign in again" banner leads nowhere: the button reaches no login gateway, restarting does not clear it, and the state comes back about a day after any successful sign-in. The cause is WebKit's Intelligent Tracking Prevention. ITP treats a cookie read from an iframe on another registrable domain as third-party and blocks it. Enterprise SSO is built on exactly that: the service embeds a hidden frame pointed at its identity provider and reads the session cookie from there. With the cookie blocked the silent refresh can only fail, and the service falls back to telling the user to sign in again, which runs the same silent flow and fails the same way. What the page reports while this happens: AADSTS50058: a silent sign-in request was sent but no user is signed in followed by Teams loading its own report-third-party-cookies.html. The roughly-daily rhythm is ITP's own: it grants third-party cookie access for 24 hours after a first-party user interaction, so the service works right after a sign-in and stops once that window closes. Nothing inside the app can end the loop, because ITP's record is on disk and no button in the page reaches a real sign-in. Turning ITP off per store costs little that this app's isolation is not already paying for. Every service gets its own WKWebsiteDataStore, so one service can never read another's cookies whatever ITP does. What ITP adds on top is blocking third-party cookies *within* a single service's own container, where the only participants are that service and the identity provider it chose. What is genuinely given up is stated in the code comment rather than glossed: inside one service's container, an embedded ad network can keep a cookie across the sites it appears on there. The content blocker already drops most of those requests and the blast radius stops at the one service, but whether that trade belongs on by default or behind a per-service toggle is the project's call, not mine to assume. The code is written so either answer is a small change. The setter is SPI, so it is probed before use and its effect is read back, with a warning logged if either step fails. It also takes a scalar BOOL, so it has to be called through its implementation with that signature: perform(_:with:) passes an object pointer in the argument slot and any non-nil pointer reads as a true BOOL, which turns ITP on rather than off and leaves no trace. That cost me a day, so the comment says it plainly. Verified by hand: with this in place Teams signed in and stayed signed in across restarts and past the 24-hour mark, with no new login prompted. I have no second tenant to test against, so a confirmation from someone else on an Entra tenant would be worth having before this is trusted broadly.
marcioviniciusspiridigliozzi-dot
force-pushed
the
fix/sso-third-party-cookies
branch
from
August 11, 2026 10:27
2f0488d to
b9d4b75
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Microsoft Teams cannot be signed into from inside Chorus. Its own "you need to sign in again" banner leads nowhere: the button reaches no login gateway, restarting does not clear it, and the state comes back about a day after any successful sign-in.
The cause
WebKit's Intelligent Tracking Prevention treats a cookie read from an iframe on another registrable domain as third-party, and blocks it. Enterprise SSO is built on exactly that: the service embeds a hidden frame pointed at its identity provider and reads the session cookie from there.
With the cookie blocked the silent refresh can only fail, so the service falls back to telling the user to sign in again — which runs the same silent flow and fails the same way.
What the page reports while this happens:
followed by Teams loading its own
report-third-party-cookies.html.The roughly-daily rhythm is ITP's own. It grants third-party cookie access for 24 hours after a first-party user interaction, so the service works right after a sign-in and stops once that window closes. That periodicity is what convinced me — I had two other theories first, and both were wrong.
Nothing inside the app can end the loop. ITP's record is on disk, so restarting does not clear it, and no button in the page reaches a real sign-in.
What this gives up, stated plainly
Turning ITP off per store costs little that this app's isolation is not already paying for. Every service gets its own
WKWebsiteDataStore, so one service can never read another's cookies whatever ITP does. What ITP adds on top is blocking third-party cookies within a single service's own container, where the only participants are that service and the identity provider it chose.What is genuinely given up: inside one service's container, an embedded ad network can keep a cookie across the sites it appears on there. The content blocker already drops most of those requests, and the blast radius stops at the one service.
Whether that trade belongs on by default or behind a per-service toggle is your call, not mine to assume. I have it on by default here because the failure it fixes is total — the service is unusable — but the code is written so either answer is a small change. Say the word and I will move it behind a setting.
Implementation notes
The setter is SPI, so it is probed before use and its effect is read back, with a warning logged if either step fails. If a macOS release drops it, SSO services break the way they do today and the log says why.
It also takes a scalar
BOOL, so it has to be called through its implementation with that signature.perform(_:with:)passes an object pointer in the argument slot, and any non-nil pointer reads as a trueBOOL— which turns ITP on rather than off, silently. That cost me a day, so the comment says so plainly rather than leaving the next person to find it.Verification, and its limits
By hand: with this in place Teams signed in and stayed signed in across restarts and past the 24-hour mark, with no new login prompted. I also checked it does not cost persistence elsewhere — zero storage errors on the services I run.
I only have one Entra tenant to test against, so I would not call this proven. If anyone on another tenant can confirm, that would be worth having before you trust it broadly. I noticed #14's reporter is on Teams and responsive — they may be willing.
Note on CI
This branches from
main, which currently has an intermittentStoreRepairtest flake; I sent a fix for it in #15. A red run here is most likely that rather than this change.