首页 文章

Ember-simple-auth,Torii和Facebook Oauth2的工作流程

提问于
浏览
17

my previous question about ember-simple-auth and torii之后,我使用他们的Facebook帐户成功验证了我的用户 .

但目前,torii的提供商facebook-oauth2正在从Facebook返回授权码;当承诺解决时,我将此授权代码发送到我的后端,在那里我执行针对Facebook的请求以获取用户的ID和电子邮件:然后我在我的后端验证用户,生成特定的访问令牌并发送回我的ember应用程序 .

客户代码:

// app/controllers/login.js
import Ember from 'ember';
import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin';

export
default Ember.Controller.extend(LoginControllerMixin, {
    // This authenticator for a simple login/password authentication.
    authenticator: 'simple-auth-authenticator:oauth2-password-grant',
    actions: {
        // This method for login with Facebook.
        authenticateWithFacebook: function() {
            var _this = this;
            this.get('session').authenticate(
                'simple-auth-authenticator:torii',
                "facebook-oauth2"
            ).then(
                function() {
                    var authCode = _this.get('session.authorizationCode');
                    Ember.$.ajax({
                            type: "POST",
                            url: window.ENV.host + "/facebook/auth.json",
                            data: JSON.stringify({
                                    auth_code: authCode
                            }),
                            contentType: "application/json; charset=utf-8",
                            dataType: "json",
                            success: function(data) {
                                    // TODO : manage access_token and save it to the session
                            },
                            failure: function(errMsg) {
                                    // TODO : manage error
                            }
                    });
                },
                function(error) {
                    alert('There was an error when trying to sign you in: ' + error);
                }
            );
        }
    }
});

问题是:当authenticate的promise解析后,ember-simple-auth的会话被标记为已验证,然后app会重定向到特定的已验证路由 . 但在这种情况下,当我的后端返回“真正的”access_token时,应该对会话进行身份验证 .

有没有办法用ember-simple-auth-torii管理这个工作流程,还是应该编写自己的身份验证器?

3 回答

  • 1

    正如Beerlington建议的那样,我终于编写了自己的身份验证器 . 但是我也给了我的用户一种使用登录/密码进行身份验证的方法,所以我覆盖了ember-simple-auth-oauth2身份验证器,仅更改了“authenticate”方法并使用了ember-simple-auth-torii .

    现在我可以使用Torii从用户的Facebook帐户获取授权代码,将此代码发送到我的后端,验证用户身份并生成一个访问令牌,该令牌将由ember-simple-auth管理,如oauth2令牌 .

    这是代码:

    // initializers/simple-auth-config.js
    import Ember from 'ember';
    import Oauth2 from 'simple-auth-oauth2/authenticators/oauth2';
    
    /**
      Authenticator that extends simple-auth-oauth2 and wraps the
      [Torii library](https://github.com/Vestorly/torii)'s facebook-oauth2 provider.
    
        It is a mix between ember-simple-auth-torii and ember-simple-auth-oauth2.
    
        First it uses Torii to get the facebook access token or the authorization code.
    
        Then it performs a request to the backend's API in order to authenticate the
        user (fetching personnal information from Facebook, creating account, login,
        generate session and access token). Then it uses simple-auth's
        oauth2 authenticator to maintain the session.
    
        _The factory for this authenticator is registered as
        `'authenticator:facebook'` in Ember's container._
    
        @class Facebook
        @namespace Authenticators
        @extends Oauth2
    */
    var FacebookAuthenticator = Oauth2.extend({
        /**
        @property torii
        @private
        */
        torii: null,
    
        /**
        @property provider
        @private
        */
        provider: "facebook-oauth2",
    
        /**
        Authenticates the session by opening the torii provider. For more
        documentation on torii, see the
        [project's README](https://github.com/Vestorly/torii#readme). Then it makes a
        request to the backend's token endpoint and manage the result to create
        the session.
    
        @method authenticate
        @return {Ember.RSVP.Promise} A promise that resolves when the provider successfully 
        authenticates a user and rejects otherwise
        */
        authenticate: function() {
            var _this = this;
            return new Ember.RSVP.Promise(function(resolve, reject) {
                _this.torii.open(_this.provider).then(function(data) {
                    var data = {
                        facebook_auth_code: data.authorizationCode
                    };
                    _this.makeRequest(_this.serverTokenEndpoint, data).then(function(response) {
                        Ember.run(function() {
                            var expiresAt = _this.absolutizeExpirationTime(response.expires_in);
                            _this.scheduleAccessTokenRefresh(response.expires_in, expiresAt, response.refresh_token);
                            if (!Ember.isEmpty(expiresAt)) {
                                response = Ember.merge(response, {
                                expires_at: expiresAt
                            });
                            }
                            resolve(response);
                        });
                    }, function(xhr, status, error) {
                        Ember.run(function() {
                                reject(xhr.responseJSON || xhr.responseText);
                        });
                    });
                }, reject);
            });
        },
    });
    
    export
    default {
        name: 'simple-auth-config',
        before: 'simple-auth',
        after: 'torii',
        initialize: function(container, application) {
            window.ENV = window.ENV || {};
            window.ENV['simple-auth-oauth2'] = {
                serverTokenEndpoint: window.ENV.host + "/oauth/token",
                refreshAccessTokens: true
            };
    
            var torii = container.lookup('torii:main');
            var authenticator = FacebookAuthenticator.create({
                torii: torii
            });
            container.register('authenticator:facebook', authenticator, {
                instantiate: false
            });
        }
    };
    

    我的后端在Rails中,并使用Doorkeeper来管理access_token和Devise . 我重写了Doorkeeper :: TokensController以传递带有令牌的user_id并管理facebook的授权码(如果有的话)(该代码应该被重构):

    class TokensController < Doorkeeper::TokensController
        include Devise::Controllers::SignInOut # Include helpers to sign_in
    
        # The main accessor for the warden proxy instance
        # Used by Devise::Controllers::SignInOut::sign_in
        #
        def warden
            request.env['warden']
        end
    
        # Override this method in order to manage facebook authorization code and
        # add resource_owner_id in the token's response as
        # user_id.
        #
        def create
            if params[:facebook_auth_code]
                # Login with Facebook.
                oauth = Koala::Facebook::OAuth.new("app_id", "app_secret", "redirect_url")
    
                access_token = oauth.get_access_token params[:facebook_auth_code]
                graph = Koala::Facebook::API.new(access_token, "app_secret")
                facebook_user = graph.get_object("me", {}, api_version: "v2.1")
    
                user = User.find_or_create_by(email: facebook_user["email"]).tap do |u|
                    u.facebook_id = facebook_user["id"]
                    u.gender = facebook_user["gender"]
                    u.username = "#{facebook_user["first_name"]} #{facebook_user["last_name"]}"
                    u.password = Devise.friendly_token.first(8)
                    u.save!
                end
    
                access_token = Doorkeeper::AccessToken.create!(application_id: nil, :resource_owner_id => user.id, expires_in: 7200)
                sign_in(:user, user)
    
                token_data = {
                    access_token: access_token.token,
                    token_type: "bearer",
                    expires_in: access_token.expires_in,
                    user_id: user.id.to_s
                }
    
                render json: token_data.to_json, status: :ok
    
            else
                # Doorkeeper's defaut behaviour when the user signs in with login/password.
                begin
                    response = strategy.authorize
                    self.headers.merge! response.headers
                    self.response_body = response.body.merge(user_id: (response.token.resource_owner_id && response.token.resource_owner_id.to_s)).to_json
                    self.status        = response.status
                rescue Doorkeeper::Errors::DoorkeeperError => e
                    handle_token_exception e
                end
    
            end
        end
    end
    

    这是我在初始化程序doorkeeper.rb中使用的代码,用于验证用户身份

    Doorkeeper.configure do
      # Change the ORM that doorkeeper will use.
      # Currently supported options are :active_record, :mongoid2, :mongoid3, :mongo_mapper
      orm :mongoid4
    
      resource_owner_from_credentials do |routes|
        request.params[:user] = {:email => request.params[:username], :password => request.params[:password]}
        request.env["devise.allow_params_authentication"] = true
        request.env["warden"].authenticate!(:scope => :user)
      end
      # This block will be called to check whether the resource owner is authenticated or not.
      resource_owner_authenticator do
        # Put your resource owner authentication logic here.
        # Example implementation:
        #   User.find_by_id(session[:user_id]) || redirect_to(new_user_session_url)
        #
        # USING DEVISE IS THE FOLLOWING WAY TO RETRIEVE THE USER
        current_user || warden.authenticate!(:scope => :user)
      end
    
      # Under some circumstances you might want to have applications auto-approved,
      # so that the user skips the authorization step.
      # For example if dealing with trusted a application.
      skip_authorization do |resource_owner, client|
         # client.superapp? or resource_owner.admin?
         true
      end
    end
    
  • 0

    我花了几天时间试图弄清楚如何使它与torii一起工作,最后放弃了我自己的身份验证器 . 这是来自torii和ember-simple-auth的代码的混合,因此它不是最干净的,并且可能无法处理所有边缘情况 . 它基本上扩展了ember-simple-auth oauth2身份验证器,并添加了自定义代码以将访问令牌传递给API .

    应用程序/ LIB / Facebook的authenticator.js

    /* global FB */
    
    import OAuth2Authenticator from 'simple-auth-oauth2/authenticators/oauth2';
    import ajax from 'ic-ajax';
    
    var fbPromise;
    
    var settings = {
      appId: '1234567890',
      version: 'v2.1'
    };
    
    function fbLoad(){
      if (fbPromise) { return fbPromise; }
    
      fbPromise = new Ember.RSVP.Promise(function(resolve){
        FB.init(settings);
        Ember.run(null, resolve);
      });
    
      return fbPromise;
    }
    
    function fblogin() {
      return new Ember.RSVP.Promise(function(resolve, reject){
        FB.login(function(response){
          if (response.authResponse) {
            Ember.run(null, resolve, response.authResponse);
          } else {
            Ember.run(null, reject, response.status);
          }
        }, {scope: 'email'});
      });
    }
    
    export default OAuth2Authenticator.extend({
      authenticate: function() {
        var _this = this;
    
        return new Ember.RSVP.Promise(function(resolve, reject) {
          fbLoad().then(fblogin).then(function(response) {
            ajax(MyApp.API_NAMESPACE + '/oauth/facebook', {
              type: 'POST',
              data: {
                auth_token: response.accessToken,
                user_id: response.userId
              }
            }).then(function(response) {
              Ember.run(function() {
                var expiresAt = _this.absolutizeExpirationTime(response.expires_in);
                _this.scheduleAccessTokenRefresh(response.expires_in, expiresAt, response.refresh_token);
                if (!Ember.isEmpty(expiresAt)) {
                  response = Ember.merge(response, { expires_at: expiresAt });
                }
                resolve(response);
              });
            }).catch(function(xhr) {
              Ember.run(function() {
                reject(xhr.textStatus);
              });
            });
          });
        });
      },
    
      loadFbLogin: function(){
        fbLoad();
      }.on('init')
    });
    
  • 17

    我用过这个:

    import Ember from 'ember';
    import Torii from 'ember-simple-auth/authenticators/torii';
    import ENV from "../config/environment";
    
    const { inject: { service } } = Ember;
    
    export default Torii.extend({
      torii: service(),
      ajax: service(),
    
      authenticate() {
        const ajax = this.get('ajax');
    
        return this._super(...arguments).then((data) => {
          return ajax.request(ENV.APP.API_HOST + "/oauth/token", {
            type:     'POST',
            dataType: 'json',
            data:     { 'grant_type': 'assertion', 'auth_code': data.authorizationCode, 'data': data }
          }).then((response) => {
            return {
              access_token: response.access_token,
              provider: data.provider,
              data: data
            };
          }).catch((error) => {
            console.log(error);
          });
        });
      }
    });
    

相关问题