让ng-token-auth与devise_token_auth配合使用

12

我有一个Rails和Ionic项目。后端使用devise_token_auth宝石,前端使用ng-token-auth;这些应该“无缝”工作。

就注册和登录而言,我已经使一切正常,而且返回了有效的响应对象。然而,在我使用$state.go('app.somepage')之后的任何进一步请求都会导致401未经授权的响应。

我感觉我实际上没有将令牌存储在任何地方。可以有人帮忙吗?

以下是一些代码片段:

    .controller('LoginCtrl',['$scope', '$auth', '$state', function($scope, $auth, $state) {
    $scope.loginForm = {}
    $scope.handleLoginBtnClick = function() {
      console.log($scope.loginForm);
      $auth.submitLogin($scope.loginForm)
          .then(function(resp) {
            $state.go('app.feed');
          })
          .catch(function(resp) {
            console.log(resp.errors);
          });
    };

状态定义:

    .state('app', {
  url: "/app",
  abstract: true,
  templateUrl: "templates/menu.html",
  controller: 'AppCtrl',
  resolve: {
    auth: function($auth) {
      return $auth.validateUser();
    }
  }

})

资源:

factory('Post', ['railsResourceFactory', 'apiUrl', function (railsResourceFactory, apiUrl) {
    return railsResourceFactory({
        url: apiUrl + '/posts',
        name: 'post'
    });
}]).

在PostsCtrl里:

  $scope.loadFeed = function() {
    Post.query().then(function (posts) {
      $scope.posts = posts;
    }, function (error) {
      console.log( 'Did not get posts!'); ### THIS FIRES
    }).finally(function() {
      // Stop the ion-refresher from spinning
      $scope.$broadcast('scroll.refreshComplete');
    });
  };

登录响应对象:

{"data":{"id":1,"provider":"email","uid":"1234","phone":null,"name":"Admin","image":null,"username":"admin"}}

ApplicationController 的顶部:

class ApplicationController < ActionController::Base
  include DeviseTokenAuth::Concerns::SetUserByToken

  before_filter :add_allow_credentials_headers
  before_filter :cors_preflight_check
  after_filter :cors_set_access_control_headers
  before_action :configure_permitted_parameters, if: :devise_controller?

  ..yadayada...

  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:sign_up) << :phone
    devise_parameter_sanitizer.for(:sign_up) << :username
    devise_parameter_sanitizer.for(:sign_up) << :session

    devise_parameter_sanitizer.for(:sign_in) << :phone
    devise_parameter_sanitizer.for(:sign_in) << :username
    devise_parameter_sanitizer.for(:sign_in) << :session
  end

还有一些Rails端默认的用户模型。

Rails日志:

Started GET "/posts" for 192.168.83.26 at 2015-02-24 23:29:02 -0500
Processing by PostsController#index as JSON
  Parameters: {"post"=>{}}
Filter chain halted as :authenticate_user! rendered or redirected
Completed 401 Unauthorized in 1ms (Views: 0.2ms | ActiveRecord: 0.0ms)

如果有人能提供一些见解,那将非常棒。如果需要的话,我很乐意发布更多片段。


我还没有完全阅读你的问题,但是我已经写了一篇关于Rails/Angular身份验证的文章,使用Lynn Dylan Hurley的库。https://www.airpair.com/ruby-on-rails/posts/authentication-with-angularjs-and-ruby-on-rails - Jason Swett
另外,我手头不知道你问题的答案,但是在你的情况下,我会建一个新的空项目并按照我的教程操作,看看是否起作用。如果教程可以运行,再对比一下你的项目和教程的差异。 - Jason Swett
你在这个项目上进展如何?我正在做类似的事情,遇到了一些问题,你开源了吗? - Phil Brockwell
不好意思,但是如果你阅读下面我选中的答案,那应该会给你一些线索。我没有权限开源代码。 :( - mostlydev
7个回答

8
很明显,解决方案非常简单。在大多数提供的示例中,它们忽略了允许access-token和其他CORS标头。我们在config.ru的底部使用了rack-cors来解决这个问题。
require 'rack/cors'
use Rack::Cors do

  # allow all origins in development
  allow do
    origins '*'
    resource '*',
             :headers => :any,
             :expose  => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
             :methods => [:get, :post, :delete, :put, :options]
  end
end

接下来,在ApplicationController.rb文件中:

  before_filter :add_allow_credentials_headers
  skip_before_filter :verify_authenticity_token
  before_filter :cors_preflight_check
  after_filter :cors_set_access_control_headers


  def cors_set_access_control_headers
    headers['Access-Control-Allow-Origin'] = '*'
    headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE, OPTIONS'
    headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization, Token'
    headers['Access-Control-Max-Age'] = '1728000'
  end

  def cors_preflight_check
    if request.method == 'OPTIONS'
      headers['Access-Control-Allow-Origin'] = '*'
      headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE, OPTIONS'
      headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-Prototype-Version, Token'
      headers['Access-Control-Max-Age'] = '1728000'

      render :text => '', :content_type => 'text/plain'
    end
  end

  def add_allow_credentials_headers
    # https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#section_5
    #
    # Because we want our front-end to send cookies to allow the API to be authenticated
    # (using 'withCredentials' in the XMLHttpRequest), we need to add some headers so
    # the browser will not reject the response
    response.headers['Access-Control-Allow-Origin'] = request.headers['Origin'] || '*'
    response.headers['Access-Control-Allow-Credentials'] = 'true'
  end

2
不确定为什么这个问题没有得到任何投票,或者为什么基本文档中没有涵盖到这个问题。对我来说,这是必要的功能,而且我认为我们并没有做任何异常的操作。 - commandantk
1
同意。我不知道没有这个怎么能工作。虽然我只需要 allow 块,而不需要任何 ApplicationController 的东西。此外,如果您使用 Rails,则允许块可以放在您的 application.rb 中,而不是 config.ru。 - smoyth
谢谢。我看到这是许多人的问题。我选择了自己的答案,因为它实际上是解决方案,而且我认为你最后的评论很相关。我们有点采用了“大锤子”的方法。感谢您提供有关config.ru中代码的提示。 - mostlydev
大家好,我遇到了完全相同的问题(ionic,没有访问令牌头),这些解决方案都没有帮助。非常感谢任何想法! - Uri Klar

3

2
关于我的情况,我使用cookie来存储令牌。每当我们在Angular应用程序中执行$auth方法时,其中一些方法会尝试访问您在Rails路由器中定义的设备路由,并匹配/验证存储在任何请求头中的令牌(每次尝试进行http请求!检查您的请求标头是否包含uid或auth_token,如果您要通过GET /validate_token进行验证,则可以通过浏览器检查器中的 https://github.com/lynndylanhurley/devise_token_auth#usage-tldr )。
由于您没有提及您的路由,我们可以假设/auth。
那些由$auth提供的$http请求应该包含令牌以对Rails的Devise进行身份验证,并在我们执行$auth.submitLogin()时将其捕获并存储到浏览器的cookie中。
以下是我之前项目中它如何工作的示例。
app.factory('authInterceptor', ['$q', 'ipCookie', '$location',  function($q, ipCookie, $location) {
  return {
    request: function(config) {
      config.headers = config.headers || {};
      if (ipCookie('access-token')) {
        config.headers['Access-Token'] = ipCookie('access-token');
        config.headers['Client'] = ipCookie('client');
        config.headers['Expiry'] = ipCookie('expiry');
        config.headers['Uid'] = ipCookie('uid');
      }
      return config;
    },
    responseError: function(response) {
      if (response.status === 401) {
        $location.path('/login');
        ipCookie.remove('access-token');
      }
      return $q.reject(response);
    }
  };
}])

将令牌格式设置为以下格式(或根据需要自定义):
$authProvider.configure({
  tokenValidationPath: '/auth/validate_token',
  signOutUrl: '/auth/sign_out',
  confirmationSuccessUrl: window.location.href,
  emailSignInPath: '/auth/sign_in',
  storage: 'cookies',
  tokenFormat: {
    "access-token": "{{ token }}",
    "token-type": "Bearer",
    "client": "{{ clientId }}",
    "expiry": "{{ expiry }}",
    "uid": "{{ uid }}"
  }
});

不要忘记在拦截器中注入 ipCookie(查找 angular-cookie 而不是 angular-cookies) ,因为这是 ng-token-auth 用于管理 cookies 的 cookie 库。
如果我没有表达清楚,请在下面发表评论并提出问题。 :D

2
谢谢,解决了。此外,我们使用本地存储来存储令牌,这同样有效。 - mostlydev
1
我可以验证这个解决方案是可行的。我还在使用本地存储。 - Jason Swett

1
也许已经太晚了,
但问题在于你无法在cookie上获得授权(仅限Android)。因此,您可以尝试使用localStorage来保存会话信息(在iOS和Android上)。

e.g

.config(function($authProvider) {
  $authProvider.configure({
    apiUrl: 'http://myprivateapidomain/api',
    storage: 'localStorage'
  });
})

您可以在文档的特定问题中阅读更多信息:https://github.com/lynndylanhurley/ng-token-auth/issues/93

我们最初确实这样做了,但它并没有解决问题。它只是让我们能够存储一些会话信息并“假装”已经进行了身份验证。无论如何,令牌的整个理念就是要使用会话数据。这是一个不同的问题的不同解决方案。 - mostlydev

1

虽然有点晚了,但是对于那些想在Ionic应用中使用ng-token-auth的人,我所做的使其工作的方法(在我的情况下)是将下一个配置设置到我的模块中:

app.config(['$httpProvider', function($httpProvider) {  
    $httpProvider.defaults.withCredentials = true;

  }]);

我在HTTP请求中没有发送任何cookie。


0

0

你尝试过为$authProvider添加配置吗?这个例子在https://github.com/lynndylanhurley/devise_token_auth的自述文件中。

angular.module('myApp', ['ng-token-auth'])
  .config(function($authProvider) {
    $authProvider.configure({
      apiUrl: 'http://api.example.com'
      authProviderPaths: {
        github: '/auth/github' // <-- note that this is different than what was set with github
      }
    });
  });

1
是的,谢谢,身份验证最初有效,但随后无法转换为后续页面请求。 - mostlydev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接