AngularJS: How to send auth token with $resource requests?
Asked Answered
S

8

62

I want to send an auth token when requesting a resource from my API.

I did implement a service using $resource:

factory('Todo', ['$resource', function($resource) {
 return $resource('http://localhost:port/todos.json', {port:":3001"} , {
   query: {method: 'GET', isArray: true}
 });
}])

And I have a service that stores the auth token:

factory('TokenHandler', function() {
  var tokenHandler = {};
  var token = "none";

  tokenHandler.set = function( newToken ) {
    token = newToken;
  };
  tokenHandler.get = function() {
    return token;
  };

  return tokenHandler;
});

I would like to send the token from tokenHandler.get with every request send via the Todo service. I was able to send it by putting it into the call of a specific action. For example this works:

Todo.query( {access_token : tokenHandler.get()} );

But I would prefer to define the access_token as a parameter in the Todo service, as it has to be sent with every call. And to improve DRY. But everything in the factory is executed only once, so the access_token would have to be available before defining the factory and it cant change afterwards.

Is there a way to put a dynamically updated request parameter in the service?

Swagman answered 24/6, 2012 at 9:26 Comment(0)
S
60

Thanks to Andy Joslin. I picked his idea of wrapping the resource actions. The service for the resource looks like this now:

.factory('Todo', ['$resource', 'TokenHandler', function($resource, tokenHandler) {
  var resource = $resource('http://localhost:port/todos/:id', {
    port:":3001",
    id:'@id'
    }, {
      update: {method: 'PUT'}
    });

  resource = tokenHandler.wrapActions( resource, ["query", "update"] );

  return resource;
}])

As you can see the resource is defined the usual way in the first place. In my example this includes a custom action called update. Afterwards the resource is overwritten by the return of the tokenHandler.wrapAction() method which takes the resource and an array of actions as parameters.

As you would expect the latter method actually wraps the actions to include the auth token in every request and returns a modified resource. So let's have a look at the code for that:

.factory('TokenHandler', function() {
  var tokenHandler = {};
  var token = "none";

  tokenHandler.set = function( newToken ) {
    token = newToken;
  };

  tokenHandler.get = function() {
    return token;
  };

  // wrap given actions of a resource to send auth token with every
  // request
  tokenHandler.wrapActions = function( resource, actions ) {
    // copy original resource
    var wrappedResource = resource;
    for (var i=0; i < actions.length; i++) {
      tokenWrapper( wrappedResource, actions[i] );
    };
    // return modified copy of resource
    return wrappedResource;
  };

  // wraps resource action to send request with auth token
  var tokenWrapper = function( resource, action ) {
    // copy original action
    resource['_' + action]  = resource[action];
    // create new action wrapping the original and sending token
    resource[action] = function( data, success, error){
      return resource['_' + action](
        angular.extend({}, data || {}, {access_token: tokenHandler.get()}),
        success,
        error
      );
    };
  };

  return tokenHandler;
});

As you can see the wrapActions() method creates a copy of the resource from it's parameters and loops through the actions array to call another function tokenWrapper() for every action. In the end it returns the modified copy of the resource.

The tokenWrappermethod first of all creates a copy of preexisting resource action. This copy has a trailing underscore. So query()becomes _query(). Afterwards a new method overwrites the original query() method. This new method wraps _query(), as suggested by Andy Joslin, to provide the auth token with every request send through that action.

The good thing with this approach is, that we still can use the predefined actions which come with every angularjs resource (get, query, save, etc.), without having to redefine them. And in the rest of the code (within controllers for example) we can use the default action name.

Swagman answered 24/6, 2012 at 21:20 Comment(6)
Did write a blog post about it. Check it out here: nils-blum-oeste.net/…Swagman
thx @nblumoe How can we send same token to Http Header? In your example token will go as an extended property of Json object which is being posted. my question here - #12586695Kenning
@nblumoe i am trying to understand this, but i don't see when is the token setted with tokenHandler.set(token) ? thxWalkthrough
@desgnl that's indeed not being shown here. It depends on how you retrieve the token in the first place. TokenHandler is a service which you could inject to the right place and store the token there (probably after some kind of authentication request to a server).Swagman
@nblumoe Ok. so does it make sense to set the token value to the encoded username and password, or the session cookie, or something else ?Walkthrough
@NilsBlum-Oeste, I know this is an old answer, but this grossly oversimplifies the flexibility of the args that can be passed to resource actions since params, data, success, and error are all optional args and depend on whether it's a GET or non-GET action and you are only trying to modify the params which may or may not even be passed in.Galba
H
35

Another way is to use an HTTP interceptor which replaces a "magic" Authorization header with the current OAuth token. The code below is OAuth specific, but remedying that is a simple exercise for the reader.

// Injects an HTTP interceptor that replaces a "Bearer" authorization header
// with the current Bearer token.
module.factory('oauthHttpInterceptor', function (OAuth) {
  return {
    request: function (config) {
      // This is just example logic, you could check the URL (for example)
      if (config.headers.Authorization === 'Bearer') {
        config.headers.Authorization = 'Bearer ' + btoa(OAuth.accessToken);
      }
      return config;
    }
  };
});

module.config(function ($httpProvider) {
  $httpProvider.interceptors.push('oauthHttpInterceptor');
});
Hannah answered 17/7, 2013 at 8:56 Comment(1)
More information on interceptors in the AngularJS documentation here: docs.angularjs.org/api/ng.$http. From the docs: "or purposes of global error handling, authentication, or any kind of synchronous or asynchronous pre-processing of request or postprocessing of responses, it is desirable to be able to intercept requests..."Fiesta
T
21

I really like this approach:

http://blog.brunoscopelliti.com/authentication-to-a-restful-web-service-in-an-angularjs-web-app

where the token is always automagically sent within the request header without the need of a wrapper.

// Define a new http header
$http.defaults.headers.common['auth-token'] = 'C3PO R2D2';
Tarsuss answered 7/11, 2013 at 14:16 Comment(4)
One downside is that it will be sent for all requests - and if you are accessing multiple backends this might be a problem. For most basic scenarios this will be suitable though.Hannah
@BenW Why it could be problematic if you are accessing multiple backends?Prostrate
@InancGumus Because then you are sending your credentials / tokens to an unrelated service - risk of MITM / replay attacks.Hannah
This blog post alors explains how you can send token only for some requests, or HTTP methods (ex: only for DELETE methods). It's a very interesting reading if you're into token authentification with Angular.Digitalis
A
9

You could create a wrapper function for it.

app.factory('Todo', function($resource, TokenHandler) {
    var res= $resource('http://localhost:port/todos.json', {
        port: ':3001',
    }, {
        _query: {method: 'GET', isArray: true}
    });

    res.query = function(data, success, error) {
        //We put a {} on the first parameter of extend so it won't edit data
        return res._query(
            angular.extend({}, data || {}, {access_token: TokenHandler.get()}),
            success,
            error
        );
    };

    return res;
})
Acrodont answered 24/6, 2012 at 18:35 Comment(2)
Thanks for pushing me in that direction. Based on that I did implement a solution (see my recent answer), where one doesn't have to redefine the default actions by hand but still can use the default names in controllers and so on. Furthermore it is DRY if you want to wrap multiple actions.Swagman
is it adding "access_token" as value of property to existing object which is being posted. How can we improve it to pass auth-token as part of HTTP header?Kenning
S
5

I had to deal with this problem as well. I don't think if it is an elegant solution but it works and there are 2 lines of code :

I suppose you get your token from your server after an authentication in SessionService for instance. Then, call this kind of method :

   angular.module('xxx.sessionService', ['ngResource']).
    factory('SessionService', function( $http,  $rootScope) {

         //...
       function setHttpProviderCommonHeaderToken(token){
          $http.defaults.headers.common['X-AUTH-TOKEN'] = token;
       }  
   });

After that all your requests from $resource and $http will have token in their header.

Soever answered 23/5, 2013 at 13:38 Comment(0)
G
3

Another solution would be to use resource.bind(additionalParamDefaults), that return a new instance of the resource bound with additional parameters

var myResource = $resource(url, {id: '@_id'});
var myResourceProtectedByToken = myResource.bind({ access_token : function(){
        return tokenHandler.get();
}});
return myResourceProtectedByToken;

The access_token function will be called every time any of the action on the resource is called.

Gantry answered 26/3, 2014 at 21:11 Comment(1)
Great! Thanks! Finally came up with something like that: Great! Thanks! Finally came up with something like that: function getToken(){ return localStorageService.get('token') || null; } var rest = { articles: $resource(url, {}, { load: {method: 'GET'}, save: {method: 'POST'} }).bind({token: getToken}), ... } return rest;Multiply
S
1

I might be misunderstanding all of your question (feel free to correct me :) ) but to specifically address adding the access_token for every request, have you tried injecting the TokenHandler module into the Todo module?

// app
var app = angular.module('app', ['ngResource']);

// token handler
app.factory('TokenHandler', function() { /* ... */ });

// inject the TokenHandler
app.factory('Todo', function($resource, TokenHandler) {
    // get the token
    var token = TokenHandler.get();
    // and add it as a default param
    return $resource('http://localhost:port/todos.json', {
        port: ':3001',
        access_token : token
    });
})

You can call Todo.query() and it will append ?token=none to your URL. Or if you prefer to add a token placeholder you can of course do that too:

http://localhost:port/todos.json/:token

Hope this helps :)

Snead answered 24/6, 2012 at 14:9 Comment(2)
Thanks, but this does not work. This way the access_token in the request will always be "none". TokenHandler.get() inside the factory will only be called when creating the Todo singleton. But the token may change and this has to be reflected in the code.Swagman
ah.. okay, I getcha. I thought the main crux of your question was how to tidy up Todo.get({ auth_token: TokenHelper.get() }). It wasn't clear to me that updating the token was part of the question (it is now that I read it a little better). my bad :)Snead
H
1

Following your accepted answer, I would propose to extend the resource in order to set the token with the Todo object:

.factory('Todo', ['$resource', 'TokenHandler', function($resource, tokenHandler) {
  var resource = $resource('http://localhost:port/todos/:id', {
    port:":3001",
    id:'@id'
    }, {
      update: {method: 'PUT'}
    });

  resource = tokenHandler.wrapActions( resource, ["query", "update"] );
  resource.prototype.setToken = function setTodoToken(newToken) {
    tokenHandler.set(newToken);
  };
  return resource;
}]);

In that way there is no need to import the TokenHandler each time you want to use the Todo object and you can use:

todo.setToken(theNewToken);

Another change I would do is to allow default actions if they are empty in wrapActions:

if (!actions || actions.length === 0) {
  actions = [];
  for (i in resource) {
    if (i !== 'bind') {
      actions.push(i);
    }
  }
}
Hillari answered 6/10, 2014 at 12:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.