Skip to content

Commit

Permalink
Add option to filter iframe events to prevent incorrect events trigge…
Browse files Browse the repository at this point in the history
…ring callbacks
  • Loading branch information
Aaron Chilcott authored and luisrudge committed May 18, 2017
1 parent d92f05a commit 32151b2
Show file tree
Hide file tree
Showing 8 changed files with 619 additions and 181 deletions.
15 changes: 9 additions & 6 deletions example/callback.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
parent.postMessage(window.location.hash, "https://localhost:3000/");
</script>
</head>
<body></body>

<head>
<script type="text/javascript">
parent.postMessage(window.location.hash, "https://localhost:3000/");
</script>
</head>

<body></body>

</html>
1 change: 0 additions & 1 deletion example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,6 @@ <h2>Console:</h2>
webAuth.renewAuth({
usePostMessage: true,
scope:'',
audience: 'https://brucke.auth0.com/userinfo',
redirectURI: 'https://localhost:3000/example/callback.html'
},
htmlConsole.dumpCallback.bind(htmlConsole)
Expand Down
78 changes: 40 additions & 38 deletions src/helper/iframe-handler.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
var windowHelper = require('./window');


function IframeHandler(options) {
this.auth0 = options.auth0;
this.url = options.url;
this.callback = options.callback;
this.timeout = options.timeout || 60 * 1000;
this.timeoutCallback = options.timeoutCallback || null;
this.usePostMessage = options.usePostMessage || false;
this.eventListenerType = options.eventListenerType || 'message';
this.iframe = null;
this.timeoutHandle = null;
this._destroyTimeout = null;
this.transientMessageEventListener = null;
this.transientEventListener = null;
this.proxyEventListener = null;
// If no event identifier specified, set default
this.eventValidator = options.eventValidator || {
isValid: function () {
return true;
}
};

if (typeof this.callback !== 'function') {
throw new Error('options.callback must be a function');
}
}

IframeHandler.prototype.init = function () {
Expand All @@ -22,50 +32,45 @@ IframeHandler.prototype.init = function () {
this.iframe.style.display = 'none';
this.iframe.src = this.url;

if (this.usePostMessage) {
// Workaround to avoid using bind that does not work in IE8
this.transientMessageEventListener = function (e) {
_this.messageEventListener(e);
};

_window.addEventListener('message', this.transientMessageEventListener, false);
} else {
// Workaround to avoid using bind that does not work in IE8
this.transientEventListener = function () {
_this.loadEventListener();
};

this.iframe.addEventListener('load', this.transientEventListener, false);
// Workaround to avoid using bind that does not work in IE8
this.proxyEventListener = function (e) {
_this.eventListener(e);
};

switch (this.eventListenerType) {
case 'message':
this.eventSourceObject = _window;
break;
case 'load':
this.eventSourceObject = this.iframe;
break;
default:
throw new Error('Unsupported event listener type: ' + this.eventListenerType);
}

this.eventSourceObject
.addEventListener(this.eventListenerType, this.proxyEventListener, false);

_window.document.body.appendChild(this.iframe);

this.timeoutHandle = setTimeout(function () {
_this.timeoutHandler();
}, this.timeout);
};

IframeHandler.prototype.messageEventListener = function (e) {
this.destroy();
this.callbackHandler(e.data);
};

IframeHandler.prototype.loadEventListener = function () {
var _this = this;
_this.callback(null, this.iframe.contentWindow.location.hash);
};
IframeHandler.prototype.eventListener = function (event) {
var eventData = { event: event, sourceObject: this.eventSourceObject };

IframeHandler.prototype.callbackHandler = function (result) {
var error = null;

if (result && result.error) {
error = result;
result = null;
if (!this.eventValidator.isValid(eventData)) {
return;
}

this.callback(error, result);
this.destroy();
this.callback(eventData);
};


IframeHandler.prototype.timeoutHandler = function () {
this.destroy();
if (this.timeoutCallback) {
Expand All @@ -80,14 +85,11 @@ IframeHandler.prototype.destroy = function () {
clearTimeout(this.timeoutHandle);

this._destroyTimeout = setTimeout(function () {
if (_this.usePostMessage) {
_window.removeEventListener('message', _this.transientMessageEventListener, false);
} else {
_this.iframe.removeEventListener('load', _this.transientEventListener, false);
}

_this.eventSourceObject
.removeEventListener(_this.eventListenerType, _this.proxyEventListener, false);
_window.document.body.removeChild(_this.iframe);
}, 0);
};


module.exports = IframeHandler;
13 changes: 9 additions & 4 deletions src/web-auth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,13 @@ WebAuth.prototype.validateToken = function (token, nonce, cb) {
* @param {String} [options.nonce] value used to mitigate replay attacks when using Implicit Grant. {@link https://auth0.com/docs/api-auth/tutorials/nonce}
* @param {String} [options.scope] scopes to be requested during Auth. e.g. `openid email`
* @param {String} [options.audience] identifier of the resource server who will consume the access token issued after Auth
* @param {String} [options.postMessageDataType] identifier data type to look for in postMessage event data, where events are initiated from silent callback urls, before accepting a message event is the event expected. A value of false means any postMessage event will trigger a callback.
* @see {@link https://auth0.com/docs/api/authentication#authorize-client}
*/
WebAuth.prototype.renewAuth = function (options, cb) {
var handler;
var usePostMessage = !!options.usePostMessage;
var postMessageDataType = options.postMessageDataType || false;
var _this = this;

var params = objectHelper.merge(this.baseOptions, [
Expand All @@ -250,14 +252,17 @@ WebAuth.prototype.renewAuth = function (options, cb) {

params.prompt = 'none';

params = objectHelper.blacklist(params, ['usePostMessage', 'tenant']);
params = objectHelper.blacklist(params, ['usePostMessage', 'tenant', 'postMessageDataType']);

handler = new SilentAuthenticationHandler(this, this.client.buildAuthorizeUrl(params));
handler = SilentAuthenticationHandler.create({
authenticationUrl: this.client.buildAuthorizeUrl(params),
postMessageDataType: postMessageDataType
});

handler.login(usePostMessage, function (err, hash) {
if (typeof hash === 'object') {
// hash was already parsed, so we just return it
// it's here to be backwards compatible and should be removed in the next major version
// hash was already parsed, so we just return it.
// it's here to be backwards compatible and should be removed in the next major version.
return cb(err, hash);
}
var transaction = _this.transactionManager.getStoredTransaction(params.state);
Expand Down
53 changes: 48 additions & 5 deletions src/web-auth/silent-authentication-handler.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
var IframeHandler = require('../helper/iframe-handler');

function SilentAuthenticationHandler(auth0, authenticationUrl, timeout) {
this.auth0 = auth0;
this.authenticationUrl = authenticationUrl;
this.timeout = timeout || 60 * 1000;
function SilentAuthenticationHandler(options) {
this.authenticationUrl = options.authenticationUrl;
this.timeout = options.timeout || 60 * 1000;
this.handler = null;
this.postMessageDataType = options.postMessageDataType || false;
}

SilentAuthenticationHandler.create = function (options) {
return new SilentAuthenticationHandler(options);
};

SilentAuthenticationHandler.prototype.login = function (usePostMessage, callback) {
this.handler = new IframeHandler({
auth0: this.auth0,
url: this.authenticationUrl,
callback: callback,
eventListenerType: usePostMessage ? 'message' : 'load',
callback: this.getCallbackHandler(callback, usePostMessage),
timeout: this.timeout,
eventValidator: this.getEventValidator(),
timeoutCallback: function () {
callback(null, '#error=timeout&error_description=Timeout+during+authentication+renew.');
},
Expand All @@ -22,4 +28,41 @@ SilentAuthenticationHandler.prototype.login = function (usePostMessage, callback
this.handler.init();
};

SilentAuthenticationHandler.prototype.getEventValidator = function () {
var _this = this;
return {
isValid: function (eventData) {
switch (eventData.event.type) {
case 'message':
// Default behaviour, return all message events.
if (_this.postMessageDataType === false) {
return true;
}

return eventData.event.data.type &&
eventData.event.data.type === _this.postMessageDataType;

case 'load': // Fall through to default
default:
return true;
}
}
};
};

SilentAuthenticationHandler.prototype.getCallbackHandler = function (callback, usePostMessage) {
return function (eventData) {
var callbackValue;
if (!usePostMessage) {
callbackValue = eventData.sourceObject.contentWindow.location.hash;
} else if (typeof eventData.event.data === 'object' && eventData.event.data.hash) {
callbackValue = eventData.event.data.hash;
} else {
callbackValue = eventData.event.data;
}
callback(null, callbackValue);
};
};


module.exports = SilentAuthenticationHandler;
Loading

0 comments on commit 32151b2

Please sign in to comment.