Workflow for Ember-simple-auth, Torii and Facebook Oauth2

后端 未结 3 2018
闹比i
闹比i 2020-12-13 07:45

After my previous question about ember-simple-auth and torii, I successfully authenticate my users with their Facebook accounts.

But currently, torii\'s provider fac

相关标签:
3条回答
  • 2020-12-13 08:25

    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);
          });
        });
      }
    });
    
    0 讨论(0)
  • 2020-12-13 08:29

    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. 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. It basically extends the ember-simple-auth oauth2 authenticator and adds the custom code to pass the access token to the API.

    app/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')
    });
    
    0 讨论(0)
  • 2020-12-13 08:36

    I finally wrote my own authenticator as Beerlington suggested. 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.

    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.

    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. 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) :

    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.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 讨论(0)
提交回复
热议问题