web-dev-qa-db-ja.com

AngularJS:$ resourceリクエストで認証トークンを送信する方法は?

APIからリソースをリクエストするときに認証トークンを送信したい。

$ resourceを使用してサービスを実装しました。

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

そして、私は認証トークンを保存するサービスを持っています:

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

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

  return tokenHandler;
});

Todoサービスを介して送信されるすべてのリクエストで、tokenHandler.getからトークンを送信したいと思います。特定のアクションの呼び出しに入れることで送信できました。たとえば、これは動作します:

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

ただし、access_tokenはTodoサービスのパラメーターとして定義することをお勧めします。これは、呼び出しごとに送信する必要があるためです。そして、乾燥を改善する。ただし、ファクトリ内のすべてが1回だけ実行されるため、ファクトリを定義する前にaccess_tokenを使用可能にする必要があり、その後は変更できません。

サービスに動的に更新された要求パラメーターを配置する方法はありますか?

62
Nils Blum-Oeste

アンディ・ジョスリンに感謝します。リソースアクションをラップするという彼のアイデアを選びました。リソースのサービスは次のようになります。

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

ご覧のとおり、リソースは最初から通常の方法で定義されています。私の例では、これにはupdateというカスタムアクションが含まれています。その後、リソースとアクションの配列をパラメーターとして受け取るtokenHandler.wrapAction()メソッドの戻り値によって、リソースが上書きされます。

ご想像のとおり、後者のメソッドは実際にアクションをラップしてすべてのリクエストに認証トークンを含め、変更されたリソースを返します。それでは、そのためのコードを見てみましょう:

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

ご覧のとおり、wrapActions()メソッドは、そのパラメーターからリソースのコピーを作成し、actions配列をループして、アクションごとに別の関数tokenWrapper()を呼び出します。最後に、変更されたリソースのコピーを返します。

tokenWrappermethodは、まず既存のリソースアクションのコピーを作成します。このコピーには末尾にアンダースコアが付いています。したがって、query()_query()になります。その後、新しいメソッドが元のquery()メソッドを上書きします。この新しいメソッドは、Andy Joslinによって提案された_query()をラップして、そのアクションを介して送信されるすべてのリクエストで認証トークンを提供します。

このアプローチの良い点は、すべてのanglejsリソースに付属する定義済みのアクション(get、query、saveなど)を、再定義することなく使用できることです。そして、残りのコード(たとえば、コントローラー内)では、デフォルトのアクション名を使用できます。

60
Nils Blum-Oeste

別の方法は、「マジック」認証ヘッダーを現在のOAuthトークンに置き換えるHTTPインターセプターを使用することです。以下のコードはOAuth固有ですが、それを修正することは読者にとって簡単な練習です。

// 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

私はこのアプローチが本当に好きです:

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

トークンは常に、ラッパーを必要とせずにリクエストヘッダー内で自動的に送信されます。

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

ラッパー関数を作成できます。

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

私もこの問題に対処しなければなりませんでした。私はそれがエレガントなソリューションであるとは思わないが、それは機能し、2行のコードがあります:

たとえば、SessionServiceでの認証後にサーバーからトークンを取得するとします。次に、この種のメソッドを呼び出します。

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

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

その後、$ resourceおよび$ httpからのすべてのリクエストのヘッダーにトークンが含まれます。

5
vpoulain

別の解決策は、追加のパラメーターでバインドされたリソースの新しいインスタンスを返すresource.bind(additionalParamDefaults)を使用することです

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

Access_token関数は、リソースのアクションが呼び出されるたびに呼び出されます。

3
ganmor

私はあなたの質問をすべて誤解しているかもしれません(私を修正してください:))が、すべてのリクエストにaccess_tokenを追加することに特に対処するために、TokenHandlerモジュールをTodoモジュールに挿入しようとしましたか?

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

Todo.query()を呼び出すと、?token=noneがURLに追加されます。または、トークンプレースホルダーを追加する場合は、もちろんそれもできます。

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

お役に立てれば :)

1
Darragh Enright

受け入れられた答えに続いて、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;
}]);
_

この方法では、Todoオブジェクトを使用するたびにTokenHandlerをインポートする必要はなく、次を使用できます。

todo.setToken(theNewToken);

私が行う別の変更は、wrapActionsで空の場合にデフォルトのアクションを許可することです。

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