Cross-Domain Solutions in Web Development

Understanding Cross-Domain Requests

Cross-domain refers to scenarios where resources from one domain are accessed by documents or scripts from another domain. This concept is broadly defined and includes several scenarios:

  • Resource navigation: Links, redirects, form submissions
  • Resource embedding: <link>, <script>, <img>, <frame> DOM elements, CSS files with background:url() or @font-face()
  • Script requests: AJAX requests initiated by JavaScript, cross-domain operations on DOM and JavaScript objects

When developers discuss cross-domain issues, they typically refer to the restrictions imposed by the browser's same-origin policy.

The Same-Origin Policy

The Same-Origin Policy (SOP) is a security mechanism introduced by Netscape in 1995. It's a fundamental browser security feature that prevents malicious scripts from accessing data from another origin. Without this policy, browsers would be vulnerable to attacks like XSS (Cross-Site Scripting) and CSRF (Cross-Site Request Forgery).

Two origins are considered the same if they share the same protocol, domain, and port. Even if two different domains resolve to the same IP address, they are still considered different origins.

The same-origin policy restricts the following behaviors:

  • Access to cookies, LocalStorage, and IndexDB
  • Access to DOM elements and JavaScript objects
  • Sending AJAX requests

Common Cross-Domain Scenarios

URL Description Communication Allowed
http://www.example.com/a.js Same domain, different files/paths Allowed
http://www.example.com/b.js
http://www.example.com:8000/a.js Same domain, different port Not Allowed
http://www.example.com/b.js
http://www.example.com/a.js Same domain, different protocol Not Allowed
https://www.example.com/b.js
http://www.example.com/a.js Domain and domain correspond to same IP Not Allowed
http://192.168.1.100/b.js
http://www.example.com/a.js Same main domain, different subdomains Not Allowed
http://sub.example.com/b.js
http://example.com/c.js
http://www.site1.com/a.js Different domains Not Allowed
http://www.site2.com/b.js

Cross-Domain Solutions

  1. JSONP (JSON with Padding)
  2. document.domain + iframe
  3. location.hash + iframe
  4. window.name + iframe
  5. postMessage API
  6. CORS (Cross-Origin Resource Sharing)
  7. Nginx Proxy
  8. Node.js Middleware Proxy
  9. WebSocket Protocol

1. JSONP Cross-Domain

JSONP leverages the fact that browsers allow loading scripts from different domains. By dynamically creating a script element and requesting a URL with a callback parameter, we can receive data from another domain.

Native JavaScript Implementation:


const script = document.createElement('script');
script.type = 'text/javascript';

// Pass a callback function name to the server for executing the response
script.src = 'http://api.example.com/login?user=admin&callback=handleResponse';
document.head.appendChild(script);

// Callback function to process the response
function handleResponse(data) {
    alert(JSON.stringify(data));
}

jQuery AJAX Implementation:


$.ajax({
    url: 'http://api.example.com/login',
    type: 'get',
    dataType: 'jsonp',  // Request type is jsonp
    jsonpCallback: "handleResponse",    // Custom callback function name
    data: {}
});

Vue.js Implementation:


this.$http.jsonp('http://api.example.com/login', {
    params: {},
    jsonp: 'handleResponse'
}).then((response) => {
    console.log(response); 
})

Node.js Server Example:


const querystring = require('querystring');
const http = require('http');
const server = http.createServer();

server.on('request', function(req, res) {
    const params = querystring.parse(req.url.split('?')[1]);
    const callbackName = params.callback;

    // JSONP response setup
    res.writeHead(200, { 'Content-Type': 'text/javascript' });
    res.write(callbackName + '(' + JSON.stringify(params) + ')');

    res.end();
});

server.listen('8080');
console.log('Server is running at port 8080...');

Limitation: JSONP only supports GET requests.

2. document.domain + iframe

This solution works only when the main domain is the same but subdomains differ. The principle is to set document.domain to the base domain in both pages.

Parent Window (http://www.example.com/page1.html):


<iframe id="frame" src="http://sub.example.com/page2.html"></iframe>
<script>
    document.domain = 'example.com';
    const userData = 'admin';
</script>

Child Window (http://sub.example.com/page2.html):


<script>
    document.domain = 'example.com';
    // Access parent window variables
    alert('Data from parent: ' + window.parent.userData);
</script>

3. location.hash + iframe

This method uses a middle page to facilitate communication between two different domains. The pages use iframe's location.hash to pass values, and same-domain pages communicate directly through JavaScript.

Page A (http://domain1.com/a.html):


<iframe id="frame" src="http://domain2.com/b.html" style="display:none;"></iframe>
<script>
    const frame = document.getElementById('frame');

    // Send hash value to b.html
    setTimeout(function() {
        frame.src = frame.src + '#user=admin';
    }, 1000);
    
    // Callback function for same-domain c.html
    function onCallback(response) {
        alert('Data from c.html: ' + response);
    }
</script>

Page B (http://domain2.com/b.html):


<iframe id="frame" src="http://domain1.com/c.html" style="display:none;"></iframe>
<script>
    const frame = document.getElementById('frame');

    // Listen for hash changes from a.html and forward to c.html
    window.onhashchange = function () {
        frame.src = frame.src + location.hash;
    };
</script>

Page C (http://domain1.com/c.html):


<script>
    // Listen for hash changes from b.html
    window.onhashchange = function () {
        // Call back to same-domain a.html with the result
        window.parent.parent.onCallback('Response: ' + location.hash.replace('#user=', ''));
    };
</script>

4. window.name + iframe

The window.name property maintains its value even when the page navigates to a different domain (or even after multiple page loads) and can store up to 2MB of data.

Page A (http://domain1.com/a.html):


const fetchData = function(url, callback) {
    let state = 0;
    const frame = document.createElement('iframe');

    // Load cross-domain page
    frame.src = url;

    // onload triggers twice: first for cross-domain page, then for same-domain proxy
    frame.onload = function() {
        if (state === 1) {
            // Second onload (same-domain proxy) - read window.name data
            callback(frame.contentWindow.name);
            cleanupFrame();

        } else if (state === 0) {
            // First onload (cross-domain) - switch to same-domain proxy
            frame.contentWindow.location = 'http://domain1.com/proxy.html';
            state = 1;
        }
    };

    document.body.appendChild(frame);

    // Clean up frame after data retrieval
    function cleanupFrame() {
        frame.contentWindow.document.write('');
        frame.contentWindow.close();
        document.body.removeChild(frame);
    }
};

// Request data from cross-domain page B
fetchData('http://domain2.com/b.html', function(data){
    alert(data);
});

Proxy Page (http://domain1.com/proxy.html):

This is a blank page in the same domain as a.html.

Page B (http://domain2.com/b.html):


<script>
    window.name = 'This is data from domain2!';
</script>

5. postMessage API

The postMessage API is part of HTML5's XMLHttpRequest Level 2 and allows secure cross-domain communication between windows. It can be used for:

  • Communication between a page and new windows it opens
  • Communication between multiple windows
  • Communication between a page and embedded iframes
  • Cross-domain data transfer in the above scenarios

The syntax is: postMessage(data, origin)

  • data: Any primitive type or cloneable object (best to serialize with JSON.stringify for broader browser support)
  • origin: Protocol + hostname + port. Can be "*" for any window, or "/" for same-origin only

Page A (http://domain1.com/a.html):


<iframe id="frame" src="http://domain2.com/b.html" style="display:none;"></iframe>
<script>       
    const frame = document.getElementById('frame');
    frame.onload = function() {
        const data = {
            name: 'aym'
        };
        // Send cross-domain data to domain2
        frame.contentWindow.postMessage(JSON.stringify(data), 'http://domain2.com');
    };

    // Receive response from domain2
    window.addEventListener('message', function(event) {
        alert('Data from domain2: ' + event.data);
    }, false);
</script>

Page B (http://domain2.com/b.html):


<script>
    // Receive data from domain1
    window.addEventListener('message', function(event) {
        alert('Data from domain1: ' + event.data);

        const data = JSON.parse(event.data);
        if (data) {
            data.value = 16;

            // Send processed data back to domain1
            window.parent.postMessage(JSON.stringify(data), 'http://domain1.com');
        }
    }, false);
</script>

6. Cross-Origin Resource Sharing (CORS)

CORS is a W3C standard that allows servers to indicate which origins are permitted to access their resources. For simple cross-domain requests, only the server needs to set the Access-Control-Allow-Origin header. For requests with cookies, both client and server need additional configuration.

Important: Due to the same-origin policy, cookies read will be from the target domain (not the current page). To write cookies to the current domain, see the nginx and Node.js proxy solutions below.

All modern browsers support CORS (IE8+ requires XDomainRequest for support).

Client-Side Configuration:

Native AJAX:

// Enable cookies in cross-domain requests
const xhr = new XMLHttpRequest(); // For IE8/9, use window.XDomainRequest

xhr.withCredentials = true;

xhr.open('post', 'http://api.example.com/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');

xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) {
        alert(xhr.responseText);
    }
};

jQuery AJAX:

$.ajax({
    ...
    xhrFields: {
        withCredentials: true    // Enable cookies
    },
    crossDomain: true,   // Include cross-domain headers (but not cookies)
    ...
});

Vue.js Frameworks:

With axios:


axios.defaults.withCredentials = true

With vue-resource:


Vue.http.options.credentials = true

Server-Side Configuration:

Java Backend:

/*
 * Import: import javax.servlet.http.HttpServletResponse;
 * Add parameter: HttpServletResponse response
 */

// Allowed domain for cross-origin access (include protocol+domain+port if applicable)
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com"); 

// Enable cookies in requests - note: this requires a specific domain, not "*"
response.setHeader("Access-Control-Allow-Credentials", "true"); 

// Custom headers for OPTIONS preflight requests
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");

Node.js Backend Example:

const http = require('http');
const server = http.createServer();
const qs = require('querystring');

server.on('request', function(req, res) {
    let postData = '';

    // Collect POST data
    req.addListener('data', function(chunk) {
        postData += chunk;
    });

    // Process complete request
    req.addListener('end', function() {
        postData = qs.parse(postData);

        // CORS configuration
        res.writeHead(200, {
            'Access-Control-Allow-Credentials': 'true',     // Allow cookies
            'Access-Control-Allow-Origin': 'http://www.domain1.com',    // Specific allowed domain
            'Set-Cookie': 'sessionId=abc123;Path=/;Domain=www.example.com;HttpOnly'  // HttpOnly prevents JS access
        });

        res.write(JSON.stringify(postData));
        res.end();
    });
});

server.listen('8080');
console.log('Server is running at port 8080...');

7. Nginx Proxy for Cross-Domain

Solving IconFont Cross-Domain Issues:

Browsers typically allow cross-domain access to static resources like JS, CSS, and images, but font files (eot|otf|ttf|woff|svg) are an exception. This can be resolved with the following nginx configuration:


location / {
  add_header Access-Control-Allow-Origin *;
}

Nginx Reverse Proxy for API Cross-Domain:

Principal: The same-origin policy is a browser security feature, not part of HTTP protocol. Server-side HTTP calls don't execute JavaScript and aren't subject to same-origin restrictions.

Approach: Configure nginx as a reverse proxy server with the same domain as the client but a different port. This server can forward requests to the target API and modify cookie domains to enable cookie-based authentication.

Nginx Configuration:


# Proxy server
server {
    listen       81;
    server_name  www.domain1.com;

    location / {
        proxy_pass   http://www.domain2.com:8080;  # Reverse proxy
        proxy_cookie_domain www.domain2.com www.domain1.com; # Modify cookie domain
        index  index.html index.htm;

        # When using webpack-dev-server or similar middleware, this may not be needed
        add_header Access-Control-Allow-Origin http://www.domain1.com;  
        add_header Access-Control-Allow-Credentials true;
    }
}

Frontend Code Example:


const xhr = new XMLHttpRequest();

// Enable cookies in cross-domain requests
xhr.withCredentials = true;

 // Access the nginx proxy server
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();

Node.js Backend Example:


const http = require('http');
const server = http.createServer();
const qs = require('querystring');

server.on('request', function(req, res) {
    const params = qs.parse(req.url.substring(2));

    // Set cookie for the client
    res.writeHead(200, {
        'Set-Cookie': 'sessionId=abc123;Path=/;Domain=www.domain2.com;HttpOnly'
    });

    res.write(JSON.stringify(params));
    res.end();
});

server.listen('8080');
console.log('Server is running at port 8080...');

8. Node.js Middleware Proxy for Cross-Domain

This approach uses a Node.js middleware server as a proxy to forward requests, similar to nginx. It can also modify cookie domains to enable cookie-based authentication across domains.

Non-Vue Framework Cross-Domain (Two Cross-Domain Requests):

Using Node.js + Express + http-proxy-middleware to create a proxy server.

Frontend Code Example:


const xhr = new XMLHttpRequest();

// Enable cookies in cross-domain requests
xhr.withCredentials = true;

// Access the http-proxy-middleware proxy server
xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);
xhr.send();

Middleware Server:


const express = require('express');
const proxy = require('http-proxy-middleware');
const app = express();

app.use('/', proxy({
    // Target API for proxying
    target: 'http://www.domain2.com:8080',
    changeOrigin: true,

    // Modify response headers for CORS and cookies
    onProxyRes: function(proxyRes, req, res) {
        res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');
        res.header('Access-Control-Allow-Credentials', 'true');
    },

    // Modify cookie domain
    cookieDomainRewrite: 'www.domain1.com'  // Can be false to skip modification
}));

app.listen(3000);
console.log('Proxy server is running at port 3000...');

Vue Framework Cross-Domain (Single Cross-Domain Request):

Using Node.js + webpack + webpack-dev-server for API proxying. In development mode, the Vue rendering server and API proxy run on the same webpack-dev-server, eliminating the need for CORS headers between the page and proxy.

webpack.config.js Configuration:


module.exports = {
    entry: {},
    module: {},
    ...
    devServer: {
        historyApiFallback: true,
        proxy: [{
            context: '/login',
            target: 'http://www.domain2.com:8080',  // Target API
            changeOrigin: true,
            secure: false,  // Use for HTTPS APIs that cause errors
            cookieDomainRewrite: 'www.domain1.com'  // Can be false to skip
        }],
        noInfo: true
    }
}

9. WebSocket Protocol for Cross-Domain

WebSocket is a HTML5 protocol that enables full-duplex communication between browsers and servers, with built-in support for cross-domain communication. It's an excellent implementation of server-push technology.

While the native WebSocket API works, libraries like Socket.io provide more convenient interfaces and backward compatibility for browsers that don't support WebSocket.

Frontend Code:


<div>User input: <input type="text"></div>
<script src="https://cdn.jsdelivr.net/npm/socket.io-client@2.3.0/dist/socket.io.js"></script>
<script>
const socket = io('http://www.domain2.com:8080');

// Connection established
socket.on('connect', function() {
    // Listen for server messages
    socket.on('message', function(msg) {
        console.log('Data from server: ' + msg); 
    });

    // Listen for server disconnection
    socket.on('disconnect', function() { 
        console.log('Server connection closed.'); 
    });
});

document.querySelector('input').onblur = function() {
    socket.send(this.value);
};
</script>

Node.js WebSocket Server:


const http = require('http');
const socketIo = require('socket.io');

// Create HTTP server
const server = http.createServer(function(req, res) {
    res.writeHead(200, {
        'Content-type': 'text/html'
    });
    res.end();
});

server.listen('8080');
console.log('Server is running at port 8080...');

// Set up WebSocket server
socketIo(server).on('connection', function(client) {
    // Handle incoming messages
    client.on('message', function(msg) {
        client.send('Hello: ' + msg);
        console.log('Data from client: ' + msg);
    });

    // Handle disconnection
    client.on('disconnect', function() {
        console.log('Client disconnected.'); 
    });
});

Tags: cross-domain cors JSONP postMessage WebSocket

Posted on Thu, 28 May 2026 18:43:12 +0000 by d_mc_a