简体   繁体   English

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

[英]Workflow for Ember-simple-auth, Torii and Facebook Oauth2

After my previous question about ember-simple-auth and torii , I successfully authenticate my users with their Facebook accounts. 我之前关于ember-simple-auth和torii的问题之后 ,我使用他们的Facebook帐户成功验证了我的用户。

But currently, torii's provider facebook-oauth2 is returning an authorization code from Facebook ; 但目前,torii的提供商facebook-oauth2正在从Facebook返回授权码; when the promise resolves, I send this authorization code to my backend where I perform a request against Facebook to get the user's id and email : then I authenticate the user on my backend, generating a specific access token and sending back to my ember application. 当承诺解决时,我将此授权代码发送到我的后端,在那里我执行针对Facebook的请求以获取用户的ID和电子邮件:然后我在我的后端验证用户,生成特定的访问令牌并发送回我的ember应用程序。

Client code : 客户代码:

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

The problem is : the ember-simple-auth's session is marked as authenticated when the authenticate's promise resolves and then the app redirects to the specific authenticated route. 问题是:当验证者的承诺解析后,ember-simple-auth的会话被标记为已验证,然后应用程序重定向到特定的验证路由。 But in this case the session should be authenticated when my backend returns the "real" access_token. 但在这种情况下,当我的后端返回“真正的”access_token时,应该对会话进行身份验证。

Is there a way to manage this workflow with ember-simple-auth-torii or should I write my own authenticator ? 有没有办法用ember-simple-auth-torii管理这个工作流程,还是我应该编写自己的身份验证器?

I finally wrote my own authenticator as Beerlington suggested. 正如Beerlington建议的那样,我终于写了自己的身份验证器。 But also I give to my users a way to authenticate using login/password, so I overrode the ember-simple-auth-oauth2 authenticator, changing only the "authenticate" method and used ember-simple-auth-torii. 但我也给了我的用户一种使用登录/密码进行身份验证的方法,所以我覆盖了ember-simple-auth-oauth2身份验证器,仅更改了“authenticate”方法并使用了ember-simple-auth-torii。

Now I can use Torii to get the authorization code from the user's Facebook account, send this code to my backend, authentify the user and generate an access token that will be managed by ember-simple-auth like an oauth2 token. 现在我可以使用Torii从用户的Facebook帐户获取授权代码,将此代码发送到我的后端,对用户进行身份验证并生成一个访问令牌,该令牌将由ember-simple-auth管理,如oauth2令牌。

Here is the code : 这是代码:

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

My backend is in Rails and uses Doorkeeper to manage the access_token and Devise. 我的后端在Rails中,并使用Doorkeeper来管理access_token和Devise。 I overrode Doorkeeper::TokensController to pass the user_id with the token and manage the facebook's authorization code if any (that code should be refactored) : 我重写了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

Here is the code I use in the initializer doorkeeper.rb to authentify the user 这是我在初始化程序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

I spent a few days trying to figure out how to make it work with torii and ended up ditching it for my own authenticator. 我花了几天时间试图弄清楚如何使它与torii一起工作,并最终放弃了我自己的身份验证器。 This is a mix of code from torii and ember-simple-auth so it's not the cleanest, and probably doesn't handle all the edge cases. 这是来自torii和ember-simple-auth的代码的混合,因此它不是最干净的,并且可能无法处理所有边缘情况。 It basically extends the ember-simple-auth oauth2 authenticator and adds the custom code to pass the access token to the API. 它基本上扩展了ember-simple-auth oauth2身份验证器,并添加了自定义代码以将访问令牌传递给API。

app/lib/facebook-authenticator.js 应用程序/ 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')
});

I used this: 我用过这个:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM