Web browsers enforce the Same-Origin Policy to prevent scripts from one origin interacting with resources from another. An origin is defined by matching scheme, host, and port. While cross-origin embedding (e.g., via <script>, <img>, <iframe>) is permitted, JavaScript is restricted from reading or manipulating the loaded content.
Common Restrictions
- XMLHttpRequest and Fetch API cannot send cross-origin requests without server approval.
- Cross-origin cookies and Web Storage (localStorage/sessionStorage) are inaccessible.
- DOM access across origins in iframes is blocked unless explicitly allowed.
Workaround 1: JSONP via Script Injection
Leverage the fact that <script> tags bypass CORS. Inject a script tag with a callback parameter. The server wraps data in the named function.
// Client-side
function handleResponse(payload) {
console.log(payload);
}
const script = document.createElement('script');
script.src = 'http://localhost/api?cb=handleResponse';
document.body.appendChild(script);
// Server-side (PHP example)
$data = ['status' => 'ok', 'items' => [1, 2, 3]];
$cb = $_GET['cb'] ?? 'callback';
echo $cb . '(' . json_encode($data) . ');';
Workaround 2: CORS Headers
Configure the server to respond with appropriate CORS headers. Simple requests (GET/POST with basic headers) trigger no preflight. Complex requests require an OPTIONS prfelight.
// Server response headers
Access-Control-Allow-Origin: https://trusted-site.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Workaround 3: Reverse Proxy with Nginx
Route cross-origin requests through a same-origin proxy endpoint.
location /api/remote {
proxy_pass http://backend-server:3000/;
proxy_set_header Host $host;
}
Workaround 4: Image Beacon (GET-only)
Use image tags for fire-and-forget tracking. Limited to GET and no response handling.
<img src="https://tracker.com/pixel?user=123&event=view" alt="" width="1" height="1">
Workaround 5: Shared Domain via document.domain
For subdomains under the same root (e.g., app.site.com and api.site.com), relax policy by setting:
document.domain = "site.com";
// Now iframe.contentWindow is accessible
Workaround 6: window.name Transport
Exploit persistent window.name property across page navigations within an iframe.
// Inside iframe
window.name = JSON.stringify({ token: 'abc123' });
// Parent reads after iframe reloads to same-origin page
const data = JSON.parse(iframe.contentWindow.name);
Workaround 7: postMessage API
Securely exchange messages between windows or iframes regardless of origin. Always validate event.origin.
// Sender
targetWindow.postMessage({ action: 'update' }, 'https://receiver.com');
// Receiver
window.addEventListener('message', (event) => {
if (event.origin !== 'https://sender.com') return;
console.log('Received:', event.data);
});