Embedding third-party content with an iframe is straightforward until it suddenly stops working.
Some pages refuse to render, authentication flows fail, and redirects behave unexpectedly. In most cases, the problem isn’t your code. It’s the browser enforcing security rules around embedded content.
In this guide, we’ll break down why these restrictions exist and how to work with them. You’ll learn:
- Why OAuth does not work inside iframes
- How CSP and
X-Frame-Optionscontrol embedding - How the
sandboxattribute restricts iframe behavior - How to safely communicate between an iframe and its parent using
postMessage.
What is an iframe?
An iframe (Inline Frame) is an HTML element that embeds another webpage or external content inside your current page.
It works like a window that displays content from a different source without redirecting the user.
<iframe src="https://example.com" width="600" height="400" title="Example site"></iframe>
Parent Page (your app)
This is the main browsing context.
Iframe Content
Runs in a separate, isolated context.
Security Considerations
Some sites intentionally block being loaded inside an iframe. This protects users from clickjacking attacks, where malicious sites visually disguise login forms or sensitive actions. To allow embedding of your site:
- Set the CSP
frame-ancestorsdirective with trusted parent domains - Ensure
X-Frame-Optionsisn't set toDENYorSAMEORIGIN
Best Practice: Only allow trusted origins to enable secure iframe embedding.
OAuth Restrictions in Iframes
OAuth flows generally do not work inside iframes due to modern browser security controls:
1. Clickjacking Protection
Most providers send headers like:
X-Frame-OptionsContent-Security-Policy
These block login pages from being embedded, preventing malicious framing attacks.
2. Third-Party Cookie Blocking
Browsers restrict cookies in cross-site iframes, which breaks the session handling required for OAuth redirects and state validation. This affects major providers like Google, Facebook, GitHub, etc.
Parent Page (your app)
Error Occured
Best Practices
OAuth flows should run in a top-level browsing context, not inside an iframe. e.g
- Redirect OAuth in the Parent Window
// inside click handler in iframe site
if (window.top) {
window.top.location.href = authUrl;
}
Then allow navigation using:
<iframe src="SITE_URL" sandbox="allow-top-navigation"></iframe>
- Open OAuth in a New Tab
// iframe site
window.open(authLink);
Allow popups using:
<iframe src="SITE_URL" sandbox="allow-popups"></iframe>
Iframe sandbox Attribute
The sandbox attribute is a security feature that restricts what embedded content can do.
Adding sandbox with no value applies all restrictions:
- No scripts
- No form submissions
- Cannot navigate the top-level window
- No popups
- No automatic features (autoplay, pointer lock, etc.).
By passing a value, you can determine what behaviours you'd like to permit.
Common Sandbox Values for an iframe`
allow-scripts— Runs JavaScript inside the iframeallow-forms— Allows form submissionsallow-popups— Enables opening new windows viawindow.open()allow-same-origin— Treats iframe content as same-origin (cookies + DOM access)allow-top-navigation— Lets the iframe redirect the top-level pageallow-top-navigation-by-user-activation— Allows top navigation only after a user click/tapallow-modals— Enables modal dialogs likealert,confirm, andprompt
Communication Between an iframe and It's Parent
When embedding an iframe, you may need communication with the parent page.
For example, to notify about user actions. Due to the Same-Origin Policy, the parent and iframe cannot directly access each other's DOM or JS if they are on different origins.
A standard approach to enabling communication is by using : window.postMessage()
Sending a Message (iframe → parent)
window.parent.postMessage({ type: "loginSuccess" }, "https://parent-site.com");
Listening for Messages (parent)
window.addEventListener("message", (event) => {
if (event.origin !== "https://iframe-site.com") return;
console.log(event.data);
});
Best Practices
- Always validate
event.origin - Never trust incoming data blindly
- Avoid using
"*"as the target origin unless absolutely necessary - Ensure the iframe isn't sandboxed without
allow-scripts