diff --git a/README.md b/README.md index be953d21..24987be9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Active Directory Authentication Library (ADAL) for JavaScript +Active Directory Authentication Library (ADAL) for JavaScript ==================================== [![Build Status](https://travis-ci.org/AzureAD/azure-activedirectory-library-for-js.svg?branch=master)](https://travis-ci.org/AzureAD/azure-activedirectory-library-for-js) @@ -196,6 +196,8 @@ adalAuthenticationServiceProvider.init( ### Security Tokens are accessible from javascript since Adal.JS is using HTML5 storage. Default storage option is sessionStorage, which keeps the tokens per session. You should ask user to login again for important operations on your app. +You should protect your site for XSS. Please check the article here: https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet + ### CORS API usage and IE Adal will get access token for given CORS API endpoints in the config. Access token is requested using Iframe. Iframe needs to access the cookies for the same domain that you did the initial sign in. IE does not allow to access cookies in IFrame for localhost. Your url needs to be fully qualified domain i.e http://yoursite.azurewebsites.com. Chrome does not have this restriction. @@ -220,11 +222,12 @@ app.factory('contactService', ['$http', function ($http) { ``` You can read extended blogs for CORS API related to learn about Office365 usage. -Andrew’s related to Cors and office365 usage +Andrew's related to Cors and office365 usage http://www.andrewconnell.com/blog/adal-js-cors-with-o365-apis-files-sharepoint -Vittorio’s blog +Vittorio's blog http://www.cloudidentity.com/blog/2015/02/19/introducing-adal-js-v1/ http://www.cloudidentity.com/blog/2014/10/28/adal-javascript-and-angularjs-deep-dive/ ### Trusted Site settings in IE If you put your site in the trusted site list, cookies are not accessible for iFrame requests. You need to remove protected mode for Internet zone or add the authority url for the login to the trusted sites as well. + diff --git a/bower.json b/bower.json index 19e5e1bc..6e57c305 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "adal-angular", - "version": "1.0.0", + "version": "1.0.1", "homepage": "https://github.com/AzureAD/azure-activedirectory-library-for-js", "authors": [ "MSOpentech" diff --git a/lib/adal-angular.js b/lib/adal-angular.js index 2fdd34fe..ec4c7900 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -1,5 +1,5 @@ //---------------------------------------------------------------------- -// AdalJS v1.0.0 +// AdalJS v1.0.1 // @preserve Copyright (c) Microsoft Open Technologies, Inc. // All Rights Reserved // Apache License 2.0 @@ -81,13 +81,18 @@ if (typeof module !== 'undefined' && module.exports) { var locationChangeHandler = function () { var hash = $window.location.hash; - + if (_adal.isCallback(hash)) { // callback can come from login or iframe request var requestInfo = _adal.getRequestInfo(hash); _adal.saveTokenFromHash(requestInfo); - $window.location.hash = ''; + + if ($location.$$html5) { + $window.location = $window.location.origin + $window.location.pathname; + } else { + $window.location.hash = ''; + } if (requestInfo.requestType !== _adal.REQUEST_TYPE.LOGIN) { _adal.callback = $window.parent.AuthenticationContext().callback; @@ -153,35 +158,37 @@ if (typeof module !== 'undefined' && module.exports) { }, 1); }; + var loginHandler = function () { + _adal._logstatus('Login event for:' + $location.$$path); + if (_adal.config && _adal.config.localLoginUrl) { + $location.path(_adal.config.localLoginUrl); + } else { + // directly start login flow + _adal._saveItem(_adal.CONSTANTS.STORAGE.START_PAGE, $location.$$path); + _adal._logstatus('Start login at:' + window.location.href); + $rootScope.$broadcast('adal:loginRedirect'); + _adal.login(); + } + }; + + function isADLoginRequired(route, global) { + return global.requireADLogin ? route.requireADLogin !== false : !!route.requireADLogin; + } + var routeChangeHandler = function (e, nextRoute) { - if (nextRoute && nextRoute.$$route && nextRoute.$$route.requireADLogin) { + if (nextRoute && nextRoute.$$route && isADLoginRequired(nextRoute.$$route, _adal.config)) { if (!_oauthData.isAuthenticated) { - console.log('Route change event for:' + $location.$$path); - if (_adal.config && _adal.config.localLoginUrl) { - $location.path(_adal.config.localLoginUrl); - } else { - // directly start login flow - _adal._saveItem(_adal.CONSTANTS.STORAGE.START_PAGE, $location.$$path); - console.log('Start login at:' + window.location.href); - $rootScope.$broadcast('adal:loginRedirect'); - _adal.login(); - } + _adal._logstatus('Route change event for:' + $location.$$path); + loginHandler(); } } }; var stateChangeHandler = function (e, nextRoute) { - if (nextRoute && nextRoute.requireADLogin) { + if (nextRoute && isADLoginRequired(nextRoute, _adal.config)) { if (!_oauthData.isAuthenticated) { - console.log('Route change event for:' + nextRoute.url); - if (_adal.config && _adal.config.localLoginUrl) { - $location.path(_adal.config.localLoginUrl); - } else { - _adal._saveItem(_adal.CONSTANTS.STORAGE.START_PAGE, nextRoute.url); - console.log('Start login at:' + window.location.href); - $rootScope.$broadcast('adal:loginRedirect'); - _adal.login(); - } + _adal._logstatus('State change event for:' + $location.$$path); + loginHandler(); } } }; @@ -315,6 +322,6 @@ if (typeof module !== 'undefined' && module.exports) { }; }]); } else { - console.log('Angular.JS is not included'); + console.error('Angular.JS is not included'); } }()); diff --git a/lib/adal.js b/lib/adal.js index a5fc416c..4409d83c 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -1,5 +1,5 @@ //---------------------------------------------------------------------- -// AdalJS v1.0.0 +// AdalJS v1.0.1 // @preserve Copyright (c) Microsoft Open Technologies, Inc. // All Rights Reserved // Apache License 2.0 @@ -40,7 +40,7 @@ if (typeof module !== 'undefined' && module.exports) { * @property {tenant} Your target tenant * @property {clientId} Identifier assigned to your app by Azure Active Directory * @property {redirectUri} Endpoint at which you expect to receive tokens - * @property {instance} Azure Active Directory Instance(default:https://login.windows.net/) + * @property {instance} Azure Active Directory Instance(default:https://login.microsoftonline.com/) * @property {endpoints} Collection of {Endpoint-ResourceId} used for autmatically attaching tokens in webApi calls */ @@ -110,7 +110,7 @@ AuthenticationContext = function (config) { AuthenticationContext.prototype._singletonInstance = this; // public - this.instance = 'https://login.windows.net/'; + this.instance = 'https://login.microsoftonline.com/'; this.config = {}; this.callback = null; this.popUp = false; @@ -729,18 +729,18 @@ AuthenticationContext.prototype.handleWindowCallback = function () { requestInfo.requestType === this.REQUEST_TYPE.ID_TOKEN) && window.parent) { // iframe call but same single page - console.log('Window is in iframe'); + this._logstatus('Window is in iframe'); callback = window.parent.AuthenticationContext().callback; window.src = ''; } else if (window && window.oauth2Callback) { - console.log('Window is redirecting'); + this._logstatus('Window is redirecting'); callback = this.callback; } window.location.hash = ''; window.location = this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST); if (requestInfo.requestType === this.REQUEST_TYPE.RENEW_TOKEN) { - callback(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters[this.CONSTANTS.ACCESS_TOKEN]); + callback(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters[this.CONSTANTS.ACCESS_TOKEN] || || requestInfo.parameters[this.CONSTANTS.ID_TOKEN]); return; } else if (requestInfo.requestType === this.REQUEST_TYPE.ID_TOKEN) { // JS context may not have the user if callback page was different, so parse idtoken again to callback @@ -761,7 +761,7 @@ AuthenticationContext.prototype._getNavigateUrl = function (responseType, resour } var urlNavigate = this.instance + tenant + '/oauth2/authorize' + this._serialize(responseType, this.config, resource) + this._addClientId(); - console.log('Navigate url:' + urlNavigate); + this._logstatus('Navigate url:' + urlNavigate); return urlNavigate; }; diff --git a/package.json b/package.json index 13028ddc..a2bcae43 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "type": "git", "url": "" }, - "version": "1.0.0", + "version": "1.0.1", "description": "Windows Azure Active Directory Client Library for js", "keywords": [ "implicit", diff --git a/tests/unit/spec/AdalSpec.js b/tests/unit/spec/AdalSpec.js index f1b0c0e8..b5360daf 100644 --- a/tests/unit/spec/AdalSpec.js +++ b/tests/unit/spec/AdalSpec.js @@ -62,6 +62,7 @@ describe('Adal', function () { var STORAGE_TOKEN_KEYS = STORAGE_PREFIX + '.token.keys'; var RESOURCE1 = 'token.resource1'; var SECONDS_TO_EXPIRE = 3600; + var DEFAULT_INSTANCE = "https://login.microsoftonline.com/"; var IDTOKEN_MOCK = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjVUa0d0S1JrZ2FpZXpFWTJFc0xDMmdPTGpBNCJ9.eyJhdWQiOiJlOWE1YThiNi04YWY3LTQ3MTktOTgyMS0wZGVlZjI1NWY2OGUiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLXBwZS5uZXQvNTJkNGIwNzItOTQ3MC00OWZiLTg3MjEtYmMzYTFjOTkxMmExLyIsImlhdCI6MTQxMTk1OTAwMCwibmJmIjoxNDExOTU5MDAwLCJleHAiOjE0MTE5NjI5MDAsInZlciI6IjEuMCIsInRpZCI6IjUyZDRiMDcyLTk0NzAtNDlmYi04NzIxLWJjM2ExYzk5MTJhMSIsImFtciI6WyJwd2QiXSwib2lkIjoiZmEzYzVmYTctN2Q5OC00Zjk3LWJmYzQtZGJkM2E0YTAyNDMxIiwidXBuIjoidXNlckBvYXV0aGltcGxpY2l0LmNjc2N0cC5uZXQiLCJ1bmlxdWVfbmFtZSI6InVzZXJAb2F1dGhpbXBsaWNpdC5jY3NjdHAubmV0Iiwic3ViIjoiWTdUbXhFY09IUzI0NGFHa3RjbWpicnNrdk5tU1I4WHo5XzZmbVc2NXloZyIsImZhbWlseV9uYW1lIjoiYSIsImdpdmVuX25hbWUiOiJ1c2VyIiwibm9uY2UiOiI4MGZmYTkwYS1jYjc0LTRkMGYtYTRhYy1hZTFmOTNlMzJmZTAiLCJwd2RfZXhwIjoiNTc3OTkxMCIsInB3ZF91cmwiOiJodHRwczovL3BvcnRhbC5taWNyb3NvZnRvbmxpbmUuY29tL0NoYW5nZVBhc3N3b3JkLmFzcHgifQ.WHsl8TH1rQ3dQbRkV0TS6GBVAxzNOpG3nGG6mpEBCwAOCbyW6qRsSoo4qq8I5IGyerDf2cvcS-zzatHEROpRC9dcpwkRm6ta5dFZuouFyZ_QiYVKSMwfzEC_FI-6p7eT8gY6FbV51bp-Ah_WKJqEmaXv-lqjIpgsMGeWDgZRlB9cPODXosBq-PEk0q27Be-_A-KefQacJuWTX2eEhECLyuAu-ETVJb7s19jQrs_LJXz_ISib4DdTKPa7XTBDJlVGdCI18ctB67XwGmGi8MevkeKqFI8dkykTxeJ0MXMmEQbE6Fw-gxmP7uJYbZ61Jqwsw24zMDMeXatk2VWMBPCuhA'; var storageFake = function () { var store = {}; @@ -143,7 +144,7 @@ describe('Adal', function () { spyOn(adal, 'promptUser'); console.log('instance:' + adal.instance); adal.login(); - expect(adal.promptUser).toHaveBeenCalledWith('https://login.windows.net/' + conf.tenant + '/oauth2/authorize?response_type=id_token&client_id=client&redirect_uri=contoso_site&state=33333333-3333-4333-b333-333333333333' + expect(adal.promptUser).toHaveBeenCalledWith(DEFAULT_INSTANCE + conf.tenant + '/oauth2/authorize?response_type=id_token&client_id=client&redirect_uri=contoso_site&state=33333333-3333-4333-b333-333333333333' + adal._addClientId() + '&nonce=33333333-3333-4333-b333-333333333333'); expect(adal.config.state).toBe('33333333-3333-4333-b333-333333333333'); }); @@ -169,7 +170,7 @@ describe('Adal', function () { adal.config.displayCall = displayCallback; spyOn(adal.config, 'displayCall'); adal.login(); - expect(adal.config.displayCall).toHaveBeenCalledWith('https://login.windows.net/' + conf.tenant + '/oauth2/authorize?response_type=id_token&client_id=client&redirect_uri=contoso_site&state=33333333-3333-4333-b333-333333333333' + expect(adal.config.displayCall).toHaveBeenCalledWith(DEFAULT_INSTANCE + conf.tenant + '/oauth2/authorize?response_type=id_token&client_id=client&redirect_uri=contoso_site&state=33333333-3333-4333-b333-333333333333' + adal._addClientId() + '&nonce=33333333-3333-4333-b333-333333333333' ); @@ -236,7 +237,7 @@ describe('Adal', function () { runs(function () { console.log('Frame src:' + frameMock.src); - expect(frameMock.src).toBe('https://login.windows.net/' + conf.tenant + '/oauth2/authorize?response_type=token&client_id=client&resource=' + RESOURCE1 + '&redirect_uri=contoso_site&state=33333333-3333-4333-b333-333333333333%7Ctoken.resource1' + expect(frameMock.src).toBe(DEFAULT_INSTANCE + conf.tenant + '/oauth2/authorize?response_type=token&client_id=client&resource=' + RESOURCE1 + '&redirect_uri=contoso_site&state=33333333-3333-4333-b333-333333333333%7Ctoken.resource1' + adal._addClientId() + '&prompt=none&login_hint=test%40testuser.com&domain_hint=testuser.com&nonce=33333333-3333-4333-b333-333333333333'); }); @@ -344,7 +345,7 @@ describe('Adal', function () { adal.config.postLogoutRedirectUri = 'https://contoso.com/logout'; spyOn(adal, 'promptUser'); adal.logOut(); - expect(adal.promptUser).toHaveBeenCalledWith('https://login.windows.net/' + adal.config.tenant + '/oauth2/logout?post_logout_redirect_uri=https%3A%2F%2Fcontoso.com%2Flogout'); + expect(adal.promptUser).toHaveBeenCalledWith(DEFAULT_INSTANCE + adal.config.tenant + '/oauth2/logout?post_logout_redirect_uri=https%3A%2F%2Fcontoso.com%2Flogout'); }); it('uses common for tenant if not given at logout redirect', function () { @@ -355,7 +356,7 @@ describe('Adal', function () { adal.config.postLogoutRedirectUri = 'https://contoso.com/logout'; spyOn(adal, 'promptUser'); adal.logOut(); - expect(adal.promptUser).toHaveBeenCalledWith('https://login.windows.net/common/oauth2/logout?post_logout_redirect_uri=https%3A%2F%2Fcontoso.com%2Flogout'); + expect(adal.promptUser).toHaveBeenCalledWith(DEFAULT_INSTANCE + 'common/oauth2/logout?post_logout_redirect_uri=https%3A%2F%2Fcontoso.com%2Flogout'); }); it('gets user from cache', function () {