The embedded JIRA ticket rendered logged-out every time, and the iframe never threw a single error. We had an 880-ticket backlog and were building an internal triage surface called ticket-shell inside our admin panel. The page shows the current JIRA ticket inline, with Prev/Next buttons so we can walk a queue without leaving it. JIRA holds the ticket data and our page holds the queue logic. The obvious move was to drop the ticket into an <iframe>, so that’s what we did.
What the iframe actually rendered
JIRA’s session lives in cookies set on JIRA’s own origin. Ours is a self-hosted instance on its own subdomain, and our admin page lives on a different origin. When JIRA loads inside an iframe on our page, the browser treats the subframe as a cross-origin, third-party context and won’t carry JIRA’s session cookies into it. The frame makes its request unauthenticated, and JIRA serves the logged-out view.
So the iframe loaded fine. It loaded a login screen instead of the ticket. Same-site and third-party cookie rules exist so that evil.example can’t embed yourbank.example in a hidden iframe and ride your logged-in session.
The page already had a 4-second watchdog for the other failure mode, an iframe that never loads because JIRA set X-Frame-Options or Content-Security-Policy: frame-ancestors to refuse embedding. When it fires, it shows an “Iframe load failed” card that points at the embedding headers first, then the cookie case. But the cookie problem never trips it. The frame loads, the watchdog stays quiet, and you stare at a login screen.
Because we own this JIRA, our first instinct was that we could change its X-Frame-Options in admin. That wouldn’t have helped. The header was never the problem, and the cookie boundary is not ours to change. No header on our admin page makes a browser carry a different origin’s session cookie into our frame. SameSite=None, partitioned cookies, the Storage Access API and document.domain games don’t turn a separate-origin login session into an embeddable one for a passive iframe. We were trying to share a logged-in session across an origin boundary the browser is built to keep separate, and the browser was always going to win.
Moving JIRA out of the frame
A top-level window navigated to JIRA is a first-party context. The cookies flow, and you’re logged in, the same as if you’d typed the URL. So we shipped a popup mode for ticket-shell. The commit touched the server page, the client JS and the stylesheet:
- A
?mode=popupquery param and a View select in the top bar (Iframe / Popup), so we could flip between the inline-embed attempt and popup mode. - In popup mode, the area where the iframe used to live shows a centered card with the current ticket key and an “Open in JIRA” button.
- The button calls
window.opento load the real JIRA ticket in a separate, top-level window. The session is intact and the ticket renders fully.
That fixed the logged-out problem and created a second one.
One window per click
window.open(url) with no name, or with a unique name per click, spawns a new window every time. Walk a queue of 30 tickets with Prev/Next and you end up with 30 JIRA windows on your desktop. You spend more time closing windows than reading tickets.
The second argument to window.open is the window name, the same target you’d put on an <a> tag, and it’s the whole fix:
// Spawns a new window on every navigation. Don't do this.window.open(jiraUrl);
// Reuses the SAME window every time. Do this.var POPUP_WINDOW_NAME = "jiraTicketWindow";window.open(jiraUrl, POPUP_WINDOW_NAME, "noopener=no");The noopener=no matters. The default noopener behavior severs the handle the browser uses to find a window by name, so leaving it on can break the reuse and spawn a fresh window anyway. Keep the opener relationship and the named lookup works.
With a fixed name, the browser looks for an existing window with that name first. If one exists, it navigates that window to the new URL instead of opening another. The first “Open in JIRA” pops a window, and every Prev/Next after that swaps the ticket in place. The result is one JIRA window parked off to the side that always shows the ticket you’re on, while the admin page keeps the queue logic.
The whole workaround is a query param, a toggle, an “Open in JIRA” button and a hardcoded window name. It’s small next to the time we spent on the iframe.
Checking the window count
The iframe embed was the hack, because it borrowed a session across an origin boundary that exists to stop exactly that. The popup uses the browser as designed, with first-party top-level navigation, a real session and real cookies.
The fixed window name carries over to any case where you bounce a user out to a third-party surface repeatedly, such as an external dashboard, a doc viewer, a payment provider or a separate admin.
To verify it in your own admin tool, pick one fixed window name, keep noopener=no, then open a ticket and hit Next a handful of times. Your taskbar should show exactly one JIRA window. If a second one ever appears, the name got dropped or changed somewhere in that click path, and that’s where to look.
Related
- When AI Makes Analysis Cheap, Your Decision UI Becomes the Bottleneck: a JIRA triage surface with the same cross-origin session challenge
- The CAPTCHA lockout you trigger yourself: when a wrong API endpoint hammers you to a 403: another case where the browser’s security model bites at an unexpected moment
- Our Agent’s Chrome Was Running but Invisible: A Windows Session 0 Isolation Ghost Story: cross-session isolation creating a similar “it ran but nothing happened” confusion
- PayPal Pay-Later Messages Won’t Re-Render, So Destroy the Node and Recreate It: another third-party embed that fights the browser’s state model
- clipboard.js Named Its Global ‘Clipboard’. So Did the Browser. Boom.: a browser API boundary collision in a similar legacy-JS context