stormpath / stormpath-sdk-angularjs

User Management for AngularJS (1.x) applications
http://docs.stormpath.com/angularjs/sdk/
Apache License 2.0
165 stars 58 forks source link

OAuth implementation #195

Closed the-overengineer closed 7 years ago

the-overengineer commented 8 years ago

Implements a client-slide OAuth2 workflow, with a generic interface for the actual storage of tokens.

This is a part of the Client API feature. When communicating with a remote API, the SDK will now use /oauth/token instead of /login, and /oauth/revoke instead of /logout (with POST requests). Additionally, it will take care of setting the proper Authorization header, as well as of removing and refreshing the token pair on certain OAuth2-standard-prescribed errors.

Once can test this by taking the URL from the application webConfig (https://<app-name>.apps.dev.stormpath.io at the time this was written), and registering it as the remote endpoint in a config block:

.config(function(STORMPATH_CONFIG) {
  STORMPATH_CONFIG.ENDPOINT_PREFIX = 'https://<app-name>.apps.dev.stormpath.io';
})

When the domain is different than the request origin, the OAuth routine will automatically kick in. The implementation expects the client to support the password and refresh_token grant types.

The tokens are stored in a fairly generic way. Concrete token store implementations are managed through name-object pairs via the TokenStoreManager service. While this introduces string keys, it allows for a fairly centralised management system for them. Implementations must obey a simple contract, documented in the changed code:

{
  put(key: string, value: any) : $q.promise(), // indication of success
  get(key: string) : $q.promise(value),
  remove(key: string) : $q.promise() // indication of success
}

This allows for both synchronous and asynchronous store implementations.

By default, a dependency-less LocalStorageTokenStore is provided, and set as the default token store mechanism. However, one can easily add their own token store (as documented in the ngdocs), as long as it satisfies the contract.

   angular.module('app')
     .run(['$q', 'TokenStoreManager', function($q, TokenStoreManager) {
       // Can also be provided by a service/factory for better code organisation
       var myStore = {
         data: {},
         get: function get(key) {
           return this.data[key] ? $q.resolve(this.data[key]) : $q.reject();
         },
         put: function put(key, value) {
           this.data[key] = value;
           return $q.resolve();
         },
         remove: function remove(key) {
           delete this.data[key];
           return $q.resolve();
         }
       };

       TokenStoreManager.registerTokenStore('basicStore', myStore);

       var alsoMyStore = TokenStoreManager.getTokenStore('basicStore');
     }]);

A similar wrapper can be written around $cookies, SecureStorage, or other similar libs. Once a storage type is registered, it can be set as either the default storage type for all token objects created in the future, or just a specific one, if that is desired for some reason.

angular.module('app')
.config(function(StormpathOAuthTokenProvider) {
  StormpathOAuthTokenProvider.setTokenStoreType('basicStore');
})
.run(function(StormpathOAuthToken) {
  // Originally uses basicStore
  StormpathOAuthToken.setTokenStoreType('cookies');
  // Now uses the cookies implementation, whatever that may be
  StormpathOAuthToken.removeToken(); // Removes  from cookies storage
});

In addition to all this, some code was moved around because it was now required by multiple modules.

This is, of course, merely an initial implementation. I am more than open to discussion.

Fixes #46

robertjd commented 8 years ago

Hi @Tweety-FER , I spent some more time with this today. As mentioned earlier I think the interfaces are looking right, but please see my most recent commits for some changes.

I'm running into a problem with the refresh() method, it gets stuck in a loop and continuously tries to refresh the token. It appears that when we retry the request, this new request is not picking up the new token that was put in storage. Can you take a look? I wasn't able to find the cause. You can change the access token timeout in the application's oauth policy.

Also, you'll need to comment out the `'X-Stormpath-Agent', that still isn't available on the dev box.

the-overengineer commented 8 years ago

Thanks for your changes @robertjd! Fixed the issue with the refresh loop. Seems to work fine now. I also extracted your isBlacklisted function into a provider (config) + service (usage) combo, so that the users can add their own blacklisted URLs.

the-overengineer commented 7 years ago

Superceded by #197