Checked on 2026-08-24.
You get the framing headers right. No X-Frame-Options, no frame-ancestors shutting you out, and the iframe loads. Then the app inside it shows you a login screen — the same app you're signed into in the next tab.
Nothing's broken. You cleared the first gate and walked into the second. The browser decides whether a page may be framed using response headers. It decides whether that page arrives with your session using a separate set of rules, about cookies. The two decisions never consult each other, and the second is stricter.
Our measurement makes the point without argument. In the 60-site data set behind can I embed this site in an iframe?, tested on 2026-08-24, Todoist sent no X-Frame-Options and no frame-ancestors — a clean pass on framing. The URL that answered was https://app.todoist.com/auth/login. Permission to frame a login page is still only permission to frame a login page.
SameSite, in three values
The SameSite cookie attribute decides most of this. It's specified in the IETF draft draft-ietf-httpbis-rfc6265bis, which obsoletes RFC 6265 if approved — worth knowing that the definitive text for a control this important is still a working draft.
The draft's summary is compact: "If the 'SameSite' attribute's value is 'Strict', the cookie will only be sent along with 'same-site' requests. If the value is 'Lax', the cookie will be sent with same-site requests, and with 'cross-site' top-level navigations... If the value is 'None', the cookie will be sent with same-site and cross-site requests." The rule runs the other way too: such cookies "cannot be set in responses to cross-site subresource requests, or cross-site nested navigations" — and framing an app is the latter.
MDN spells out the consequence of Lax exactly. A Lax cookie rides along with a cross-site request only when that request "is a top-level navigation: this essentially means that the request causes the URL shown in the browser's address bar to change." MDN then names the exclusions, and one is "navigations inside <iframe> elements."
Loading an app into your frame doesn't change the address bar. It isn't a top-level navigation. The Lax cookie stays home.
| Value |
In a cross-site frame |
Strict |
Never sent |
Lax |
Never sent |
None; Secure |
Sent, if the browser allows third-party cookies at all |
| Absent |
Treated as a default the draft describes as equivalent to Lax |
Three further details are worth having.
None without Secure isn't a cookie. The storage algorithm says so: if the same-site flag is None, "abort this algorithm and ignore the cookie entirely unless the cookie's secure-only-flag is true." Miss Secure and the browser doesn't weaken the cookie — it discards it.
An unrecognized value falls back to the strict side. The draft: a value "other than these three known keywords... will be subject to a default enforcement mode that is equivalent to 'Lax'." That's the opposite of X-Frame-Options, where an unknown value means no protection at all. The framing header fails open; the cookie attribute fails closed.
The ancestor chain counts, not just the top page. A document's "site for cookies" is the top-level origin "if and only if the top-level origin is same-site with the document's origin, and with each of the document's ancestor documents' origins." Otherwise it becomes an opaque origin, which nothing matches.
When the browser blocks third-party cookies outright
SameSite=None; Secure says the cookie's willing to travel cross-site. Whether it does is a separate question, and it belongs to the browser and the person using it.
The draft is plain here: "most user agents now limit third-party cookies in a variety of ways. Some completely block third-party cookies... Some partition cookies based upon the first-party context... Some block cookies based upon user agent cookie policy and/or user controls." Then the sentence to remember: "resources cannot rely upon third-party cookies being treated consistently by user agents for the foreseeable future."
We're not going to pin that to browser versions. The defaults differ, they've moved repeatedly, and a version number here would be wrong within months. MDN does document one concrete difference: for the Storage Access API, "the default value for Chrome is SameSite=Lax (SameSite=None is the default in Firefox and Safari)." Set the attribute explicitly and the default stops mattering.
What breaks first is the session. Everything else follows: the app renders its logged-out shell, and any preference that lived in a cookie goes with it.
Partitioned cookies, and what they do not fix
Partitioned — the attribute from the CHIPS proposal — is the designed answer for embedded content that needs state without becoming a tracking vector. Its draft defines it as an attribute that "limits the scope of the cookie such that it will only be sent when the site of the top-document... is same-site with the top-document when the > cookie was created." The stray > is in the published text.
Each top-level site gets its own jar for that embed. Secure is mandatory: the draft has a section titled "Partitioned requires Secure," and MDN repeats it.
Here's the part people get wrong. A partitioned cookie is a new cookie in a new jar. It isn't your existing login. MDN's example keeps the other attributes rather than replacing them:
Set-Cookie: __Host-example=34d8g; SameSite=None; Secure; Path=/; Partitioned;
SameSite=None is still there, because partitioning changes where the cookie's stored, not whether it travels cross-site. And because the jar is per top-level site, MDN notes the cookies "are also not automatically synchronized across sites."
So if you're embedding somebody else's app and hoping Partitioned rescues your session, it doesn't. One caveat on the paperwork: draft-cutler-httpbis-partitioned-cookies-01 is dated 10 November 2022 and expired on 14 May 2023, so treat browser documentation as the live reference.
localStorage is partitioned too
Cookies get the attention, but the same key change has come to client-side storage. MDN's State Partitioning page describes Firefox directly: it "double-keys all client-side state by the origin of the resource being loaded and by the top-level site," and "From Firefox 103, State Partitioning is turned on by default." The partitioned storage APIs it lists are localStorage, sessionStorage, DOM Cache, IndexedDB, Broadcast Channel, shared workers, and service workers.
That's a Mozilla document describing Firefox, and we won't generalize it into a claim about every browser. The cross-browser effort is a Privacy Community Group Work Item, Client-Side Storage Partitioning, which coordinates changes across many separate standards rather than defining one. It's real, and it's unfinished. Assume that what an app wrote to localStorage as a first party is not what it reads back inside your frame.
There's a harder version. If the frame carries a sandbox attribute without allow-same-origin, the HTML Standard's sandboxed origin flag "prevents script from reading from or writing to the document.cookie IDL attribute, and blocks access to localStorage." Not partitioned — absent. Every iframe sandbox token has the rest.
The Storage Access API, and why the app has to ask
There's a supported route back to unpartitioned cookies, and it belongs entirely to the framed page. The Storage Access API gives a document two methods: hasStorageAccess() to check, and requestStorageAccess() to ask.
The preconditions explain why you can't arrange this from outside. requestStorageAccess() rejects unless the calling document is a secure context; it rejects if the document's origin or the top-level origin is opaque; it rejects if the frame's sandbox has the storage-access flag set. Then one step of the main algorithm reads "If has transient activation is false" — and denies the request. The stated intent is "to always require user activation before a storage-access permission will be set."
That's a design, not an obstacle. Someone clicks inside the frame's own interface, and the browser may prompt. MDN records that the permission is keyed as <top-level site, embedded site>, and must be activated per context: other tabs and frames "have their third-party cookie access blocked by default."
Two limits are easy to miss. A grant doesn't lift SameSite: when unpartitioned cookies are retrieved with storage access, "user agents will still follow applicable SameSite restrictions (i.e., not attach cookies marked SameSite=Strict or SameSite=Lax in third party contexts)." And a sandboxed frame needs three tokens before it can even try — MDN names allow-storage-access-by-user-activation, allow-scripts, and allow-same-origin. That last pair is the one the HTML Standard warns about — a real tension, not a contradiction.
Diagnosing it yourself
When a permitted frame shows a login screen, work down this list.
- Read the whole redirect chain. Curl the URL with
-L and read every hop. Our Todoist row landed on /auth/login; you may be framing a different page than you think.
- Look at the request's cookies, not the browser's. In the network panel, select the request the frame made and read the cookies sent on that request. The jar can hold a session the request never carried.
- Read the
Set-Cookie attributes on the first-party login. Sign in normally in a top-level tab and inspect the session cookie. If it's SameSite=Lax — or carries no SameSite at all, in a browser that defaults to Lax — a cross-site frame was never going to get it.
- Check the console for a partitioning message. MDN documents Firefox's wording: "Partitioned cookie or storage access was provided to 'b.example' because it is loaded in the third-party context and storage partitioning is enabled."
- Compare against a top-level load. Open the same URL in its own tab. If it works there and not in the frame, the difference is cross-site context, not the app.
The column that exists for this
This is why our reference table carries a second column beside the header values, and why that column is empty on every row. A header read proves the browser permits the frame. It can't prove the application works inside one, and we won't guess. An empty cell means nobody tried it by hand; it never means "probably fine."
Can I embed this site in an iframe? has all 60 measured rows with raw values and dates. For the header half, the frame-ancestors directive covers the modern control and its traps.
Questions
Can I make a third-party app keep my session inside my frame?
Not from your side. Every control involved — SameSite on the session cookie, the Partitioned attribute, the requestStorageAccess() call — belongs to the framed application's own code and server. Your page can't set another site's cookie attributes, and can't call the Storage Access API on its behalf. If the app hasn't opted in, the honest answer is a link that opens it in its own tab.
Does SameSite=None mean the cookie always arrives?
No. It means the cookie's willing to travel cross-site, and that it's discarded on arrival unless Secure is set too. Whether the browser then sends it depends on that browser's third-party cookie policy and the person's own settings, which differ and change. The draft's conclusion stands: resources "cannot rely upon third-party cookies being treated consistently by user agents for the foreseeable future."
Is SameSite a security feature or a compatibility problem?
Both, and the security half came first. The attribute exists mainly to blunt cross-site request forgery, and it does the same job against a framed clickjacking attack: a cookie that isn't sent can't authorize what a stolen click triggers. Clickjacking explained covers that. Breakage in legitimate embedded apps is the price, and the Storage Access API is the sanctioned way to pay it back.