From 9152090ec66e6d36a38f2a8d418f5f60cddf48f9 Mon Sep 17 00:00:00 2001
From: LionelB <lionel@lumographe.fr>
Date: Wed, 13 Apr 2016 17:31:04 +0200
Subject: [PATCH 01/19] allow overriding default config

---
 src/providers/oauth-provider.js            | 40 +++++++------
 test/unit/providers/oauth-provider.spec.js | 67 +++++++++++++++++++++-
 2 files changed, 89 insertions(+), 18 deletions(-)

diff --git a/src/providers/oauth-provider.js b/src/providers/oauth-provider.js
index eeb3f57..cd73972 100644
--- a/src/providers/oauth-provider.js
+++ b/src/providers/oauth-provider.js
@@ -26,7 +26,7 @@ var requiredKeys = [
  */
 
 function OAuthProvider() {
-  var config;
+  var defaultConfig;
 
   /**
    * Configure.
@@ -36,7 +36,7 @@ function OAuthProvider() {
 
   this.configure = function(params) {
     // Can only be configured once.
-    if (config) {
+    if (defaultConfig) {
       throw new Error('Already configured.');
     }
 
@@ -46,31 +46,30 @@ function OAuthProvider() {
     }
 
     // Extend default configuration.
-    config = angular.extend({}, defaults, params);
+    defaultConfig = angular.extend({}, defaults, params);
 
     // Check if all required keys are set.
     angular.forEach(requiredKeys, (key) => {
-      if (!config[key]) {
+      if (!defaultConfig[key]) {
         throw new Error(`Missing parameter: ${key}.`);
       }
     });
 
     // Remove `baseUrl` trailing slash.
-    if('/' === config.baseUrl.substr(-1)) {
-      config.baseUrl = config.baseUrl.slice(0, -1);
+    if('/' === defaultConfig.baseUrl.substr(-1)) {
+      defaultConfig.baseUrl = defaultConfig.baseUrl.slice(0, -1);
     }
 
     // Add `grantPath` facing slash.
-    if('/' !== config.grantPath[0]) {
-      config.grantPath = `/${config.grantPath}`;
+    if('/' !== defaultConfig.grantPath[0]) {
+      defaultConfig.grantPath = `/${defaultConfig.grantPath}`;
     }
 
     // Add `revokePath` facing slash.
-    if('/' !== config.revokePath[0]) {
-      config.revokePath = `/${config.revokePath}`;
+    if('/' !== defaultConfig.revokePath[0]) {
+      defaultConfig.revokePath = `/${defaultConfig.revokePath}`;
     }
-
-    return config;
+    return defaultConfig;
   };
 
   /**
@@ -85,7 +84,7 @@ function OAuthProvider() {
        */
 
       constructor() {
-        if (!config) {
+        if (!defaultConfig) {
           throw new Error('`OAuthProvider` must be configured first.');
         }
       }
@@ -110,7 +109,10 @@ function OAuthProvider() {
        * @return {promise} A response promise.
        */
 
-      getAccessToken(data, options) {
+      getAccessToken(data, options, config) {
+        //Override default Oauth config
+        config = angular.extend({}, defaultConfig, config);
+
         data = angular.extend({
           client_id: config.clientId,
           grant_type: 'password'
@@ -145,7 +147,10 @@ function OAuthProvider() {
        * @return {promise} A response promise.
        */
 
-      getRefreshToken(data, options) {
+      getRefreshToken(data, options, config) {
+        //Override default Oauth config
+        config = angular.extend({}, defaultConfig, config);
+
         data = angular.extend({
           client_id: config.clientId,
           grant_type: 'refresh_token',
@@ -181,7 +186,10 @@ function OAuthProvider() {
        * @return {promise} A response promise.
        */
 
-      revokeToken(data, options) {
+      revokeToken(data, options, config) {
+        //Override default Oauth config
+        config = angular.extend(defaultConfig, config);
+
         var refreshToken = OAuthToken.getRefreshToken();
 
         data = angular.extend({
diff --git a/test/unit/providers/oauth-provider.spec.js b/test/unit/providers/oauth-provider.spec.js
index 0b56c3c..39ff315 100644
--- a/test/unit/providers/oauth-provider.spec.js
+++ b/test/unit/providers/oauth-provider.spec.js
@@ -216,6 +216,26 @@ describe('OAuthProvider', function() {
         $httpBackend.verifyNoOutstandingExpectation();
         $httpBackend.verifyNoOutstandingRequest();
       }));
+
+      it('should allow to override oauth server config', inject(function($httpBackend, OAuth, OAuthToken) {
+        var config = {
+          baseUrl: 'https://new.website.com',
+          grantPath: '/oauth2/token',
+        };
+
+        $httpBackend.expectPOST(config.baseUrl + config.grantPath, data)
+          .respond({ token_type: 'bearer', access_token: 'foo', expires_in: 3600, refresh_token: 'bar' });
+
+        OAuth.getAccessToken({
+          username: 'foo',
+          password: 'bar'
+        }, {}, config);
+
+        $httpBackend.flush();
+
+        $httpBackend.verifyNoOutstandingExpectation();
+        $httpBackend.verifyNoOutstandingRequest();
+      }));
     });
 
     describe('refreshToken()', function() {
@@ -293,8 +313,6 @@ describe('OAuthProvider', function() {
             expires_in: 3600,
             refresh_token: 'biz'
           });
-        }).catch(function() {
-          should.fail();
         });
 
         $httpBackend.flush();
@@ -302,6 +320,25 @@ describe('OAuthProvider', function() {
         $httpBackend.verifyNoOutstandingExpectation();
         $httpBackend.verifyNoOutstandingRequest();
       }));
+
+      it('should allow to override oauth server config', inject(function($httpBackend, OAuth, OAuthToken) {
+        var config = {
+          baseUrl: 'https://new.website.com',
+          grantPath: '/oauth2/token/new',
+        };
+
+        OAuthToken.setToken({ token_type: 'bearer', access_token: 'foo', expires_in: 3600, refresh_token: 'bar' });
+
+        $httpBackend.expectPOST(config.baseUrl + config.grantPath, queryString.stringify(data))
+          .respond({ token_type: 'bearer', access_token: 'qux', expires_in: 3600, refresh_token: 'biz' });
+
+        OAuth.getRefreshToken(data, {}, config);
+
+        $httpBackend.flush();
+
+        $httpBackend.verifyNoOutstandingExpectation();
+        $httpBackend.verifyNoOutstandingRequest();
+      }));
     });
 
     describe('revokeToken()', function () {
@@ -388,6 +425,32 @@ describe('OAuthProvider', function() {
         $httpBackend.verifyNoOutstandingExpectation();
         $httpBackend.verifyNoOutstandingRequest();
       }));
+
+      it('should allow to override oauth server config', inject(function($httpBackend, OAuth, OAuthToken) {
+        var data = queryString.stringify({
+          client_id: defaults.clientId,
+          token: 'bar',
+          token_type_hint: 'refresh_token',
+          client_secret: defaults.clientSecret
+        });
+
+        var config = {
+          baseUrl: 'https://new.website.com',
+          revokePath: '/oauth2/revoke/new',
+        };
+
+        OAuthToken.setToken({ token_type: 'bearer', access_token: 'foo', expires_in: 3600, refresh_token: 'bar' });
+
+        $httpBackend.expectPOST(config.baseUrl + config.revokePath, data)
+          .respond(200);
+
+        OAuth.revokeToken(data, {}, config);
+
+        $httpBackend.flush();
+
+        $httpBackend.verifyNoOutstandingExpectation();
+        $httpBackend.verifyNoOutstandingRequest();
+      }));
     });
   });
 });

From 304aeb0134c6c057367b76c646608f00853cd51f Mon Sep 17 00:00:00 2001
From: LionelB <lionel@lumographe.fr>
Date: Sun, 10 Jul 2016 16:31:51 +0200
Subject: [PATCH 02/19] allow OAuth configuration at run time

---
 README.md                                  |  14 ++
 src/providers/oauth-provider.js            |  93 ++++++------
 test/unit/providers/oauth-provider.spec.js | 165 +++++++++++----------
 3 files changed, 152 insertions(+), 120 deletions(-)

diff --git a/README.md b/README.md
index 9ff56ca..5f92ef9 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,8 @@ angular.module('myApp', ['angular-oauth2'])
   }]);
 ```
 
+ps: You can also configure service at runtime, in case you retrieve oauth server configuration from a ajax request.
+
 ###### 4. Catch `OAuth` errors and do something with them (optional):
 
 ```js
@@ -84,6 +86,18 @@ OAuthProvider.configure({
 
 #### OAuth
 
+Update configuration defaults
+
+```js
+OAuth.configure({
+  baseUrl: null,
+  clientId: null,
+  clientSecret: null,
+  grantPath: '/oauth2/token',
+  revokePath: '/oauth2/revoke'
+});
+
+```
 Check authentication status:
 
 ```js
diff --git a/src/providers/oauth-provider.js b/src/providers/oauth-provider.js
index cd73972..dff8d0b 100644
--- a/src/providers/oauth-provider.js
+++ b/src/providers/oauth-provider.js
@@ -29,46 +29,51 @@ function OAuthProvider() {
   var defaultConfig;
 
   /**
-   * Configure.
-   *
-   * @param {object} params - An `object` of params to extend.
+   * @private
+   * sanitize configuration parameters
+   * @param {object} an `object` of params to sanitize
+   * @return {object} an sanitize version of the params
    */
-
-  this.configure = function(params) {
-    // Can only be configured once.
-    if (defaultConfig) {
-      throw new Error('Already configured.');
-    }
-
-    // Check if is an `object`.
+  const sanitizeConfigParams = (params) => {
     if (!(params instanceof Object)) {
       throw new TypeError('Invalid argument: `config` must be an `Object`.');
     }
 
     // Extend default configuration.
-    defaultConfig = angular.extend({}, defaults, params);
+    const config = angular.extend({}, defaults, params);
 
     // Check if all required keys are set.
     angular.forEach(requiredKeys, (key) => {
-      if (!defaultConfig[key]) {
+      if (!config[key]) {
         throw new Error(`Missing parameter: ${key}.`);
       }
     });
 
     // Remove `baseUrl` trailing slash.
-    if('/' === defaultConfig.baseUrl.substr(-1)) {
-      defaultConfig.baseUrl = defaultConfig.baseUrl.slice(0, -1);
+    if ('/' === config.baseUrl.substr(-1)) {
+      config.baseUrl = config.baseUrl.slice(0, -1);
     }
 
     // Add `grantPath` facing slash.
-    if('/' !== defaultConfig.grantPath[0]) {
-      defaultConfig.grantPath = `/${defaultConfig.grantPath}`;
+    if ('/' !== config.grantPath[0]) {
+      config.grantPath = `/${config.grantPath}`;
     }
 
     // Add `revokePath` facing slash.
-    if('/' !== defaultConfig.revokePath[0]) {
-      defaultConfig.revokePath = `/${defaultConfig.revokePath}`;
+    if ('/' !== config.revokePath[0]) {
+      config.revokePath = `/${config.revokePath}`;
     }
+
+    return config;
+  };
+  
+  /**
+   * Configure.
+   *
+   * @param {object} params - An `object` of params to extend.
+   */
+  this.configure = function(params) {
+    defaultConfig = sanitizeConfigParams(params);
     return defaultConfig;
   };
 
@@ -88,6 +93,17 @@ function OAuthProvider() {
           throw new Error('`OAuthProvider` must be configured first.');
         }
       }
+      
+      /**
+       * Configure OAuth service during runtime
+       *
+       * @param {Object} params - An object of params to extend
+       */
+      configure(params) {
+        defaultConfig = sanitizeConfigParams(params);
+        return defaultConfig;
+      }
+
 
       /**
        * Verifies if the `user` is authenticated or not based on the `token`
@@ -109,17 +125,14 @@ function OAuthProvider() {
        * @return {promise} A response promise.
        */
 
-      getAccessToken(data, options, config) {
-        //Override default Oauth config
-        config = angular.extend({}, defaultConfig, config);
-
+      getAccessToken(data, options) {
         data = angular.extend({
-          client_id: config.clientId,
+          client_id: defaultConfig.clientId,
           grant_type: 'password'
         }, data);
 
-        if (null !== config.clientSecret) {
-          data.client_secret = config.clientSecret;
+        if (null !== defaultConfig.clientSecret) {
+          data.client_secret = defaultConfig.clientSecret;
         }
 
         data = queryString.stringify(data);
@@ -131,7 +144,7 @@ function OAuthProvider() {
           }
         }, options);
 
-        return $http.post(`${config.baseUrl}${config.grantPath}`, data, options).then((response) => {
+        return $http.post(`${defaultConfig.baseUrl}${defaultConfig.grantPath}`, data, options).then((response) => {
           OAuthToken.setToken(response.data);
 
           return response;
@@ -147,18 +160,15 @@ function OAuthProvider() {
        * @return {promise} A response promise.
        */
 
-      getRefreshToken(data, options, config) {
-        //Override default Oauth config
-        config = angular.extend({}, defaultConfig, config);
-
+      getRefreshToken(data, options) {
         data = angular.extend({
-          client_id: config.clientId,
+          client_id: defaultConfig.clientId,
           grant_type: 'refresh_token',
           refresh_token: OAuthToken.getRefreshToken(),
         }, data);
 
-        if (null !== config.clientSecret) {
-          data.client_secret = config.clientSecret;
+        if (null !== defaultConfig.clientSecret) {
+          data.client_secret = defaultConfig.clientSecret;
         }
 
         data = queryString.stringify(data);
@@ -170,7 +180,7 @@ function OAuthProvider() {
           }
         }, options);
 
-        return $http.post(`${config.baseUrl}${config.grantPath}`, data, options).then((response) => {
+        return $http.post(`${defaultConfig.baseUrl}${defaultConfig.grantPath}`, data, options).then((response) => {
           OAuthToken.setToken(response.data);
 
           return response;
@@ -186,20 +196,17 @@ function OAuthProvider() {
        * @return {promise} A response promise.
        */
 
-      revokeToken(data, options, config) {
-        //Override default Oauth config
-        config = angular.extend(defaultConfig, config);
-
+      revokeToken(data, options) {
         var refreshToken = OAuthToken.getRefreshToken();
 
         data = angular.extend({
-          client_id: config.clientId,
+          client_id: defaultConfig.clientId,
           token: refreshToken ? refreshToken : OAuthToken.getAccessToken(),
           token_type_hint: refreshToken ? 'refresh_token' : 'access_token'
         }, data);
 
-        if (null !== config.clientSecret) {
-          data.client_secret = config.clientSecret;
+        if (null !== defaultConfig.clientSecret) {
+          data.client_secret = defaultConfig.clientSecret;
         }
 
         data = queryString.stringify(data);
@@ -210,7 +217,7 @@ function OAuthProvider() {
           }
         }, options);
 
-        return $http.post(`${config.baseUrl}${config.revokePath}`, data, options).then((response) => {
+        return $http.post(`${defaultConfig.baseUrl}${defaultConfig.revokePath}`, data, options).then((response) => {
           OAuthToken.removeToken();
 
           return response;
diff --git a/test/unit/providers/oauth-provider.spec.js b/test/unit/providers/oauth-provider.spec.js
index 39ff315..26f2eb8 100644
--- a/test/unit/providers/oauth-provider.spec.js
+++ b/test/unit/providers/oauth-provider.spec.js
@@ -13,7 +13,7 @@ describe('OAuthProvider', function() {
   };
 
   describe('configure()', function() {
-    var provider;
+    var provider, testSuite;
 
     beforeEach(function() {
       angular.module('angular-oauth2.test', [])
@@ -37,17 +37,6 @@ describe('OAuthProvider', function() {
       }
     });
 
-    it('should throw an error if already configured', function() {
-      try {
-        provider.configure(defaults);
-        provider.configure(defaults);
-
-        should.fail();
-      } catch(e) {
-        e.should.be.an.instanceOf(Error);
-      }
-    });
-
     it('should throw an error if `baseUrl` param is empty', function() {
       try {
         provider.configure(_.omit(defaults, 'baseUrl'));
@@ -137,6 +126,93 @@ describe('OAuthProvider', function() {
       OAuthToken.removeToken();
     }));
 
+    describe('configure()', function() {
+      it('should throw an error if configuration is not an object', inject(function(OAuth) {
+        try {
+          OAuth.configure(false);
+
+          should.fail();
+        } catch(e) {
+          e.should.be.an.instanceOf(TypeError);
+          e.message.should.match(/config/);
+        }
+      }));
+      
+      it('should throw an error if `baseUrl` param is empty', inject(function(OAuth) {
+        try {
+          OAuth.configure(_.omit(defaults, 'baseUrl'));
+
+          should.fail();
+        } catch(e) {
+          e.should.be.an.instanceOf(Error);
+          e.message.should.match(/baseUrl/);
+        }
+      }));
+
+      it('should throw an error if `clientId` param is empty', inject(function(OAuth) {
+        try {
+          OAuth.configure(_.omit(defaults, 'clientId'));
+
+          should.fail();
+        } catch(e) {
+          e.should.be.an.instanceOf(Error);
+          e.message.should.match(/clientId/);
+        }
+      }));
+
+      it('should not throw an error if `clientSecret` param is empty', inject(function(OAuth) {
+        var config = OAuth.configure(_.omit(defaults, 'clientSecret'));
+
+        (null === config.clientSecret).should.true;
+      }));
+
+      it('should throw an error if `grantPath` param is empty', inject(function(OAuth) {
+        try {
+          OAuth.configure(_.defaults({ grantPath: null }, defaults));
+
+          should.fail();
+        } catch(e) {
+          e.should.be.an.instanceOf(Error);
+          e.message.should.match(/grantPath/);
+        }
+      }));
+
+      it('should remove trailing slash from `baseUrl`', inject(function(OAuth) {
+        var config = OAuth.configure(_.defaults({
+          baseUrl: 'https://api.website.com/'
+        }, defaults));
+
+        config.baseUrl.should.equal('https://api.website.com');
+      }));
+
+      it('should add facing slash from `grantPath`', inject(function(OAuth) {
+        var config = OAuth.configure(_.defaults({
+          grantPath: 'oauth2/token'
+        }, defaults));
+
+        config.grantPath.should.equal('/oauth2/token');
+      }));
+
+      it('should throw an error if `revokePath` param is empty', inject(function(OAuth) {
+        try {
+          OAuth.configure(_.defaults({ revokePath: null }, defaults));
+
+          should.fail();
+        } catch(e) {
+          e.should.be.an.instanceOf(Error);
+          e.message.should.match(/revokePath/);
+        }
+      }));
+
+      it('should add facing slash from `revokePath`', inject(function(OAuth) {
+        var config = OAuth.configure(_.defaults({
+          revokePath: 'oauth2/revoke'
+        }, defaults));
+
+        config.revokePath.should.equal('/oauth2/revoke');
+      }));
+    });
+
     describe('isAuthenticated()', function() {
       it('should be true when there is a stored `token` cookie', inject(function(OAuth, OAuthToken) {
         OAuthToken.setToken({ token_type: 'bearer', access_token: 'foo', expires_in: 3600, refresh_token: 'bar' });
@@ -216,26 +292,6 @@ describe('OAuthProvider', function() {
         $httpBackend.verifyNoOutstandingExpectation();
         $httpBackend.verifyNoOutstandingRequest();
       }));
-
-      it('should allow to override oauth server config', inject(function($httpBackend, OAuth, OAuthToken) {
-        var config = {
-          baseUrl: 'https://new.website.com',
-          grantPath: '/oauth2/token',
-        };
-
-        $httpBackend.expectPOST(config.baseUrl + config.grantPath, data)
-          .respond({ token_type: 'bearer', access_token: 'foo', expires_in: 3600, refresh_token: 'bar' });
-
-        OAuth.getAccessToken({
-          username: 'foo',
-          password: 'bar'
-        }, {}, config);
-
-        $httpBackend.flush();
-
-        $httpBackend.verifyNoOutstandingExpectation();
-        $httpBackend.verifyNoOutstandingRequest();
-      }));
     });
 
     describe('refreshToken()', function() {
@@ -320,25 +376,6 @@ describe('OAuthProvider', function() {
         $httpBackend.verifyNoOutstandingExpectation();
         $httpBackend.verifyNoOutstandingRequest();
       }));
-
-      it('should allow to override oauth server config', inject(function($httpBackend, OAuth, OAuthToken) {
-        var config = {
-          baseUrl: 'https://new.website.com',
-          grantPath: '/oauth2/token/new',
-        };
-
-        OAuthToken.setToken({ token_type: 'bearer', access_token: 'foo', expires_in: 3600, refresh_token: 'bar' });
-
-        $httpBackend.expectPOST(config.baseUrl + config.grantPath, queryString.stringify(data))
-          .respond({ token_type: 'bearer', access_token: 'qux', expires_in: 3600, refresh_token: 'biz' });
-
-        OAuth.getRefreshToken(data, {}, config);
-
-        $httpBackend.flush();
-
-        $httpBackend.verifyNoOutstandingExpectation();
-        $httpBackend.verifyNoOutstandingRequest();
-      }));
     });
 
     describe('revokeToken()', function () {
@@ -425,32 +462,6 @@ describe('OAuthProvider', function() {
         $httpBackend.verifyNoOutstandingExpectation();
         $httpBackend.verifyNoOutstandingRequest();
       }));
-
-      it('should allow to override oauth server config', inject(function($httpBackend, OAuth, OAuthToken) {
-        var data = queryString.stringify({
-          client_id: defaults.clientId,
-          token: 'bar',
-          token_type_hint: 'refresh_token',
-          client_secret: defaults.clientSecret
-        });
-
-        var config = {
-          baseUrl: 'https://new.website.com',
-          revokePath: '/oauth2/revoke/new',
-        };
-
-        OAuthToken.setToken({ token_type: 'bearer', access_token: 'foo', expires_in: 3600, refresh_token: 'bar' });
-
-        $httpBackend.expectPOST(config.baseUrl + config.revokePath, data)
-          .respond(200);
-
-        OAuth.revokeToken(data, {}, config);
-
-        $httpBackend.flush();
-
-        $httpBackend.verifyNoOutstandingExpectation();
-        $httpBackend.verifyNoOutstandingRequest();
-      }));
     });
   });
 });

From b2a878facfd7cb1728fd544f8386c94bfa82cb26 Mon Sep 17 00:00:00 2001
From: Timokasse <Timokasse@users.noreply.github.com>
Date: Tue, 9 Aug 2016 12:42:27 +0200
Subject: [PATCH 03/19] Specify the type of grant used (#96)

Detail the authentication mechanism used
---
 README.md | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/README.md b/README.md
index 9ff56ca..edade1e 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,18 @@
 
 AngularJS OAuth2 authentication module written in ES6.
 
+Currently `angular-oauth2` only uses the [Resouce Owner Password Credential Grant](https://tools.ietf.org/html/rfc6749#section-4.3), i.e, using a credentials combination (username, password), we'll request an access token (using `grant_type='password'`) which, in case of success, will typically return a response such as:
+
+```
+{
+  "access_token": "foobar",
+  "token_type": "Bearer",
+  "expires_in": 3600,
+  "refresh_token": "foobiz"
+}
+```
+Internally we'll automatically store it as a cookie and it will be used in every request adding an `Authorization` header: `Authorization: 'Bearer foobar'`.
+
 ---
 
 ## Installation

From 7cb1dbb2d5d8ed24edcbe3f7cf6881680b5eac95 Mon Sep 17 00:00:00 2001
From: LionelB <lionel@lumographe.fr>
Date: Wed, 17 Aug 2016 15:33:31 +0200
Subject: [PATCH 04/19] fix closure memory leak in OAuth-provider

---
 src/providers/oauth-provider.js            | 41 +++++++++-----------
 test/unit/providers/oauth-provider.spec.js | 44 +++++++++++++---------
 2 files changed, 44 insertions(+), 41 deletions(-)

diff --git a/src/providers/oauth-provider.js b/src/providers/oauth-provider.js
index dff8d0b..2de0045 100644
--- a/src/providers/oauth-provider.js
+++ b/src/providers/oauth-provider.js
@@ -26,7 +26,6 @@ var requiredKeys = [
  */
 
 function OAuthProvider() {
-  var defaultConfig;
 
   /**
    * @private
@@ -72,9 +71,8 @@ function OAuthProvider() {
    *
    * @param {object} params - An `object` of params to extend.
    */
-  this.configure = function(params) {
-    defaultConfig = sanitizeConfigParams(params);
-    return defaultConfig;
+  this.configure = (params) => {
+    this.defaultConfig = sanitizeConfigParams(params);
   };
 
   /**
@@ -88,10 +86,8 @@ function OAuthProvider() {
        * Check if `OAuthProvider` is configured.
        */
 
-      constructor() {
-        if (!defaultConfig) {
-          throw new Error('`OAuthProvider` must be configured first.');
-        }
+      constructor(config) {
+        this.config = config;
       }
       
       /**
@@ -100,8 +96,7 @@ function OAuthProvider() {
        * @param {Object} params - An object of params to extend
        */
       configure(params) {
-        defaultConfig = sanitizeConfigParams(params);
-        return defaultConfig;
+        this.config = sanitizeConfigParams(params);
       }
 
 
@@ -127,12 +122,12 @@ function OAuthProvider() {
 
       getAccessToken(data, options) {
         data = angular.extend({
-          client_id: defaultConfig.clientId,
+          client_id: this.config.clientId,
           grant_type: 'password'
         }, data);
 
-        if (null !== defaultConfig.clientSecret) {
-          data.client_secret = defaultConfig.clientSecret;
+        if (null !== this.config.clientSecret) {
+          data.client_secret = this.config.clientSecret;
         }
 
         data = queryString.stringify(data);
@@ -144,7 +139,7 @@ function OAuthProvider() {
           }
         }, options);
 
-        return $http.post(`${defaultConfig.baseUrl}${defaultConfig.grantPath}`, data, options).then((response) => {
+        return $http.post(`${this.config.baseUrl}${this.config.grantPath}`, data, options).then((response) => {
           OAuthToken.setToken(response.data);
 
           return response;
@@ -162,13 +157,13 @@ function OAuthProvider() {
 
       getRefreshToken(data, options) {
         data = angular.extend({
-          client_id: defaultConfig.clientId,
+          client_id: this.config.clientId,
           grant_type: 'refresh_token',
           refresh_token: OAuthToken.getRefreshToken(),
         }, data);
 
-        if (null !== defaultConfig.clientSecret) {
-          data.client_secret = defaultConfig.clientSecret;
+        if (null !== this.config.clientSecret) {
+          data.client_secret = this.config.clientSecret;
         }
 
         data = queryString.stringify(data);
@@ -180,7 +175,7 @@ function OAuthProvider() {
           }
         }, options);
 
-        return $http.post(`${defaultConfig.baseUrl}${defaultConfig.grantPath}`, data, options).then((response) => {
+        return $http.post(`${this.config.baseUrl}${this.config.grantPath}`, data, options).then((response) => {
           OAuthToken.setToken(response.data);
 
           return response;
@@ -200,13 +195,13 @@ function OAuthProvider() {
         var refreshToken = OAuthToken.getRefreshToken();
 
         data = angular.extend({
-          client_id: defaultConfig.clientId,
+          client_id: this.config.clientId,
           token: refreshToken ? refreshToken : OAuthToken.getAccessToken(),
           token_type_hint: refreshToken ? 'refresh_token' : 'access_token'
         }, data);
 
-        if (null !== defaultConfig.clientSecret) {
-          data.client_secret = defaultConfig.clientSecret;
+        if (null !== this.config.clientSecret) {
+          data.client_secret = this.config.clientSecret;
         }
 
         data = queryString.stringify(data);
@@ -217,7 +212,7 @@ function OAuthProvider() {
           }
         }, options);
 
-        return $http.post(`${defaultConfig.baseUrl}${defaultConfig.revokePath}`, data, options).then((response) => {
+        return $http.post(`${this.config.baseUrl}${this.config.revokePath}`, data, options).then((response) => {
           OAuthToken.removeToken();
 
           return response;
@@ -225,7 +220,7 @@ function OAuthProvider() {
       }
     }
 
-    return new OAuth();
+    return new OAuth(this.defaultConfig);
   };
 
   this.$get.$inject = ['$http', 'OAuthToken'];
diff --git a/test/unit/providers/oauth-provider.spec.js b/test/unit/providers/oauth-provider.spec.js
index 26f2eb8..4e4e076 100644
--- a/test/unit/providers/oauth-provider.spec.js
+++ b/test/unit/providers/oauth-provider.spec.js
@@ -60,9 +60,10 @@ describe('OAuthProvider', function() {
     });
 
     it('should not throw an error if `clientSecret` param is empty', function() {
-      var config = provider.configure(_.omit(defaults, 'clientSecret'));
-
-      (null === config.clientSecret).should.true;
+      try {
+        provider.configure(_.omit(defaults, 'clientSecret'));
+        should.not.fail();
+      } catch(e) {}
     });
 
     it('should throw an error if `grantPath` param is empty', function() {
@@ -77,19 +78,19 @@ describe('OAuthProvider', function() {
     });
 
     it('should remove trailing slash from `baseUrl`', function() {
-      var config = provider.configure(_.defaults({
+      provider.configure(_.defaults({
         baseUrl: 'https://api.website.com/'
       }, defaults));
 
-      config.baseUrl.should.equal('https://api.website.com');
+      provider.defaultConfig.baseUrl.should.equal('https://api.website.com');
     });
 
     it('should add facing slash from `grantPath`', function() {
-      var config = provider.configure(_.defaults({
+      provider.configure(_.defaults({
         grantPath: 'oauth2/token'
       }, defaults));
 
-      config.grantPath.should.equal('/oauth2/token');
+      provider.defaultConfig.grantPath.should.equal('/oauth2/token');
     });
 
     it('should throw an error if `revokePath` param is empty', function() {
@@ -104,11 +105,11 @@ describe('OAuthProvider', function() {
     });
 
     it('should add facing slash from `revokePath`', function() {
-      var config = provider.configure(_.defaults({
+      provider.configure(_.defaults({
         revokePath: 'oauth2/revoke'
       }, defaults));
 
-      config.revokePath.should.equal('/oauth2/revoke');
+      provider.defaultConfig.revokePath.should.equal('/oauth2/revoke');
     });
   });
 
@@ -125,6 +126,11 @@ describe('OAuthProvider', function() {
     afterEach(inject(function(OAuthToken) {
       OAuthToken.removeToken();
     }));
+    describe('construtor', function() {
+      it('should set initialize config with data passed in configure', inject(function(OAuth) {
+        OAuth.config.should.eql(defaults);
+      }))
+    })
 
     describe('configure()', function() {
       it('should throw an error if configuration is not an object', inject(function(OAuth) {
@@ -161,9 +167,11 @@ describe('OAuthProvider', function() {
       }));
 
       it('should not throw an error if `clientSecret` param is empty', inject(function(OAuth) {
-        var config = OAuth.configure(_.omit(defaults, 'clientSecret'));
-
-        (null === config.clientSecret).should.true;
+        try{
+          OAuth.configure(_.omit(defaults, 'clientSecret'));
+          
+          should.not.fail();
+        } catch(e) {}
       }));
 
       it('should throw an error if `grantPath` param is empty', inject(function(OAuth) {
@@ -178,19 +186,19 @@ describe('OAuthProvider', function() {
       }));
 
       it('should remove trailing slash from `baseUrl`', inject(function(OAuth) {
-        var config = OAuth.configure(_.defaults({
+        OAuth.configure(_.defaults({
           baseUrl: 'https://api.website.com/'
         }, defaults));
 
-        config.baseUrl.should.equal('https://api.website.com');
+        OAuth.config.baseUrl.should.equal('https://api.website.com');
       }));
 
       it('should add facing slash from `grantPath`', inject(function(OAuth) {
-        var config = OAuth.configure(_.defaults({
+        OAuth.configure(_.defaults({
           grantPath: 'oauth2/token'
         }, defaults));
 
-        config.grantPath.should.equal('/oauth2/token');
+        OAuth.config.grantPath.should.equal('/oauth2/token');
       }));
 
       it('should throw an error if `revokePath` param is empty', inject(function(OAuth) {
@@ -205,11 +213,11 @@ describe('OAuthProvider', function() {
       }));
 
       it('should add facing slash from `revokePath`', inject(function(OAuth) {
-        var config = OAuth.configure(_.defaults({
+        OAuth.configure(_.defaults({
           revokePath: 'oauth2/revoke'
         }, defaults));
 
-        config.revokePath.should.equal('/oauth2/revoke');
+        OAuth.config.revokePath.should.equal('/oauth2/revoke');
       }));
     });
 

From bda928374062f11471b3d4d824490f6cfc9738bc Mon Sep 17 00:00:00 2001
From: LionelB <lionel@lumographe.fr>
Date: Wed, 17 Aug 2016 15:34:06 +0200
Subject: [PATCH 05/19] update README with runtime configuration docs

---
 README.md | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 5f92ef9..2d372ed 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@ If you're using `bower` they will be automatically downloaded upon installing th
 <script src="<VENDOR_FOLDER>/angular-oauth2/dist/angular-oauth2.min.js"></script>
 ```
 
-###### 3. Configure `OAuth` (required) and `OAuthToken` (optional):
+###### 3. Configure `OAuth` (optional) and `OAuthToken` (optional):
 
 ```js
 angular.module('myApp', ['angular-oauth2'])
@@ -44,7 +44,18 @@ angular.module('myApp', ['angular-oauth2'])
   }]);
 ```
 
-ps: You can also configure service at runtime, in case you retrieve oauth server configuration from a ajax request.
+You can also configure `OAuth` service in a `.run()`block, in case you retrieve the Oauth server configuration from a ajax request.
+
+```js
+angular.module('myApp', ['angular-oauth2'])
+  .run(['OAuth', function(OAuth) {
+    OAuth.configure({
+      baseUrl: 'https://api.website.com',
+      clientId: 'CLIENT_ID',
+      clientSecret: 'CLIENT_SECRET' // optional
+    });
+  }]);
+```
 
 ###### 4. Catch `OAuth` errors and do something with them (optional):
 

From 2a71da7bf039509b7690be9e3a339c38884a6617 Mon Sep 17 00:00:00 2001
From: LionelB <lionel@lumographe.fr>
Date: Wed, 2 Nov 2016 21:40:37 +0100
Subject: [PATCH 06/19] remove unused testSuite var

---
 test/unit/providers/oauth-provider.spec.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/unit/providers/oauth-provider.spec.js b/test/unit/providers/oauth-provider.spec.js
index 4e4e076..dbb447c 100644
--- a/test/unit/providers/oauth-provider.spec.js
+++ b/test/unit/providers/oauth-provider.spec.js
@@ -13,7 +13,7 @@ describe('OAuthProvider', function() {
   };
 
   describe('configure()', function() {
-    var provider, testSuite;
+    var provider;
 
     beforeEach(function() {
       angular.module('angular-oauth2.test', [])

From 4109fb8dfe21284815e811ec0b593d9b2918207c Mon Sep 17 00:00:00 2001
From: Rui Silva <rui.penso@gmail.com>
Date: Thu, 3 Nov 2016 11:18:45 +0000
Subject: [PATCH 07/19] Release 4.1.0

---
 CHANGELOG.md               | 55 ++++++++++++++++++-----------------
 dist/angular-oauth2.js     | 59 ++++++++++++++++++++------------------
 dist/angular-oauth2.min.js |  2 +-
 package.json               |  2 +-
 4 files changed, 62 insertions(+), 56 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab24432..2d6fa99 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,48 +1,51 @@
 ## Changelog
 
-### upcoming / 2016-02-12
-- [#80](https://github.com/seegno/angular-oauth2/pull/80) Reintroduce authorization header to be overridden (@ruipenso)
+### 4.1.0 / 2016-11-03
+- [#87](https://github.com/oauthjs/angular-oauth2/pull/87) allow overriding oauth base config using options (@lionelB)
+
+### 4.0.0 / 2016-02-12
+- [#80](https://github.com/oauthjs/angular-oauth2/pull/80) Reintroduce authorization header to be overridden (@ruipenso)
 
 ### 3.1.1 / 2016-02-12
-- [#79](https://github.com/seegno/angular-oauth2/pull/79) Update dependencies (@ruipenso)
+- [#79](https://github.com/oauthjs/angular-oauth2/pull/79) Update dependencies (@ruipenso)
 
 ### 3.1.0 / 2016-02-10
-- [#78](https://github.com/seegno/angular-oauth2/pull/78) Update OAuth methods to allow `data` and `options` override (@ruipenso)
-- [#71](https://github.com/seegno/angular-oauth2/pull/71) Add `client_id` and `client_secret` on revoke_token (@tinogomes)
-- [#77](https://github.com/seegno/angular-oauth2/pull/77) Update interceptor to allow authorization header to be overridden (@ruipenso)
-- [#75](https://github.com/seegno/angular-oauth2/pull/75) Update README dependencies (@ruipenso)
-- [#76](https://github.com/seegno/angular-oauth2/pull/76) Update `package.json` (@ruipenso)
+- [#78](https://github.com/oauthjs/angular-oauth2/pull/78) Update OAuth methods to allow `data` and `options` override (@ruipenso)
+- [#71](https://github.com/oauthjs/angular-oauth2/pull/71) Add `client_id` and `client_secret` on revoke_token (@tinogomes)
+- [#77](https://github.com/oauthjs/angular-oauth2/pull/77) Update interceptor to allow authorization header to be overridden (@ruipenso)
+- [#75](https://github.com/oauthjs/angular-oauth2/pull/75) Update README dependencies (@ruipenso)
+- [#76](https://github.com/oauthjs/angular-oauth2/pull/76) Update `package.json` (@ruipenso)
 
 ### 3.0.1 / 2015-06-01
-- [#27](https://github.com/seegno/angular-oauth2/pull/27) Add travis-ci configuration (@joaogranado)
+- [#27](https://github.com/oauthjs/angular-oauth2/pull/27) Add travis-ci configuration (@joaogranado)
 
 ### 3.0.0 / 2015-06-01
-- [#49](https://github.com/seegno/angular-oauth2/pull/49) Add changelog task (@ruipenso)
-- [#48](https://github.com/seegno/angular-oauth2/pull/48) Update readme (@ruipenso)
-- [#25](https://github.com/seegno/angular-oauth2/pull/25) Replace ipCookie with ngCookies (@joaogranado)
-- [#28](https://github.com/seegno/angular-oauth2/pull/28) Add methods to get/set `token` property (@joaogranado)
-- [#47](https://github.com/seegno/angular-oauth2/pull/47) Add unauthorized error interception (@ruipenso)
+- [#49](https://github.com/oauthjs/angular-oauth2/pull/49) Add changelog task (@ruipenso)
+- [#48](https://github.com/oauthjs/angular-oauth2/pull/48) Update readme (@ruipenso)
+- [#25](https://github.com/oauthjs/angular-oauth2/pull/25) Replace ipCookie with ngCookies (@joaogranado)
+- [#28](https://github.com/oauthjs/angular-oauth2/pull/28) Add methods to get/set `token` property (@joaogranado)
+- [#47](https://github.com/oauthjs/angular-oauth2/pull/47) Add unauthorized error interception (@ruipenso)
 
 ### 2.1.1 / 2015-05-28
-- [#40](https://github.com/seegno/angular-oauth2/pull/40) Fix missing dependency on readme example (@ruipenso)
+- [#40](https://github.com/oauthjs/angular-oauth2/pull/40) Fix missing dependency on readme example (@ruipenso)
 
 ### 2.1.0 / 2015-03-09
-- [#15](https://github.com/seegno/angular-oauth2/pull/15) Add `clientSecret` as optional (@ruipenso)
-- [#18](https://github.com/seegno/angular-oauth2/pull/18) Remove npm postinstall script (@ruipenso)
-- [#14](https://github.com/seegno/angular-oauth2/pull/14) Fix readme  configuration (@ruipenso)
+- [#15](https://github.com/oauthjs/angular-oauth2/pull/15) Add `clientSecret` as optional (@ruipenso)
+- [#18](https://github.com/oauthjs/angular-oauth2/pull/18) Remove npm postinstall script (@ruipenso)
+- [#14](https://github.com/oauthjs/angular-oauth2/pull/14) Fix readme  configuration (@ruipenso)
 
 ### 2.0.0 / 2015-02-04
-- [#11](https://github.com/seegno/angular-oauth2/pull/11) Add options to `ipCookie.remove()` on OAuthToken (@ruipenso)
-- [#10](https://github.com/seegno/angular-oauth2/pull/10) Update `oauthInterceptor` responseError handling (@ruipenso)
-- [#7](https://github.com/seegno/angular-oauth2/pull/7) Fix readme typo of download url (@brunnolou)
-- [#6](https://github.com/seegno/angular-oauth2/pull/6) README.md: typo fix (@AdirAmsalem)
+- [#11](https://github.com/oauthjs/angular-oauth2/pull/11) Add options to `ipCookie.remove()` on OAuthToken (@ruipenso)
+- [#10](https://github.com/oauthjs/angular-oauth2/pull/10) Update `oauthInterceptor` responseError handling (@ruipenso)
+- [#7](https://github.com/oauthjs/angular-oauth2/pull/7) Fix readme typo of download url (@brunnolou)
+- [#6](https://github.com/oauthjs/angular-oauth2/pull/6) README.md: typo fix (@AdirAmsalem)
 
 ### 1.0.2 / 2015-01-19
-- [#4](https://github.com/seegno/angular-oauth2/pull/4) Add support for higher dependencies versions (@joaogranado)
+- [#4](https://github.com/oauthjs/angular-oauth2/pull/4) Add support for higher dependencies versions (@joaogranado)
 
 ### 1.0.1 / 2015-01-19
-- [#3](https://github.com/seegno/angular-oauth2/pull/3) Fix bower ignore list (@ruipenso)
+- [#3](https://github.com/oauthjs/angular-oauth2/pull/3) Fix bower ignore list (@ruipenso)
 
 ### 1.0.0 / 2015-01-18
-- [#2](https://github.com/seegno/angular-oauth2/pull/2) Fix indentation in README examples (@fixe)
-- [#1](https://github.com/seegno/angular-oauth2/pull/1) Add project structure (@ruipenso)
\ No newline at end of file
+- [#2](https://github.com/oauthjs/angular-oauth2/pull/2) Fix indentation in README examples (@fixe)
+- [#1](https://github.com/oauthjs/angular-oauth2/pull/1) Add project structure (@ruipenso)
diff --git a/dist/angular-oauth2.js b/dist/angular-oauth2.js
index 14bb50e..91122c9 100644
--- a/dist/angular-oauth2.js
+++ b/dist/angular-oauth2.js
@@ -1,6 +1,6 @@
 /**
  * angular-oauth2 - Angular OAuth2
- * @version v4.0.0
+ * @version v4.1.0
  * @link https://github.com/seegno/angular-oauth2
  * @license MIT
  */
@@ -14,10 +14,6 @@
     }
 })(this, function(angular, ngCookies, queryString) {
     var ngModule = angular.module("angular-oauth2", [ ngCookies ]).config(oauthConfig).factory("oauthInterceptor", oauthInterceptor).provider("OAuth", OAuthProvider).provider("OAuthToken", OAuthTokenProvider);
-    function oauthConfig($httpProvider) {
-        $httpProvider.interceptors.push("oauthInterceptor");
-    }
-    oauthConfig.$inject = [ "$httpProvider" ];
     function oauthInterceptor($q, $rootScope, OAuthToken) {
         return {
             request: function request(config) {
@@ -40,6 +36,10 @@
         };
     }
     oauthInterceptor.$inject = [ "$q", "$rootScope", "OAuthToken" ];
+    function oauthConfig($httpProvider) {
+        $httpProvider.interceptors.push("oauthInterceptor");
+    }
+    oauthConfig.$inject = [ "$httpProvider" ];
     var _createClass = function() {
         function defineProperties(target, props) {
             for (var i = 0; i < props.length; i++) {
@@ -70,15 +70,12 @@
     };
     var requiredKeys = [ "baseUrl", "clientId", "grantPath", "revokePath" ];
     function OAuthProvider() {
-        var config;
-        this.configure = function(params) {
-            if (config) {
-                throw new Error("Already configured.");
-            }
+        var _this = this;
+        var sanitizeConfigParams = function sanitizeConfigParams(params) {
             if (!(params instanceof Object)) {
                 throw new TypeError("Invalid argument: `config` must be an `Object`.");
             }
-            config = angular.extend({}, defaults, params);
+            var config = angular.extend({}, defaults, params);
             angular.forEach(requiredKeys, function(key) {
                 if (!config[key]) {
                     throw new Error("Missing parameter: " + key + ".");
@@ -95,15 +92,21 @@
             }
             return config;
         };
+        this.configure = function(params) {
+            _this.defaultConfig = sanitizeConfigParams(params);
+        };
         this.$get = function($http, OAuthToken) {
             var OAuth = function() {
-                function OAuth() {
+                function OAuth(config) {
                     _classCallCheck(this, OAuth);
-                    if (!config) {
-                        throw new Error("`OAuthProvider` must be configured first.");
-                    }
+                    this.config = config;
                 }
                 _createClass(OAuth, [ {
+                    key: "configure",
+                    value: function configure(params) {
+                        this.config = sanitizeConfigParams(params);
+                    }
+                }, {
                     key: "isAuthenticated",
                     value: function isAuthenticated() {
                         return !!OAuthToken.getToken();
@@ -112,11 +115,11 @@
                     key: "getAccessToken",
                     value: function getAccessToken(data, options) {
                         data = angular.extend({
-                            client_id: config.clientId,
+                            client_id: this.config.clientId,
                             grant_type: "password"
                         }, data);
-                        if (null !== config.clientSecret) {
-                            data.client_secret = config.clientSecret;
+                        if (null !== this.config.clientSecret) {
+                            data.client_secret = this.config.clientSecret;
                         }
                         data = queryString.stringify(data);
                         options = angular.extend({
@@ -125,7 +128,7 @@
                                 "Content-Type": "application/x-www-form-urlencoded"
                             }
                         }, options);
-                        return $http.post("" + config.baseUrl + config.grantPath, data, options).then(function(response) {
+                        return $http.post("" + this.config.baseUrl + this.config.grantPath, data, options).then(function(response) {
                             OAuthToken.setToken(response.data);
                             return response;
                         });
@@ -134,12 +137,12 @@
                     key: "getRefreshToken",
                     value: function getRefreshToken(data, options) {
                         data = angular.extend({
-                            client_id: config.clientId,
+                            client_id: this.config.clientId,
                             grant_type: "refresh_token",
                             refresh_token: OAuthToken.getRefreshToken()
                         }, data);
-                        if (null !== config.clientSecret) {
-                            data.client_secret = config.clientSecret;
+                        if (null !== this.config.clientSecret) {
+                            data.client_secret = this.config.clientSecret;
                         }
                         data = queryString.stringify(data);
                         options = angular.extend({
@@ -148,7 +151,7 @@
                                 "Content-Type": "application/x-www-form-urlencoded"
                             }
                         }, options);
-                        return $http.post("" + config.baseUrl + config.grantPath, data, options).then(function(response) {
+                        return $http.post("" + this.config.baseUrl + this.config.grantPath, data, options).then(function(response) {
                             OAuthToken.setToken(response.data);
                             return response;
                         });
@@ -158,12 +161,12 @@
                     value: function revokeToken(data, options) {
                         var refreshToken = OAuthToken.getRefreshToken();
                         data = angular.extend({
-                            client_id: config.clientId,
+                            client_id: this.config.clientId,
                             token: refreshToken ? refreshToken : OAuthToken.getAccessToken(),
                             token_type_hint: refreshToken ? "refresh_token" : "access_token"
                         }, data);
-                        if (null !== config.clientSecret) {
-                            data.client_secret = config.clientSecret;
+                        if (null !== this.config.clientSecret) {
+                            data.client_secret = this.config.clientSecret;
                         }
                         data = queryString.stringify(data);
                         options = angular.extend({
@@ -171,7 +174,7 @@
                                 "Content-Type": "application/x-www-form-urlencoded"
                             }
                         }, options);
-                        return $http.post("" + config.baseUrl + config.revokePath, data, options).then(function(response) {
+                        return $http.post("" + this.config.baseUrl + this.config.revokePath, data, options).then(function(response) {
                             OAuthToken.removeToken();
                             return response;
                         });
@@ -179,7 +182,7 @@
                 } ]);
                 return OAuth;
             }();
-            return new OAuth();
+            return new OAuth(this.defaultConfig);
         };
         this.$get.$inject = [ "$http", "OAuthToken" ];
     }
diff --git a/dist/angular-oauth2.min.js b/dist/angular-oauth2.min.js
index becd693..98e09a3 100644
--- a/dist/angular-oauth2.min.js
+++ b/dist/angular-oauth2.min.js
@@ -1 +1 @@
-!function(e,t){"function"==typeof define&&define.amd?define(["angular","angular-cookies","query-string"],t):"object"==typeof exports?module.exports=t(require("angular"),require("angular-cookies"),require("query-string")):e.angularOAuth2=t(e.angular,"ngCookies",e.queryString)}(this,function(e,t,n){function r(e){e.interceptors.push("oauthInterceptor")}function o(e,t,n){return{request:function(e){return e.headers=e.headers||{},!e.headers.hasOwnProperty("Authorization")&&n.getAuthorizationHeader()&&(e.headers.Authorization=n.getAuthorizationHeader()),e},responseError:function(r){return 400!==r.status||!r.data||"invalid_request"!==r.data.error&&"invalid_grant"!==r.data.error||(n.removeToken(),t.$emit("oauth:error",r)),(401===r.status&&r.data&&"invalid_token"===r.data.error||r.headers("www-authenticate")&&0===r.headers("www-authenticate").indexOf("Bearer"))&&t.$emit("oauth:error",r),e.reject(r)}}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(){var t;this.configure=function(n){if(t)throw new Error("Already configured.");if(!(n instanceof Object))throw new TypeError("Invalid argument: `config` must be an `Object`.");return t=e.extend({},h,n),e.forEach(f,function(e){if(!t[e])throw new Error("Missing parameter: "+e+".")}),"/"===t.baseUrl.substr(-1)&&(t.baseUrl=t.baseUrl.slice(0,-1)),"/"!==t.grantPath[0]&&(t.grantPath="/"+t.grantPath),"/"!==t.revokePath[0]&&(t.revokePath="/"+t.revokePath),t},this.$get=function(r,o){var a=function(){function a(){if(i(this,a),!t)throw new Error("`OAuthProvider` must be configured first.")}return s(a,[{key:"isAuthenticated",value:function(){return!!o.getToken()}},{key:"getAccessToken",value:function(i,a){return i=e.extend({client_id:t.clientId,grant_type:"password"},i),null!==t.clientSecret&&(i.client_secret=t.clientSecret),i=n.stringify(i),a=e.extend({headers:{Authorization:void 0,"Content-Type":"application/x-www-form-urlencoded"}},a),r.post(""+t.baseUrl+t.grantPath,i,a).then(function(e){return o.setToken(e.data),e})}},{key:"getRefreshToken",value:function(i,a){return i=e.extend({client_id:t.clientId,grant_type:"refresh_token",refresh_token:o.getRefreshToken()},i),null!==t.clientSecret&&(i.client_secret=t.clientSecret),i=n.stringify(i),a=e.extend({headers:{Authorization:void 0,"Content-Type":"application/x-www-form-urlencoded"}},a),r.post(""+t.baseUrl+t.grantPath,i,a).then(function(e){return o.setToken(e.data),e})}},{key:"revokeToken",value:function(i,a){var u=o.getRefreshToken();return i=e.extend({client_id:t.clientId,token:u?u:o.getAccessToken(),token_type_hint:u?"refresh_token":"access_token"},i),null!==t.clientSecret&&(i.client_secret=t.clientSecret),i=n.stringify(i),a=e.extend({headers:{"Content-Type":"application/x-www-form-urlencoded"}},a),r.post(""+t.baseUrl+t.revokePath,i,a).then(function(e){return o.removeToken(),e})}}]),a}();return new a},this.$get.$inject=["$http","OAuthToken"]}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function u(){var t={name:"token",options:{secure:!0}};this.configure=function(n){if(!(n instanceof Object))throw new TypeError("Invalid argument: `config` must be an `Object`.");return e.extend(t,n),t},this.$get=function(e){var n=function(){function n(){i(this,n)}return s(n,[{key:"setToken",value:function(n){return e.putObject(t.name,n,t.options)}},{key:"getToken",value:function(){return e.getObject(t.name)}},{key:"getAccessToken",value:function(){return this.getToken()?this.getToken().access_token:void 0}},{key:"getAuthorizationHeader",value:function(){return this.getTokenType()&&this.getAccessToken()?this.getTokenType().charAt(0).toUpperCase()+this.getTokenType().substr(1)+" "+this.getAccessToken():void 0}},{key:"getRefreshToken",value:function(){return this.getToken()?this.getToken().refresh_token:void 0}},{key:"getTokenType",value:function(){return this.getToken()?this.getToken().token_type:void 0}},{key:"removeToken",value:function(){return e.remove(t.name,t.options)}}]),n}();return new n},this.$get.$inject=["$cookies"]}var c=e.module("angular-oauth2",[t]).config(r).factory("oauthInterceptor",o).provider("OAuth",a).provider("OAuthToken",u);r.$inject=["$httpProvider"],o.$inject=["$q","$rootScope","OAuthToken"];var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),h={baseUrl:null,clientId:null,clientSecret:null,grantPath:"/oauth2/token",revokePath:"/oauth2/revoke"},f=["baseUrl","clientId","grantPath","revokePath"],s=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();return c});
\ No newline at end of file
+!function(e,t){"function"==typeof define&&define.amd?define(["angular","angular-cookies","query-string"],t):"object"==typeof exports?module.exports=t(require("angular"),require("angular-cookies"),require("query-string")):e.angularOAuth2=t(e.angular,"ngCookies",e.queryString)}(this,function(e,t,n){function r(e,t,n){return{request:function(e){return e.headers=e.headers||{},!e.headers.hasOwnProperty("Authorization")&&n.getAuthorizationHeader()&&(e.headers.Authorization=n.getAuthorizationHeader()),e},responseError:function(r){return 400!==r.status||!r.data||"invalid_request"!==r.data.error&&"invalid_grant"!==r.data.error||(n.removeToken(),t.$emit("oauth:error",r)),(401===r.status&&r.data&&"invalid_token"===r.data.error||r.headers("www-authenticate")&&0===r.headers("www-authenticate").indexOf("Bearer"))&&t.$emit("oauth:error",r),e.reject(r)}}}function o(e){e.interceptors.push("oauthInterceptor")}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(){var t=this,r=function(t){if(!(t instanceof Object))throw new TypeError("Invalid argument: `config` must be an `Object`.");var n=e.extend({},f,t);return e.forEach(h,function(e){if(!n[e])throw new Error("Missing parameter: "+e+".")}),"/"===n.baseUrl.substr(-1)&&(n.baseUrl=n.baseUrl.slice(0,-1)),"/"!==n.grantPath[0]&&(n.grantPath="/"+n.grantPath),"/"!==n.revokePath[0]&&(n.revokePath="/"+n.revokePath),n};this.configure=function(e){t.defaultConfig=r(e)},this.$get=function(t,o){var a=function(){function a(e){i(this,a),this.config=e}return s(a,[{key:"configure",value:function(e){this.config=r(e)}},{key:"isAuthenticated",value:function(){return!!o.getToken()}},{key:"getAccessToken",value:function(r,i){return r=e.extend({client_id:this.config.clientId,grant_type:"password"},r),null!==this.config.clientSecret&&(r.client_secret=this.config.clientSecret),r=n.stringify(r),i=e.extend({headers:{Authorization:void 0,"Content-Type":"application/x-www-form-urlencoded"}},i),t.post(""+this.config.baseUrl+this.config.grantPath,r,i).then(function(e){return o.setToken(e.data),e})}},{key:"getRefreshToken",value:function(r,i){return r=e.extend({client_id:this.config.clientId,grant_type:"refresh_token",refresh_token:o.getRefreshToken()},r),null!==this.config.clientSecret&&(r.client_secret=this.config.clientSecret),r=n.stringify(r),i=e.extend({headers:{Authorization:void 0,"Content-Type":"application/x-www-form-urlencoded"}},i),t.post(""+this.config.baseUrl+this.config.grantPath,r,i).then(function(e){return o.setToken(e.data),e})}},{key:"revokeToken",value:function(r,i){var a=o.getRefreshToken();return r=e.extend({client_id:this.config.clientId,token:a?a:o.getAccessToken(),token_type_hint:a?"refresh_token":"access_token"},r),null!==this.config.clientSecret&&(r.client_secret=this.config.clientSecret),r=n.stringify(r),i=e.extend({headers:{"Content-Type":"application/x-www-form-urlencoded"}},i),t.post(""+this.config.baseUrl+this.config.revokePath,r,i).then(function(e){return o.removeToken(),e})}}]),a}();return new a(this.defaultConfig)},this.$get.$inject=["$http","OAuthToken"]}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function u(){var t={name:"token",options:{secure:!0}};this.configure=function(n){if(!(n instanceof Object))throw new TypeError("Invalid argument: `config` must be an `Object`.");return e.extend(t,n),t},this.$get=function(e){var n=function(){function n(){i(this,n)}return s(n,[{key:"setToken",value:function(n){return e.putObject(t.name,n,t.options)}},{key:"getToken",value:function(){return e.getObject(t.name)}},{key:"getAccessToken",value:function(){return this.getToken()?this.getToken().access_token:void 0}},{key:"getAuthorizationHeader",value:function(){if(this.getTokenType()&&this.getAccessToken())return this.getTokenType().charAt(0).toUpperCase()+this.getTokenType().substr(1)+" "+this.getAccessToken()}},{key:"getRefreshToken",value:function(){return this.getToken()?this.getToken().refresh_token:void 0}},{key:"getTokenType",value:function(){return this.getToken()?this.getToken().token_type:void 0}},{key:"removeToken",value:function(){return e.remove(t.name,t.options)}}]),n}();return new n},this.$get.$inject=["$cookies"]}var c=e.module("angular-oauth2",[t]).config(o).factory("oauthInterceptor",r).provider("OAuth",a).provider("OAuthToken",u);r.$inject=["$q","$rootScope","OAuthToken"],o.$inject=["$httpProvider"];var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),f={baseUrl:null,clientId:null,clientSecret:null,grantPath:"/oauth2/token",revokePath:"/oauth2/revoke"},h=["baseUrl","clientId","grantPath","revokePath"],s=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();return c});
\ No newline at end of file
diff --git a/package.json b/package.json
index 8fe7e25..98481f8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "angular-oauth2",
-  "version": "4.0.0",
+  "version": "4.1.0",
   "description": "Angular OAuth2",
   "main": "./dist/angular-oauth2.js",
   "repository": {

From 3ee5abbda423f15fc077d6a01d6a129b0f03ab07 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ant=C3=A9rio=20Vieira?= <anteriovieira@gmail.com>
Date: Tue, 8 Nov 2016 14:56:09 -0200
Subject: [PATCH 08/19] Updated readme

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index ef99b09..758e055 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ angular.module('myApp', ['angular-oauth2'])
   }]);
 ```
 
-You can also configure `OAuth` service in a `.run()`block, in case you retrieve the Oauth server configuration from a ajax request.
+You can also configure `OAuth` service in a `.run()` block, in case you retrieve the Oauth server configuration from a ajax request.
 
 ```js
 angular.module('myApp', ['angular-oauth2'])
@@ -109,7 +109,7 @@ OAuthProvider.configure({
 
 #### OAuth
 
-Update configuration defaults
+Update configuration defaults:
 
 ```js
 OAuth.configure({

From f1bd93c17bd556c61a12fc733edc6569bf2dda00 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Coelho?= <acoelho@uphold.com>
Date: Mon, 3 Apr 2017 09:38:54 +0100
Subject: [PATCH 09/19] =?UTF-8?q?Fix=20`access=5Ftoken`=C2=A0of=C2=A0undef?=
 =?UTF-8?q?ined?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/providers/oauth-token-provider.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/providers/oauth-token-provider.js b/src/providers/oauth-token-provider.js
index 060f1dd..7688dfb 100644
--- a/src/providers/oauth-token-provider.js
+++ b/src/providers/oauth-token-provider.js
@@ -63,7 +63,9 @@ function OAuthTokenProvider() {
        */
 
       getAccessToken() {
-        return this.getToken() ? this.getToken().access_token : undefined;
+        const { access_token } = this.getToken() || {};
+
+        return access_token;
       }
 
       /**

From bb6c55cb334083d182b158b9dc22b733c33d8f81 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Coelho?= <acoelho@uphold.com>
Date: Mon, 3 Apr 2017 09:40:18 +0100
Subject: [PATCH 10/19] =?UTF-8?q?Fix=20`refresh=5Ftoken`=C2=A0of=C2=A0unde?=
 =?UTF-8?q?fined?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/providers/oauth-token-provider.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/providers/oauth-token-provider.js b/src/providers/oauth-token-provider.js
index 7688dfb..771b890 100644
--- a/src/providers/oauth-token-provider.js
+++ b/src/providers/oauth-token-provider.js
@@ -85,7 +85,9 @@ function OAuthTokenProvider() {
        */
 
       getRefreshToken() {
-        return this.getToken() ? this.getToken().refresh_token : undefined;
+        const { refresh_token } = this.getToken() || {};
+
+        return refresh_token;
       }
 
       /**

From a7ea4a4e83a33172394b10739161b8d6cc0bc0c7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Coelho?= <acoelho@uphold.com>
Date: Mon, 3 Apr 2017 09:42:24 +0100
Subject: [PATCH 11/19] =?UTF-8?q?Fix=20`token=5Ftype`=C2=A0of=C2=A0undefin?=
 =?UTF-8?q?ed?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/providers/oauth-token-provider.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/providers/oauth-token-provider.js b/src/providers/oauth-token-provider.js
index 771b890..5e6095f 100644
--- a/src/providers/oauth-token-provider.js
+++ b/src/providers/oauth-token-provider.js
@@ -95,7 +95,9 @@ function OAuthTokenProvider() {
        */
 
       getTokenType() {
-        return this.getToken() ? this.getToken().token_type : undefined;
+        const { token_type } = this.getToken() || {};
+
+        return token_type;
       }
 
       /**

From e35acfe3f85a6a3bfa40fa04cfede2c22c7fe60a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Coelho?= <acoelho@uphold.com>
Date: Mon, 3 Apr 2017 09:51:39 +0100
Subject: [PATCH 12/19] =?UTF-8?q?Fix=20`charAt`=C2=A0and=20`substr`=C2=A0o?=
 =?UTF-8?q?f=C2=A0undefined?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/providers/oauth-token-provider.js | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/src/providers/oauth-token-provider.js b/src/providers/oauth-token-provider.js
index 5e6095f..13dca0e 100644
--- a/src/providers/oauth-token-provider.js
+++ b/src/providers/oauth-token-provider.js
@@ -73,11 +73,14 @@ function OAuthTokenProvider() {
        */
 
       getAuthorizationHeader() {
-        if (!(this.getTokenType() && this.getAccessToken())) {
+        const tokenType = this.getTokenType();
+        const accessToken = this.getAccessToken();
+
+        if (!tokenType || !accessToken) {
           return;
         }
 
-        return `${this.getTokenType().charAt(0).toUpperCase() + this.getTokenType().substr(1)} ${this.getAccessToken()}`;
+        return `${tokenType.charAt(0).toUpperCase() + tokenType.substr(1)} ${accessToken}`;
       }
 
       /**

From 726252abb0db7ded425e8bda60925ff8b53bb24a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Coelho?= <acoelho@uphold.com>
Date: Mon, 3 Apr 2017 10:19:16 +0100
Subject: [PATCH 13/19] Bump version of angular packages

---
 package.json | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/package.json b/package.json
index 98481f8..4091d7d 100644
--- a/package.json
+++ b/package.json
@@ -22,12 +22,12 @@
   },
   "homepage": "https://github.com/seegno/angular-oauth2",
   "dependencies": {
-    "angular": "^1.4.0",
-    "angular-cookies": "^1.4.0",
+    "angular": "1.5.9",
+    "angular-cookies": "1.5.9",
     "query-string": "^1.0.0"
   },
   "devDependencies": {
-    "angular-mocks": "^1.3.9",
+    "angular-mocks": "1.5.9",
     "github-changes": "^1.0.0",
     "gulp": "^3.8.10",
     "gulp-babel": "^5.3.0",

From fbb34b38d1f7b8f1b9360c0908ffd7027d980a35 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Coelho?= <acoelho@uphold.com>
Date: Mon, 3 Apr 2017 10:33:50 +0100
Subject: [PATCH 14/19] Fix unsupported `headers` and `status` of undefined

---
 src/interceptors/oauth-interceptor.js | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/interceptors/oauth-interceptor.js b/src/interceptors/oauth-interceptor.js
index bbdcadb..b94be80 100644
--- a/src/interceptors/oauth-interceptor.js
+++ b/src/interceptors/oauth-interceptor.js
@@ -16,6 +16,10 @@ function oauthInterceptor($q, $rootScope, OAuthToken) {
       return config;
     },
     responseError: function(rejection) {
+      if (!rejection) {
+        return $q.reject(rejection);
+      }
+
       // Catch `invalid_request` and `invalid_grant` errors and ensure that the `token` is removed.
       if (400 === rejection.status && rejection.data &&
         ('invalid_request' === rejection.data.error || 'invalid_grant' === rejection.data.error)
@@ -29,7 +33,7 @@ function oauthInterceptor($q, $rootScope, OAuthToken) {
       // The token isn't removed here so it can be refreshed when the `invalid_token` error occurs.
       if (401 === rejection.status &&
         (rejection.data && 'invalid_token' === rejection.data.error) ||
-        (rejection.headers('www-authenticate') && 0 === rejection.headers('www-authenticate').indexOf('Bearer'))
+        (rejection.headers && rejection.headers('www-authenticate') && 0 === rejection.headers('www-authenticate').indexOf('Bearer'))
       ) {
         $rootScope.$emit('oauth:error', rejection);
       }

From e90bd56a8362be46a8d3ecc9320bd03176ce1e75 Mon Sep 17 00:00:00 2001
From: Rui Silva <penso@uphold.com>
Date: Mon, 3 Apr 2017 12:51:03 +0100
Subject: [PATCH 15/19] Release 4.1.1

---
 CHANGELOG.md               |  7 ++++++-
 bower.json                 |  6 +++---
 dist/angular-oauth2.js     | 33 ++++++++++++++++++++++-----------
 dist/angular-oauth2.min.js |  2 +-
 package.json               |  4 ++--
 5 files changed, 34 insertions(+), 18 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d6fa99..002ccf8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,12 @@
 ## Changelog
 
+### 4.1.1 / 2017-04-03
+- [#118](https://github.com/oauthjs/angular-oauth2/pull/118) Validate object property access (@hitmanmcc)
+- [#107](https://github.com/oauthjs/angular-oauth2/pull/107) Updated readme (@anteriovieira)
+
 ### 4.1.0 / 2016-11-03
 - [#87](https://github.com/oauthjs/angular-oauth2/pull/87) allow overriding oauth base config using options (@lionelB)
+- [#96](https://github.com/oauthjs/angular-oauth2/pull/96) Specify the type of grant used (#96) (@Timokasse)
 
 ### 4.0.0 / 2016-02-12
 - [#80](https://github.com/oauthjs/angular-oauth2/pull/80) Reintroduce authorization header to be overridden (@ruipenso)
@@ -37,7 +42,7 @@
 ### 2.0.0 / 2015-02-04
 - [#11](https://github.com/oauthjs/angular-oauth2/pull/11) Add options to `ipCookie.remove()` on OAuthToken (@ruipenso)
 - [#10](https://github.com/oauthjs/angular-oauth2/pull/10) Update `oauthInterceptor` responseError handling (@ruipenso)
-- [#7](https://github.com/oauthjs/angular-oauth2/pull/7) Fix readme typo of download url (@brunnolou)
+- [#7](https://github.com/oauthjs/angular-oauth2/pull/7) Fix readme typo of download url (@seegno)
 - [#6](https://github.com/oauthjs/angular-oauth2/pull/6) README.md: typo fix (@AdirAmsalem)
 
 ### 1.0.2 / 2015-01-19
diff --git a/bower.json b/bower.json
index c351c6e..137280d 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
 {
   "name": "angular-oauth2",
-  "version": "4.0.0",
+  "version": "4.1.1",
   "description": "AngularJS OAuth2",
   "main": "./dist/angular-oauth2.js",
   "authors": [
@@ -23,8 +23,8 @@
     "test"
   ],
   "dependencies": {
-    "angular": "^1.4.0",
-    "angular-cookies": "^1.4.0",
+    "angular": "1.5.9",
+    "angular-cookies": "1.5.9",
     "query-string": "^1.0.0"
   }
 }
diff --git a/dist/angular-oauth2.js b/dist/angular-oauth2.js
index 91122c9..ab9f75a 100644
--- a/dist/angular-oauth2.js
+++ b/dist/angular-oauth2.js
@@ -1,6 +1,6 @@
 /**
  * angular-oauth2 - Angular OAuth2
- * @version v4.1.0
+ * @version v4.1.1
  * @link https://github.com/seegno/angular-oauth2
  * @license MIT
  */
@@ -14,6 +14,10 @@
     }
 })(this, function(angular, ngCookies, queryString) {
     var ngModule = angular.module("angular-oauth2", [ ngCookies ]).config(oauthConfig).factory("oauthInterceptor", oauthInterceptor).provider("OAuth", OAuthProvider).provider("OAuthToken", OAuthTokenProvider);
+    function oauthConfig($httpProvider) {
+        $httpProvider.interceptors.push("oauthInterceptor");
+    }
+    oauthConfig.$inject = [ "$httpProvider" ];
     function oauthInterceptor($q, $rootScope, OAuthToken) {
         return {
             request: function request(config) {
@@ -24,11 +28,14 @@
                 return config;
             },
             responseError: function responseError(rejection) {
+                if (!rejection) {
+                    return $q.reject(rejection);
+                }
                 if (400 === rejection.status && rejection.data && ("invalid_request" === rejection.data.error || "invalid_grant" === rejection.data.error)) {
                     OAuthToken.removeToken();
                     $rootScope.$emit("oauth:error", rejection);
                 }
-                if (401 === rejection.status && rejection.data && "invalid_token" === rejection.data.error || rejection.headers("www-authenticate") && 0 === rejection.headers("www-authenticate").indexOf("Bearer")) {
+                if (401 === rejection.status && rejection.data && "invalid_token" === rejection.data.error || rejection.headers && rejection.headers("www-authenticate") && 0 === rejection.headers("www-authenticate").indexOf("Bearer")) {
                     $rootScope.$emit("oauth:error", rejection);
                 }
                 return $q.reject(rejection);
@@ -36,10 +43,6 @@
         };
     }
     oauthInterceptor.$inject = [ "$q", "$rootScope", "OAuthToken" ];
-    function oauthConfig($httpProvider) {
-        $httpProvider.interceptors.push("oauthInterceptor");
-    }
-    oauthConfig.$inject = [ "$httpProvider" ];
     var _createClass = function() {
         function defineProperties(target, props) {
             for (var i = 0; i < props.length; i++) {
@@ -239,25 +242,33 @@
                 }, {
                     key: "getAccessToken",
                     value: function getAccessToken() {
-                        return this.getToken() ? this.getToken().access_token : undefined;
+                        var _ref = this.getToken() || {};
+                        var access_token = _ref.access_token;
+                        return access_token;
                     }
                 }, {
                     key: "getAuthorizationHeader",
                     value: function getAuthorizationHeader() {
-                        if (!(this.getTokenType() && this.getAccessToken())) {
+                        var tokenType = this.getTokenType();
+                        var accessToken = this.getAccessToken();
+                        if (!tokenType || !accessToken) {
                             return;
                         }
-                        return this.getTokenType().charAt(0).toUpperCase() + this.getTokenType().substr(1) + " " + this.getAccessToken();
+                        return tokenType.charAt(0).toUpperCase() + tokenType.substr(1) + " " + accessToken;
                     }
                 }, {
                     key: "getRefreshToken",
                     value: function getRefreshToken() {
-                        return this.getToken() ? this.getToken().refresh_token : undefined;
+                        var _ref2 = this.getToken() || {};
+                        var refresh_token = _ref2.refresh_token;
+                        return refresh_token;
                     }
                 }, {
                     key: "getTokenType",
                     value: function getTokenType() {
-                        return this.getToken() ? this.getToken().token_type : undefined;
+                        var _ref3 = this.getToken() || {};
+                        var token_type = _ref3.token_type;
+                        return token_type;
                     }
                 }, {
                     key: "removeToken",
diff --git a/dist/angular-oauth2.min.js b/dist/angular-oauth2.min.js
index 98e09a3..b5ecefe 100644
--- a/dist/angular-oauth2.min.js
+++ b/dist/angular-oauth2.min.js
@@ -1 +1 @@
-!function(e,t){"function"==typeof define&&define.amd?define(["angular","angular-cookies","query-string"],t):"object"==typeof exports?module.exports=t(require("angular"),require("angular-cookies"),require("query-string")):e.angularOAuth2=t(e.angular,"ngCookies",e.queryString)}(this,function(e,t,n){function r(e,t,n){return{request:function(e){return e.headers=e.headers||{},!e.headers.hasOwnProperty("Authorization")&&n.getAuthorizationHeader()&&(e.headers.Authorization=n.getAuthorizationHeader()),e},responseError:function(r){return 400!==r.status||!r.data||"invalid_request"!==r.data.error&&"invalid_grant"!==r.data.error||(n.removeToken(),t.$emit("oauth:error",r)),(401===r.status&&r.data&&"invalid_token"===r.data.error||r.headers("www-authenticate")&&0===r.headers("www-authenticate").indexOf("Bearer"))&&t.$emit("oauth:error",r),e.reject(r)}}}function o(e){e.interceptors.push("oauthInterceptor")}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(){var t=this,r=function(t){if(!(t instanceof Object))throw new TypeError("Invalid argument: `config` must be an `Object`.");var n=e.extend({},f,t);return e.forEach(h,function(e){if(!n[e])throw new Error("Missing parameter: "+e+".")}),"/"===n.baseUrl.substr(-1)&&(n.baseUrl=n.baseUrl.slice(0,-1)),"/"!==n.grantPath[0]&&(n.grantPath="/"+n.grantPath),"/"!==n.revokePath[0]&&(n.revokePath="/"+n.revokePath),n};this.configure=function(e){t.defaultConfig=r(e)},this.$get=function(t,o){var a=function(){function a(e){i(this,a),this.config=e}return s(a,[{key:"configure",value:function(e){this.config=r(e)}},{key:"isAuthenticated",value:function(){return!!o.getToken()}},{key:"getAccessToken",value:function(r,i){return r=e.extend({client_id:this.config.clientId,grant_type:"password"},r),null!==this.config.clientSecret&&(r.client_secret=this.config.clientSecret),r=n.stringify(r),i=e.extend({headers:{Authorization:void 0,"Content-Type":"application/x-www-form-urlencoded"}},i),t.post(""+this.config.baseUrl+this.config.grantPath,r,i).then(function(e){return o.setToken(e.data),e})}},{key:"getRefreshToken",value:function(r,i){return r=e.extend({client_id:this.config.clientId,grant_type:"refresh_token",refresh_token:o.getRefreshToken()},r),null!==this.config.clientSecret&&(r.client_secret=this.config.clientSecret),r=n.stringify(r),i=e.extend({headers:{Authorization:void 0,"Content-Type":"application/x-www-form-urlencoded"}},i),t.post(""+this.config.baseUrl+this.config.grantPath,r,i).then(function(e){return o.setToken(e.data),e})}},{key:"revokeToken",value:function(r,i){var a=o.getRefreshToken();return r=e.extend({client_id:this.config.clientId,token:a?a:o.getAccessToken(),token_type_hint:a?"refresh_token":"access_token"},r),null!==this.config.clientSecret&&(r.client_secret=this.config.clientSecret),r=n.stringify(r),i=e.extend({headers:{"Content-Type":"application/x-www-form-urlencoded"}},i),t.post(""+this.config.baseUrl+this.config.revokePath,r,i).then(function(e){return o.removeToken(),e})}}]),a}();return new a(this.defaultConfig)},this.$get.$inject=["$http","OAuthToken"]}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function u(){var t={name:"token",options:{secure:!0}};this.configure=function(n){if(!(n instanceof Object))throw new TypeError("Invalid argument: `config` must be an `Object`.");return e.extend(t,n),t},this.$get=function(e){var n=function(){function n(){i(this,n)}return s(n,[{key:"setToken",value:function(n){return e.putObject(t.name,n,t.options)}},{key:"getToken",value:function(){return e.getObject(t.name)}},{key:"getAccessToken",value:function(){return this.getToken()?this.getToken().access_token:void 0}},{key:"getAuthorizationHeader",value:function(){if(this.getTokenType()&&this.getAccessToken())return this.getTokenType().charAt(0).toUpperCase()+this.getTokenType().substr(1)+" "+this.getAccessToken()}},{key:"getRefreshToken",value:function(){return this.getToken()?this.getToken().refresh_token:void 0}},{key:"getTokenType",value:function(){return this.getToken()?this.getToken().token_type:void 0}},{key:"removeToken",value:function(){return e.remove(t.name,t.options)}}]),n}();return new n},this.$get.$inject=["$cookies"]}var c=e.module("angular-oauth2",[t]).config(o).factory("oauthInterceptor",r).provider("OAuth",a).provider("OAuthToken",u);r.$inject=["$q","$rootScope","OAuthToken"],o.$inject=["$httpProvider"];var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),f={baseUrl:null,clientId:null,clientSecret:null,grantPath:"/oauth2/token",revokePath:"/oauth2/revoke"},h=["baseUrl","clientId","grantPath","revokePath"],s=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();return c});
\ No newline at end of file
+!function(e,t){"function"==typeof define&&define.amd?define(["angular","angular-cookies","query-string"],t):"object"==typeof exports?module.exports=t(require("angular"),require("angular-cookies"),require("query-string")):e.angularOAuth2=t(e.angular,"ngCookies",e.queryString)}(this,function(e,t,n){function r(e){e.interceptors.push("oauthInterceptor")}function o(e,t,n){return{request:function(e){return e.headers=e.headers||{},!e.headers.hasOwnProperty("Authorization")&&n.getAuthorizationHeader()&&(e.headers.Authorization=n.getAuthorizationHeader()),e},responseError:function(r){return r?(400!==r.status||!r.data||"invalid_request"!==r.data.error&&"invalid_grant"!==r.data.error||(n.removeToken(),t.$emit("oauth:error",r)),(401===r.status&&r.data&&"invalid_token"===r.data.error||r.headers&&r.headers("www-authenticate")&&0===r.headers("www-authenticate").indexOf("Bearer"))&&t.$emit("oauth:error",r),e.reject(r)):e.reject(r)}}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(){var t=this,r=function(t){if(!(t instanceof Object))throw new TypeError("Invalid argument: `config` must be an `Object`.");var n=e.extend({},f,t);return e.forEach(h,function(e){if(!n[e])throw new Error("Missing parameter: "+e+".")}),"/"===n.baseUrl.substr(-1)&&(n.baseUrl=n.baseUrl.slice(0,-1)),"/"!==n.grantPath[0]&&(n.grantPath="/"+n.grantPath),"/"!==n.revokePath[0]&&(n.revokePath="/"+n.revokePath),n};this.configure=function(e){t.defaultConfig=r(e)},this.$get=function(t,o){var a=function(){function a(e){i(this,a),this.config=e}return s(a,[{key:"configure",value:function(e){this.config=r(e)}},{key:"isAuthenticated",value:function(){return!!o.getToken()}},{key:"getAccessToken",value:function(r,i){return r=e.extend({client_id:this.config.clientId,grant_type:"password"},r),null!==this.config.clientSecret&&(r.client_secret=this.config.clientSecret),r=n.stringify(r),i=e.extend({headers:{Authorization:void 0,"Content-Type":"application/x-www-form-urlencoded"}},i),t.post(""+this.config.baseUrl+this.config.grantPath,r,i).then(function(e){return o.setToken(e.data),e})}},{key:"getRefreshToken",value:function(r,i){return r=e.extend({client_id:this.config.clientId,grant_type:"refresh_token",refresh_token:o.getRefreshToken()},r),null!==this.config.clientSecret&&(r.client_secret=this.config.clientSecret),r=n.stringify(r),i=e.extend({headers:{Authorization:void 0,"Content-Type":"application/x-www-form-urlencoded"}},i),t.post(""+this.config.baseUrl+this.config.grantPath,r,i).then(function(e){return o.setToken(e.data),e})}},{key:"revokeToken",value:function(r,i){var a=o.getRefreshToken();return r=e.extend({client_id:this.config.clientId,token:a?a:o.getAccessToken(),token_type_hint:a?"refresh_token":"access_token"},r),null!==this.config.clientSecret&&(r.client_secret=this.config.clientSecret),r=n.stringify(r),i=e.extend({headers:{"Content-Type":"application/x-www-form-urlencoded"}},i),t.post(""+this.config.baseUrl+this.config.revokePath,r,i).then(function(e){return o.removeToken(),e})}}]),a}();return new a(this.defaultConfig)},this.$get.$inject=["$http","OAuthToken"]}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function u(){var t={name:"token",options:{secure:!0}};this.configure=function(n){if(!(n instanceof Object))throw new TypeError("Invalid argument: `config` must be an `Object`.");return e.extend(t,n),t},this.$get=function(e){var n=function(){function n(){i(this,n)}return s(n,[{key:"setToken",value:function(n){return e.putObject(t.name,n,t.options)}},{key:"getToken",value:function(){return e.getObject(t.name)}},{key:"getAccessToken",value:function(){var e=this.getToken()||{},t=e.access_token;return t}},{key:"getAuthorizationHeader",value:function(){var e=this.getTokenType(),t=this.getAccessToken();if(e&&t)return e.charAt(0).toUpperCase()+e.substr(1)+" "+t}},{key:"getRefreshToken",value:function(){var e=this.getToken()||{},t=e.refresh_token;return t}},{key:"getTokenType",value:function(){var e=this.getToken()||{},t=e.token_type;return t}},{key:"removeToken",value:function(){return e.remove(t.name,t.options)}}]),n}();return new n},this.$get.$inject=["$cookies"]}var c=e.module("angular-oauth2",[t]).config(r).factory("oauthInterceptor",o).provider("OAuth",a).provider("OAuthToken",u);r.$inject=["$httpProvider"],o.$inject=["$q","$rootScope","OAuthToken"];var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),f={baseUrl:null,clientId:null,clientSecret:null,grantPath:"/oauth2/token",revokePath:"/oauth2/revoke"},h=["baseUrl","clientId","grantPath","revokePath"],s=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();return c});
\ No newline at end of file
diff --git a/package.json b/package.json
index 4091d7d..9f833ab 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "angular-oauth2",
-  "version": "4.1.0",
+  "version": "4.1.1",
   "description": "Angular OAuth2",
   "main": "./dist/angular-oauth2.js",
   "repository": {
@@ -53,7 +53,7 @@
     "yargs": "^3.6.0"
   },
   "scripts": {
-    "changelog": "./node_modules/.bin/github-changes -o seegno -r angular-oauth2 -a --only-pulls --use-commit-body --title 'Changelog' --date-format '/ YYYY-MM-DD'",
+    "changelog": "./node_modules/.bin/github-changes -o oauthjs -r angular-oauth2 -a --only-pulls --use-commit-body --title 'Changelog' --date-format '/ YYYY-MM-DD'",
     "test": "./node_modules/.bin/gulp test --browsers Firefox"
   }
 }

From 7758be4a914d252a26b606be0e03642e5f77fa9b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marcin=20D=C4=99bowski?=
 <marcin.debowski@profil-software.com>
Date: Thu, 4 Jan 2018 11:24:36 +0100
Subject: [PATCH 16/19] Make angular dev dependecy.

---
 package.json | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/package.json b/package.json
index 9f833ab..b5d8b06 100644
--- a/package.json
+++ b/package.json
@@ -21,12 +21,11 @@
     "url": "https://github.com/seegno/angular-oauth2/issues"
   },
   "homepage": "https://github.com/seegno/angular-oauth2",
-  "dependencies": {
-    "angular": "1.5.9",
-    "angular-cookies": "1.5.9",
-    "query-string": "^1.0.0"
-  },
+  "dependencies": {},
   "devDependencies": {
+    "angular": "^1.5.9",
+    "angular-cookies": "^1.5.9",
+    "query-string": "^1.0.0",
     "angular-mocks": "1.5.9",
     "github-changes": "^1.0.0",
     "gulp": "^3.8.10",

From 83b8de918ea6a1693b872fb58f39536ea6995960 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marcin=20D=C4=99bowski?=
 <marcin.debowski@profil-software.com>
Date: Thu, 4 Jan 2018 12:11:40 +0100
Subject: [PATCH 17/19] Update browserify.

---
 package.json | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/package.json b/package.json
index b5d8b06..b9dc462 100644
--- a/package.json
+++ b/package.json
@@ -25,8 +25,8 @@
   "devDependencies": {
     "angular": "^1.5.9",
     "angular-cookies": "^1.5.9",
-    "query-string": "^1.0.0",
     "angular-mocks": "1.5.9",
+    "browserify": "^14.5.0",
     "github-changes": "^1.0.0",
     "gulp": "^3.8.10",
     "gulp-babel": "^5.3.0",
@@ -38,7 +38,7 @@
     "gulp-wrap-umd": "^0.2.1",
     "jshint-stylish": "^1.0.0",
     "karma": "^0.13.0",
-    "karma-browserify": "^2.0.0",
+    "karma-browserify": "^5.1.2",
     "karma-chrome-launcher": "^0.1.7",
     "karma-firefox-launcher": "^0.1.4",
     "karma-mocha": "^0.1.10",
@@ -47,8 +47,10 @@
     "karma-sinon": "^1.0.4",
     "lodash": "^4.0.0",
     "mocha": "^2.4.5",
+    "query-string": "^1.0.0",
     "should": "^4.6.0",
     "sinon": "^1.17.3",
+    "watchify": "^3.9.0",
     "yargs": "^3.6.0"
   },
   "scripts": {

From eda48bda4f1ff74551be71bf88350dad22504e14 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marcin=20D=C4=99bowski?=
 <marcin.debowski@profil-software.com>
Date: Thu, 4 Jan 2018 12:15:04 +0100
Subject: [PATCH 18/19] Catch http errors to prevent Chrome unhandled
 rejections from failing tests.

---
 .../interceptors/oauth-interceptor.spec.js     | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/test/unit/interceptors/oauth-interceptor.spec.js b/test/unit/interceptors/oauth-interceptor.spec.js
index 4c5e3c7..8503200 100644
--- a/test/unit/interceptors/oauth-interceptor.spec.js
+++ b/test/unit/interceptors/oauth-interceptor.spec.js
@@ -64,7 +64,8 @@ describe('oauthInterceptor', function() {
 
     $httpBackend.expectGET('https://website.com').respond(400, { error: 'invalid_request' });
 
-    $http.get('https://website.com');
+    $http.get('https://website.com')
+      .catch(() => { });
 
     $httpBackend.flush();
 
@@ -77,7 +78,8 @@ describe('oauthInterceptor', function() {
 
     $httpBackend.expectGET('https://website.com').respond(400, { error: 'invalid_request' });
 
-    $http.get('https://website.com');
+    $http.get('https://website.com')
+      .catch(() => { });
 
     $httpBackend.flush();
 
@@ -93,7 +95,8 @@ describe('oauthInterceptor', function() {
 
     $httpBackend.expectGET('https://website.com').respond(400, { error: 'invalid_grant' });
 
-    $http.get('https://website.com');
+    $http.get('https://website.com')
+      .catch(() => { });
 
     $httpBackend.flush();
 
@@ -106,7 +109,8 @@ describe('oauthInterceptor', function() {
 
     $httpBackend.expectGET('https://website.com').respond(400, { error: 'invalid_grant' });
 
-    $http.get('https://website.com');
+    $http.get('https://website.com')
+      .catch(() => { });
 
     $httpBackend.flush();
 
@@ -122,7 +126,8 @@ describe('oauthInterceptor', function() {
 
     $httpBackend.expectGET('https://website.com').respond(401, { error: 'invalid_token' });
 
-    $http.get('https://website.com');
+    $http.get('https://website.com')
+      .catch(() => { });
 
     $httpBackend.flush();
 
@@ -138,7 +143,8 @@ describe('oauthInterceptor', function() {
 
     $httpBackend.expectGET('https://website.com').respond(401, null, { 'www-authenticate': 'Bearer realm="example"' });
 
-    $http.get('https://website.com');
+    $http.get('https://website.com')
+      .catch(() => { });
 
     $httpBackend.flush();
 

From 35c0c03a98153a4c2056b4b9411872cdce90fa0e Mon Sep 17 00:00:00 2001
From: Pedro Gomes <gomes@uphold.com>
Date: Wed, 24 Jan 2018 17:29:46 +0000
Subject: [PATCH 19/19] Release 4.2.0

---
 CHANGELOG.md               | 11 ++++++---
 bower.json                 |  2 +-
 dist/angular-oauth2.js     | 50 +++++++++++++++++++-------------------
 dist/angular-oauth2.min.js |  2 +-
 package.json               |  2 +-
 5 files changed, 35 insertions(+), 32 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 002ccf8..50a318e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
 ## Changelog
 
+### 4.2.0 / 2018-01-24
+- [#127](https://github.com/oauthjs/angular-oauth2/pull/127) Prevent loading angular multiple times with webpack (@erhardos)
+
 ### 4.1.1 / 2017-04-03
 - [#118](https://github.com/oauthjs/angular-oauth2/pull/118) Validate object property access (@hitmanmcc)
 - [#107](https://github.com/oauthjs/angular-oauth2/pull/107) Updated readme (@anteriovieira)
@@ -22,13 +25,13 @@
 - [#76](https://github.com/oauthjs/angular-oauth2/pull/76) Update `package.json` (@ruipenso)
 
 ### 3.0.1 / 2015-06-01
-- [#27](https://github.com/oauthjs/angular-oauth2/pull/27) Add travis-ci configuration (@joaogranado)
+- [#27](https://github.com/oauthjs/angular-oauth2/pull/27) Add travis-ci configuration (@seegno)
 
 ### 3.0.0 / 2015-06-01
 - [#49](https://github.com/oauthjs/angular-oauth2/pull/49) Add changelog task (@ruipenso)
 - [#48](https://github.com/oauthjs/angular-oauth2/pull/48) Update readme (@ruipenso)
-- [#25](https://github.com/oauthjs/angular-oauth2/pull/25) Replace ipCookie with ngCookies (@joaogranado)
-- [#28](https://github.com/oauthjs/angular-oauth2/pull/28) Add methods to get/set `token` property (@joaogranado)
+- [#25](https://github.com/oauthjs/angular-oauth2/pull/25) Replace ipCookie with ngCookies (@seegno)
+- [#28](https://github.com/oauthjs/angular-oauth2/pull/28) Add methods to get/set `token` property (@seegno)
 - [#47](https://github.com/oauthjs/angular-oauth2/pull/47) Add unauthorized error interception (@ruipenso)
 
 ### 2.1.1 / 2015-05-28
@@ -46,7 +49,7 @@
 - [#6](https://github.com/oauthjs/angular-oauth2/pull/6) README.md: typo fix (@AdirAmsalem)
 
 ### 1.0.2 / 2015-01-19
-- [#4](https://github.com/oauthjs/angular-oauth2/pull/4) Add support for higher dependencies versions (@joaogranado)
+- [#4](https://github.com/oauthjs/angular-oauth2/pull/4) Add support for higher dependencies versions (@seegno)
 
 ### 1.0.1 / 2015-01-19
 - [#3](https://github.com/oauthjs/angular-oauth2/pull/3) Fix bower ignore list (@ruipenso)
diff --git a/bower.json b/bower.json
index 137280d..81125c0 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
 {
   "name": "angular-oauth2",
-  "version": "4.1.1",
+  "version": "4.2.0",
   "description": "AngularJS OAuth2",
   "main": "./dist/angular-oauth2.js",
   "authors": [
diff --git a/dist/angular-oauth2.js b/dist/angular-oauth2.js
index ab9f75a..3706f93 100644
--- a/dist/angular-oauth2.js
+++ b/dist/angular-oauth2.js
@@ -18,31 +18,6 @@
         $httpProvider.interceptors.push("oauthInterceptor");
     }
     oauthConfig.$inject = [ "$httpProvider" ];
-    function oauthInterceptor($q, $rootScope, OAuthToken) {
-        return {
-            request: function request(config) {
-                config.headers = config.headers || {};
-                if (!config.headers.hasOwnProperty("Authorization") && OAuthToken.getAuthorizationHeader()) {
-                    config.headers.Authorization = OAuthToken.getAuthorizationHeader();
-                }
-                return config;
-            },
-            responseError: function responseError(rejection) {
-                if (!rejection) {
-                    return $q.reject(rejection);
-                }
-                if (400 === rejection.status && rejection.data && ("invalid_request" === rejection.data.error || "invalid_grant" === rejection.data.error)) {
-                    OAuthToken.removeToken();
-                    $rootScope.$emit("oauth:error", rejection);
-                }
-                if (401 === rejection.status && rejection.data && "invalid_token" === rejection.data.error || rejection.headers && rejection.headers("www-authenticate") && 0 === rejection.headers("www-authenticate").indexOf("Bearer")) {
-                    $rootScope.$emit("oauth:error", rejection);
-                }
-                return $q.reject(rejection);
-            }
-        };
-    }
-    oauthInterceptor.$inject = [ "$q", "$rootScope", "OAuthToken" ];
     var _createClass = function() {
         function defineProperties(target, props) {
             for (var i = 0; i < props.length; i++) {
@@ -282,5 +257,30 @@
         };
         this.$get.$inject = [ "$cookies" ];
     }
+    function oauthInterceptor($q, $rootScope, OAuthToken) {
+        return {
+            request: function request(config) {
+                config.headers = config.headers || {};
+                if (!config.headers.hasOwnProperty("Authorization") && OAuthToken.getAuthorizationHeader()) {
+                    config.headers.Authorization = OAuthToken.getAuthorizationHeader();
+                }
+                return config;
+            },
+            responseError: function responseError(rejection) {
+                if (!rejection) {
+                    return $q.reject(rejection);
+                }
+                if (400 === rejection.status && rejection.data && ("invalid_request" === rejection.data.error || "invalid_grant" === rejection.data.error)) {
+                    OAuthToken.removeToken();
+                    $rootScope.$emit("oauth:error", rejection);
+                }
+                if (401 === rejection.status && rejection.data && "invalid_token" === rejection.data.error || rejection.headers && rejection.headers("www-authenticate") && 0 === rejection.headers("www-authenticate").indexOf("Bearer")) {
+                    $rootScope.$emit("oauth:error", rejection);
+                }
+                return $q.reject(rejection);
+            }
+        };
+    }
+    oauthInterceptor.$inject = [ "$q", "$rootScope", "OAuthToken" ];
     return ngModule;
 });
\ No newline at end of file
diff --git a/dist/angular-oauth2.min.js b/dist/angular-oauth2.min.js
index b5ecefe..aed184d 100644
--- a/dist/angular-oauth2.min.js
+++ b/dist/angular-oauth2.min.js
@@ -1 +1 @@
-!function(e,t){"function"==typeof define&&define.amd?define(["angular","angular-cookies","query-string"],t):"object"==typeof exports?module.exports=t(require("angular"),require("angular-cookies"),require("query-string")):e.angularOAuth2=t(e.angular,"ngCookies",e.queryString)}(this,function(e,t,n){function r(e){e.interceptors.push("oauthInterceptor")}function o(e,t,n){return{request:function(e){return e.headers=e.headers||{},!e.headers.hasOwnProperty("Authorization")&&n.getAuthorizationHeader()&&(e.headers.Authorization=n.getAuthorizationHeader()),e},responseError:function(r){return r?(400!==r.status||!r.data||"invalid_request"!==r.data.error&&"invalid_grant"!==r.data.error||(n.removeToken(),t.$emit("oauth:error",r)),(401===r.status&&r.data&&"invalid_token"===r.data.error||r.headers&&r.headers("www-authenticate")&&0===r.headers("www-authenticate").indexOf("Bearer"))&&t.$emit("oauth:error",r),e.reject(r)):e.reject(r)}}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(){var t=this,r=function(t){if(!(t instanceof Object))throw new TypeError("Invalid argument: `config` must be an `Object`.");var n=e.extend({},f,t);return e.forEach(h,function(e){if(!n[e])throw new Error("Missing parameter: "+e+".")}),"/"===n.baseUrl.substr(-1)&&(n.baseUrl=n.baseUrl.slice(0,-1)),"/"!==n.grantPath[0]&&(n.grantPath="/"+n.grantPath),"/"!==n.revokePath[0]&&(n.revokePath="/"+n.revokePath),n};this.configure=function(e){t.defaultConfig=r(e)},this.$get=function(t,o){var a=function(){function a(e){i(this,a),this.config=e}return s(a,[{key:"configure",value:function(e){this.config=r(e)}},{key:"isAuthenticated",value:function(){return!!o.getToken()}},{key:"getAccessToken",value:function(r,i){return r=e.extend({client_id:this.config.clientId,grant_type:"password"},r),null!==this.config.clientSecret&&(r.client_secret=this.config.clientSecret),r=n.stringify(r),i=e.extend({headers:{Authorization:void 0,"Content-Type":"application/x-www-form-urlencoded"}},i),t.post(""+this.config.baseUrl+this.config.grantPath,r,i).then(function(e){return o.setToken(e.data),e})}},{key:"getRefreshToken",value:function(r,i){return r=e.extend({client_id:this.config.clientId,grant_type:"refresh_token",refresh_token:o.getRefreshToken()},r),null!==this.config.clientSecret&&(r.client_secret=this.config.clientSecret),r=n.stringify(r),i=e.extend({headers:{Authorization:void 0,"Content-Type":"application/x-www-form-urlencoded"}},i),t.post(""+this.config.baseUrl+this.config.grantPath,r,i).then(function(e){return o.setToken(e.data),e})}},{key:"revokeToken",value:function(r,i){var a=o.getRefreshToken();return r=e.extend({client_id:this.config.clientId,token:a?a:o.getAccessToken(),token_type_hint:a?"refresh_token":"access_token"},r),null!==this.config.clientSecret&&(r.client_secret=this.config.clientSecret),r=n.stringify(r),i=e.extend({headers:{"Content-Type":"application/x-www-form-urlencoded"}},i),t.post(""+this.config.baseUrl+this.config.revokePath,r,i).then(function(e){return o.removeToken(),e})}}]),a}();return new a(this.defaultConfig)},this.$get.$inject=["$http","OAuthToken"]}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function u(){var t={name:"token",options:{secure:!0}};this.configure=function(n){if(!(n instanceof Object))throw new TypeError("Invalid argument: `config` must be an `Object`.");return e.extend(t,n),t},this.$get=function(e){var n=function(){function n(){i(this,n)}return s(n,[{key:"setToken",value:function(n){return e.putObject(t.name,n,t.options)}},{key:"getToken",value:function(){return e.getObject(t.name)}},{key:"getAccessToken",value:function(){var e=this.getToken()||{},t=e.access_token;return t}},{key:"getAuthorizationHeader",value:function(){var e=this.getTokenType(),t=this.getAccessToken();if(e&&t)return e.charAt(0).toUpperCase()+e.substr(1)+" "+t}},{key:"getRefreshToken",value:function(){var e=this.getToken()||{},t=e.refresh_token;return t}},{key:"getTokenType",value:function(){var e=this.getToken()||{},t=e.token_type;return t}},{key:"removeToken",value:function(){return e.remove(t.name,t.options)}}]),n}();return new n},this.$get.$inject=["$cookies"]}var c=e.module("angular-oauth2",[t]).config(r).factory("oauthInterceptor",o).provider("OAuth",a).provider("OAuthToken",u);r.$inject=["$httpProvider"],o.$inject=["$q","$rootScope","OAuthToken"];var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),f={baseUrl:null,clientId:null,clientSecret:null,grantPath:"/oauth2/token",revokePath:"/oauth2/revoke"},h=["baseUrl","clientId","grantPath","revokePath"],s=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();return c});
\ No newline at end of file
+!function(e,t){"function"==typeof define&&define.amd?define(["angular","angular-cookies","query-string"],t):"object"==typeof exports?module.exports=t(require("angular"),require("angular-cookies"),require("query-string")):e.angularOAuth2=t(e.angular,"ngCookies",e.queryString)}(this,function(e,t,n){function r(e){e.interceptors.push("oauthInterceptor")}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(){var t=this,r=function(t){if(!(t instanceof Object))throw new TypeError("Invalid argument: `config` must be an `Object`.");var n=e.extend({},f,t);return e.forEach(h,function(e){if(!n[e])throw new Error("Missing parameter: "+e+".")}),"/"===n.baseUrl.substr(-1)&&(n.baseUrl=n.baseUrl.slice(0,-1)),"/"!==n.grantPath[0]&&(n.grantPath="/"+n.grantPath),"/"!==n.revokePath[0]&&(n.revokePath="/"+n.revokePath),n};this.configure=function(e){t.defaultConfig=r(e)},this.$get=function(t,i){var a=function(){function a(e){o(this,a),this.config=e}return s(a,[{key:"configure",value:function(e){this.config=r(e)}},{key:"isAuthenticated",value:function(){return!!i.getToken()}},{key:"getAccessToken",value:function(r,o){return r=e.extend({client_id:this.config.clientId,grant_type:"password"},r),null!==this.config.clientSecret&&(r.client_secret=this.config.clientSecret),r=n.stringify(r),o=e.extend({headers:{Authorization:void 0,"Content-Type":"application/x-www-form-urlencoded"}},o),t.post(""+this.config.baseUrl+this.config.grantPath,r,o).then(function(e){return i.setToken(e.data),e})}},{key:"getRefreshToken",value:function(r,o){return r=e.extend({client_id:this.config.clientId,grant_type:"refresh_token",refresh_token:i.getRefreshToken()},r),null!==this.config.clientSecret&&(r.client_secret=this.config.clientSecret),r=n.stringify(r),o=e.extend({headers:{Authorization:void 0,"Content-Type":"application/x-www-form-urlencoded"}},o),t.post(""+this.config.baseUrl+this.config.grantPath,r,o).then(function(e){return i.setToken(e.data),e})}},{key:"revokeToken",value:function(r,o){var a=i.getRefreshToken();return r=e.extend({client_id:this.config.clientId,token:a?a:i.getAccessToken(),token_type_hint:a?"refresh_token":"access_token"},r),null!==this.config.clientSecret&&(r.client_secret=this.config.clientSecret),r=n.stringify(r),o=e.extend({headers:{"Content-Type":"application/x-www-form-urlencoded"}},o),t.post(""+this.config.baseUrl+this.config.revokePath,r,o).then(function(e){return i.removeToken(),e})}}]),a}();return new a(this.defaultConfig)},this.$get.$inject=["$http","OAuthToken"]}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(){var t={name:"token",options:{secure:!0}};this.configure=function(n){if(!(n instanceof Object))throw new TypeError("Invalid argument: `config` must be an `Object`.");return e.extend(t,n),t},this.$get=function(e){var n=function(){function n(){o(this,n)}return s(n,[{key:"setToken",value:function(n){return e.putObject(t.name,n,t.options)}},{key:"getToken",value:function(){return e.getObject(t.name)}},{key:"getAccessToken",value:function(){var e=this.getToken()||{},t=e.access_token;return t}},{key:"getAuthorizationHeader",value:function(){var e=this.getTokenType(),t=this.getAccessToken();if(e&&t)return e.charAt(0).toUpperCase()+e.substr(1)+" "+t}},{key:"getRefreshToken",value:function(){var e=this.getToken()||{},t=e.refresh_token;return t}},{key:"getTokenType",value:function(){var e=this.getToken()||{},t=e.token_type;return t}},{key:"removeToken",value:function(){return e.remove(t.name,t.options)}}]),n}();return new n},this.$get.$inject=["$cookies"]}function u(e,t,n){return{request:function(e){return e.headers=e.headers||{},!e.headers.hasOwnProperty("Authorization")&&n.getAuthorizationHeader()&&(e.headers.Authorization=n.getAuthorizationHeader()),e},responseError:function(r){return r?(400!==r.status||!r.data||"invalid_request"!==r.data.error&&"invalid_grant"!==r.data.error||(n.removeToken(),t.$emit("oauth:error",r)),(401===r.status&&r.data&&"invalid_token"===r.data.error||r.headers&&r.headers("www-authenticate")&&0===r.headers("www-authenticate").indexOf("Bearer"))&&t.$emit("oauth:error",r),e.reject(r)):e.reject(r)}}}var c=e.module("angular-oauth2",[t]).config(r).factory("oauthInterceptor",u).provider("OAuth",i).provider("OAuthToken",a);r.$inject=["$httpProvider"];var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),f={baseUrl:null,clientId:null,clientSecret:null,grantPath:"/oauth2/token",revokePath:"/oauth2/revoke"},h=["baseUrl","clientId","grantPath","revokePath"],s=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();return u.$inject=["$q","$rootScope","OAuthToken"],c});
\ No newline at end of file
diff --git a/package.json b/package.json
index b9dc462..b3dca55 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "angular-oauth2",
-  "version": "4.1.1",
+  "version": "4.2.0",
   "description": "Angular OAuth2",
   "main": "./dist/angular-oauth2.js",
   "repository": {
