Search code examples
javascriptcross-domainrefererwindow.opener

How to identify cross-origin opener so my website only serves openers from certain origins?


I know window.opener.href is blocked due to security purpose. However, they are the one trying to access my website, how do I identify them?

Scenario: From Google Docs (origin https://xxxxx.googleusercontent.com/userCodeAppPanel), I can open a tab/window to https://www.example.com/feature. I want to communicate between the two sites using postMessage. From Google Docs, it's easy to identify my website from the message's origin but how does my site identify that it was opened by Google Docs and only transfer data if it is indeed opened by them? I can control the Javascript code of both ends but I cannot control the domain name and CORS headers of Google Docs.

// From Google Docs
const handler = window.open(new URL("/feature-for-docs", Server));
h.addEventListener("message", (e) => this.#onMessage(e));

// At my website
if (window.opener) {
    // I know someone is opening this page from somewhere.
    // How do I serve only the origin I want to? (pseudo-code below)
    if (window.opener.origin.endsWith(".googleusercontent.com")) {
         showLoginWindow();
    } else {
         alert("You may be being scammed! Open this from Google Docs.");
    }
}

My question: how does my website confirm the request is from Google Docs?

Side question I would like to be enlightened: why is it when A (Google Docs) opens B (example.com), B shouldn't know A's origin? A is the one who is in control in this situation and is the one sending out request afterall.


Solution

  • Right now I am using a "handshake" to determine the origin of the sender and only send message to such origins. Basically:

    1. Google Docs opens MyApp.

    2. MyApp sends a ready signal to the opener (doesn't matter its origin).

    3. Google Docs sends ready message to MyApp. Now I know the opener's origin through e.origin and I will only send message to such origin (and filter out only acceptable origins).

    From Google Docs Javascript, open the app window:

    this.#handle = window.open(new URL("/feature", Server));
    globalThis.addEventListener("message", (e) => this.#onMessage(e));
    
    // When handling message
    #onMessage(e) {
        // ...
        this.#handle.postMessage({
            op: "ready",
            appName: "Google Docs™️",
        }, ServerOriginLower);
    }
    

    From my web app:

    // If opener is available: send message
    (globalThis.opener as Window).postMessage({
        op: "ready",
    }, "*");
    
    // message Event Handler
    if (e.data?.op === "ready") {
        if (!this.#openerOrigin) {
            if (!e.origin.toLowerCase().endsWith("googleusercontent.com")) {
                console.warn("Unauthorized message from " + e.origin);
                return;
            }
    
            this.#openerOrigin = e.origin;
        } else {
            return;
        }
    }
    

    Note: Don't actually use this origin check (endsWith) in production. Someone may make another app using App Script. Make sure to have your own script origin in production.