How to Set the Cross-Origin-Opener-Policy Header

Cross-Origin-Opener-Policy (COOP) breaks the window reference between your pages and cross-origin pages that open them, protecting against tabnabbing and XS-Leak attacks. This guide shows how to set it on all major web servers and platforms.


The Cross-Origin-Opener-Policy (COOP) header controls whether a top-level page shares a browsing context group with cross-origin pages that open it (or that it opens) via window.open() or links. Without it, a malicious page can keep a live window reference to your site and probe it for cross-site leaks (XS-Leaks) or redirect it in tabnabbing attacks. This guide shows how to set the header across all major web servers and platforms.

Choosing a Policy Value

ValueBehaviourBest For
unsafe-noneDefault — page shares its browsing context group with cross-origin openersLegacy behaviour; no protection
same-originPage is isolated from any cross-origin opener or openee — the window reference between them is severedRecommended for most sites — full isolation against XS-Leaks and tabnabbing
same-origin-allow-popupsPage is isolated from its opener, but popups it opens without COOP keep a reference backSites that open third-party popups (OAuth flows, payment windows)
noopener-allow-popupsSevers the opener reference in both directions but does not require same-origin popupsNewer value with limited browser support — verify before relying on it
Recommended Valuesame-origin is the right choice for most sites. If your site uses OAuth or payment popups that must communicate with the opener window, use same-origin-allow-popups instead — plain same-origin will break those flows.
COOP + COEP = Cross-Origin IsolationSetting both Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp makes your page "cross-origin isolated", unlocking powerful APIs like SharedArrayBuffer and high-resolution timers. COOP alone is still valuable as a defence-in-depth security header.

Nginx

nginx
server { listen 443 ssl; server_name example.com; add_header Cross-Origin-Opener-Policy "same-origin" always; # ... rest of config }
bash
sudo nginx -t && sudo systemctl reload nginx

Apache

apache
<VirtualHost *:443> <IfModule mod_headers.c> Header always set Cross-Origin-Opener-Policy "same-origin" </IfModule> </VirtualHost>

Or in .htaccess:

apache
<IfModule mod_headers.c> Header always set Cross-Origin-Opener-Policy "same-origin" </IfModule>
bash
sudo a2enmod headers && sudo systemctl reload apache2

IIS (web.config)

xml
<system.webServer> <httpProtocol> <customHeaders> <add name="Cross-Origin-Opener-Policy" value="same-origin" /> </customHeaders> </httpProtocol> </system.webServer>

Caddy

Command
example.com { header Cross-Origin-Opener-Policy "same-origin" # ... rest of config }

Cloudflare (Transform Rules)

  1. Go to RulesTransform RulesModify Response Header.
  2. Add a rule with Operation: Set, Header name: Cross-Origin-Opener-Policy, Value: same-origin.
  3. Deploy the rule.

Next.js

javascript
// next.config.mjs const nextConfig = { async headers() { return [ { source: '/(.*)', headers: [ { key: 'Cross-Origin-Opener-Policy', value: 'same-origin', }, ], }, ]; }, }; export default nextConfig;

Watch Out for Popup-Based Flows

The most common breakage after enabling COOP is a login or checkout popup that can no longer talk back to the page that opened it. If your site uses window.open() together with window.opener or postMessage to a popup (Google/Facebook sign-in, Stripe Checkout in popup mode, OAuth flows), use same-origin-allow-popups instead of same-origin and re-test those flows before deploying.

Verifying the Header

bash
curl -I https://example.com | grep -i cross-origin-opener # Expected: cross-origin-opener-policy: same-origin

Or use the ShowDNS Security Headers Scanner. You can also confirm isolation in the browser console — on an isolated page, window.crossOriginIsolated returns true when COEP is set too.

Related Articles