Repository: auth0/auth0.js
Pull request #598 (2017-12-21 22:19:28)

Support browsers that return undefined on window.origin
Some browsers don't support window.origin. See [compatibility chart on MDN](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/origin#Browser_compatibility).

This pull requests constructs window.origin if it's not provided.

luisrudge: IE11 supports window.origin. Are you sure Edge doesn't support it?
thoean: Good point. Now I see. IE11 and Edge are the same:
* `window.origin` is `undefined`
* `window.location.origin` matches "scheme + host + port"

I think I should be able to simplify this PR, but I still think some change is needed.
luisrudge: Cool! I'll keep an eye on the updates. Thanks 🎉 
luisrudge: do all browsers support window.location.origin? if that's the case, I think we're safe to use only that one. 
luisrudge: IE, Edge and Chrome support window.location.origin. Can you test on firefox and safari?
thoean: Without further testing, it looks like window.location.origin is widely supported. See https://www.w3schools.com/jsref/prop_loc_origin.asp

However, IE only from v11. What are the aspirations regarding compatibility of this library? I could still leave the fallback for manually constructing the origin, my original version.

For sure we can remove window.origin.
luisrudge: Yeah, so let's keep it like this: 
1: options.postMessageOrigin
2: window.location.origin
3: fallback

Please add a test for each one (I believe we already have a test for 1) and we'll merge it. Thanks 🎉 
thoean: I've just done that. Thank you.

This will merge into v8. Is there anything to do to get this also into the latest / future version?

Patch:
diff --git a/src/web-auth/silent-authentication-handler.js b/src/web-auth/silent-authentication-handler.js
index ff670b7..9791942 100644
--- a/src/web-auth/silent-authentication-handler.js
+++ b/src/web-auth/silent-authentication-handler.js
@@ -6,7 +6,13 @@ function SilentAuthenticationHandler(options) {
   this.timeout = options.timeout || 60 * 1000;
   this.handler = null;
   this.postMessageDataType = options.postMessageDataType || false;
-  this.postMessageOrigin = options.postMessageOrigin || windowHelper.getWindow().origin;
+
+  // prefer origin from options, fallback to origin from browser, and some browsers (for example MS Edge) don't support origin; fallback to construct origin manually
+  this.postMessageOrigin =
+    options.postMessageOrigin ||
+    windowHelper.getWindow().location.origin ||
+    windowHelper.getWindow().location.protocol + '//' + windowHelper.getWindow().location.hostname
+      + (windowHelper.getWindow().location.port ? ':' + windowHelper.getWindow().location.port : '');
 }
 
 SilentAuthenticationHandler.create = function(options) {
@@ -53,7 +59,12 @@ SilentAuthenticationHandler.prototype.getEventValidator = function() {
             eventData.event.data.type && eventData.event.data.type === _this.postMessageDataType
           );
 
-        case 'load': // Fall through to default
+        case 'load':
+          if (eventData.sourceObject.contentWindow.location.protocol === 'about:') {
+            // Chrome is automatically loading the about:blank page, we ignore this.
+            return false;
+          }
+        // Fall through to default
         default:
           return true;
       }


Test patch:
diff --git a/test/web-auth/silent-authentication-handler.test.js b/test/web-auth/silent-authentication-handler.test.js
index dc1bd48..0f93560 100644
--- a/test/web-auth/silent-authentication-handler.test.js
+++ b/test/web-auth/silent-authentication-handler.test.js
@@ -14,10 +14,14 @@ var iframeHandler = {};
 
 describe('handlers silent-authentication-handler', function() {
   context('with context', function() {
+    beforeEach(function() {
+      global.window = { location: { origin: 'unit-test-origin' } }
+    });
     afterEach(function() {
       if (IframeHandler.prototype.init.restore) {
         IframeHandler.prototype.init.restore();
       }
+      delete global.window;
     });
     it('should return correct value for usePostMessage=false', function(done) {
       stub(IframeHandler.prototype, 'init', function() {
@@ -182,6 +186,13 @@ describe('handlers silent-authentication-handler', function() {
         validator.isValid({
           event: {
             type: 'load'
+          },
+          sourceObject: {
+            contentWindow: {
+              location: {
+                protocol: 'https:'
+              }
+            }
           }
         })
       ).to.be(true);
@@ -252,7 +263,74 @@ describe('handlers silent-authentication-handler', function() {
       });
       var validator = sah.getEventValidator();
 
-      expect(validator.isValid({ event: { type: 'load' } })).to.be(true);
+      expect(validator.isValid({
+        event: {
+          type: 'load'
+        },
+        sourceObject: {
+          contentWindow: {
+            location: {
+              protocol: 'https:'
+            }
+          }
+        }
+      })).to.be(true);
+    });
+
+    it('should negatively validate load event types for "about:" protocol', function() {
+      var sah = new SilentAuthenticationHandler({
+        postMessageDataType: false
+      });
+      var validator = sah.getEventValidator();
+
+      expect(validator.isValid({
+          event: {
+            type: 'load'
+          },
+          sourceObject: {
+            contentWindow: {
+              location: {
+                protocol: 'about:'
+              }
+            }
+          }
+      })).to.be(false);
+    });
+  });
+
+  context('constructor', function() {
+    it('sets postMessageOrigin from parameter', function() {
+      var expectedOrigin = 'unit-test-post-message-origin';
+      var param = { postMessageOrigin: expectedOrigin };
+
+      var sah = new SilentAuthenticationHandler(param);
+
+      expect(sah.postMessageOrigin).to.be(expectedOrigin);
+    });
+
+    it('sets postMessageOrigin from window', function() {
+      var expectedOrigin = 'unit-test-location-origin';
+      global.window = { location: { origin: expectedOrigin } };
+
+      var sah = new SilentAuthenticationHandler({});
+
+      expect(sah.postMessageOrigin).to.be(expectedOrigin);
+    });
+
+    it('sets postMessageOrigin from fallback (with port)', function() {
+      global.window = { location: { protocol: 'https:', hostname: 'unit-test', port: 1234 } };
+
+      var sah = new SilentAuthenticationHandler({});
+
+      expect(sah.postMessageOrigin).to.be('https://unit-test:1234');
+    });
+
+    it('sets postMessageOrigin from fallback (without port)', function() {
+      global.window = { location: { protocol: 'https:', hostname: 'unit-test' } };
+
+      var sah = new SilentAuthenticationHandler({});
+
+      expect(sah.postMessageOrigin).to.be('https://unit-test');
     });
   });
 });
diff --git a/test/web-auth/web-auth.test.js b/test/web-auth/web-auth.test.js
index 5388b67..76ebcfd 100644
--- a/test/web-auth/web-auth.test.js
+++ b/test/web-auth/web-auth.test.js
@@ -76,6 +76,7 @@ describe('auth0.WebAuth', function() {
           appState: null
         });
       };
+      global.window.location = {};
       storage.reload();
     });
 
@@ -663,6 +664,7 @@ describe('auth0.WebAuth', function() {
   context('renewAuth', function() {
     beforeEach(function() {
       global.window = {};
+      global.window.origin = 'unit-test-origin';
       global.window.removeEventListener = function() {};
     });
     afterEach(function() {
@@ -751,6 +753,7 @@ describe('auth0.WebAuth', function() {
     beforeEach(function() {
       global.window = {};
       global.window.document = {};
+      global.window.origin = 'unit-test-origin';
     });
 
     afterEach(function() {
@@ -815,6 +818,12 @@ describe('auth0.WebAuth', function() {
       });
     });
     describe('should return the access_token', function() {
+      beforeEach(function() {
+        global.window = { origin: 'unit-test-origin' };
+      });
+      afterEach(function() {
+        delete global.window;
+      });
       it('when login returns an object', function(done) {
         stub(SilentAuthenticationHandler.prototype, 'login', function(usePostMessage, cb) {
           cb(null, { accessToken: '123' });