web-dev-qa-db-fra.com

AngularJS: Comment envoyer un jeton d'authentification avec des demandes de ressources $?

Je souhaite envoyer un jeton d'authentification lorsque je demande une ressource à mon API.

J'ai implémenté un service en utilisant $ resource:

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

Et j'ai un service qui stocke le jeton d'authentification:

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

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

  return tokenHandler;
});

Je souhaite envoyer le token de tokenHandler.get à chaque demande envoyée via le service Todo. J'ai pu l'envoyer en le plaçant dans l'appel d'une action spécifique. Par exemple, cela fonctionne:

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

Mais je préférerais définir le access_token comme paramètre dans le service Todo, car il doit être envoyé à chaque appel. Et pour améliorer DRY. Mais tout dans l'usine n'est exécuté qu'une seule fois, donc le access_token devrait être disponible avant de définir l'usine et il ne peut pas changer après.

Existe-t-il un moyen de mettre un paramètre de requête mis à jour dynamiquement dans le service?

62
Nils Blum-Oeste

Merci à Andy Joslin. J'ai choisi son idée d'envelopper les actions de ressources. Le service de la ressource ressemble maintenant à ceci:

.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;
}])

Comme vous pouvez le voir, la ressource est définie de la manière habituelle en premier lieu. Dans mon exemple, cela inclut une action personnalisée appelée update. Ensuite, la ressource est écrasée par le retour de la méthode tokenHandler.wrapAction() qui prend la ressource et un tableau d'actions comme paramètres.

Comme vous vous en doutez, cette dernière méthode encapsule réellement les actions pour inclure le jeton d'authentification dans chaque demande et renvoie une ressource modifiée. Jetons donc un œil au code pour cela:

.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;
});

Comme vous pouvez le voir, la méthode wrapActions() crée une copie de la ressource à partir de ses paramètres et parcourt le tableau actions pour appeler une autre fonction tokenWrapper() pour chaque action. Au final, il renvoie la copie modifiée de la ressource.

La méthode tokenWrapper crée tout d'abord une copie de l'action de ressource préexistante. Cette copie a un trait de soulignement de fin. Donc query() devient _query(). Ensuite, une nouvelle méthode remplace la méthode query() d'origine. Cette nouvelle méthode encapsule _query(), comme suggéré par Andy Joslin, pour fournir le jeton d'authentification à chaque demande envoyée via cette action.

La bonne chose avec cette approche est que nous pouvons toujours utiliser les actions prédéfinies fournies avec chaque ressource angularjs (get, query, save, etc.), sans avoir à les redéfinir. Et dans le reste du code (dans les contrôleurs par exemple), nous pouvons utiliser le nom d'action par défaut.

60
Nils Blum-Oeste

Une autre façon consiste à utiliser un intercepteur HTTP qui remplace un en-tête d'autorisation "magique" par le jeton OAuth actuel. Le code ci-dessous est OAuth spécifique, mais y remédie) est un exercice simple pour le lecteur.

// 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');
});
34
Ben Walding

J'aime vraiment cette approche:

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

où le jeton est toujours envoyé automatiquement dans l'en-tête de la demande sans avoir besoin d'un wrapper.

// Define a new http header
$http.defaults.headers.common['auth-token'] = 'C3PO R2D2';
21
ricricucit

Vous pouvez créer une fonction wrapper pour cela.

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;
})
9
Andrew Joslin

J'ai dû aussi faire face à ce problème. Je ne pense pas que ce soit une solution élégante mais ça marche et il y a 2 lignes de code:

Je suppose que vous obtenez votre jeton de votre serveur après une authentification dans SessionService par exemple. Ensuite, appelez ce type de méthode:

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

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

Après cela, toutes vos demandes provenant de $ resource et $ http auront un jeton dans leur en-tête.

5
vpoulain

Une autre solution serait d'utiliser resource.bind (additionalParamDefaults), qui renvoie une nouvelle instance de la ressource liée avec des paramètres supplémentaires

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

La fonction access_token sera appelée à chaque appel de l'action sur la ressource.

3
ganmor

J'ai peut-être mal compris toutes vos questions (n'hésitez pas à me corriger :)) mais pour répondre spécifiquement à l'ajout du access_token Pour chaque demande, avez-vous essayé d'injecter le module TokenHandler dans le 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
    });
})

Vous pouvez appeler Todo.query() et il ajoutera ?token=none À votre URL. Ou si vous préférez ajouter un espace réservé de jeton, vous pouvez bien sûr le faire également:

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

J'espère que cela t'aides :)

1
Darragh Enright

Suite à votre réponse acceptée, je proposerais d'étendre la ressource afin de définir le jeton avec l'objet Todo:

.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;
}]);

De cette façon, il n'est pas nécessaire d'importer le TokenHandler chaque fois que vous souhaitez utiliser l'objet Todo et vous pouvez utiliser:

todo.setToken(theNewToken);

Une autre modification que je ferais serait d'autoriser les actions par défaut si elles sont vides dans wrapActions:

if (!actions || actions.length === 0) {
  actions = [];
  for (i in resource) {
    if (i !== 'bind') {
      actions.Push(i);
    }
  }
}
1
Miquel