2014-09-13 9 views
17

Dopo my previous question about ember-simple-auth and torii, ho autenticato correttamente i miei utenti con i loro account Facebook.Flusso di lavoro per Ember-simple-auth, Torii e Facebook Oauth2

Attualmente, il fornitore di torii facebook-oauth2 sta restituendo un codice di autorizzazione da Facebook; quando la promessa si risolve, invio questo codice di autorizzazione al mio back-end dove eseguo una richiesta contro Facebook per ottenere l'id e l'email dell'utente: quindi autenticherò l'utente sul mio backend, generando un token di accesso specifico e inviando indietro alla mia applicazione ember.

Codice cliente:

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

Il problema è: la sessione del tizzone-simple-auth è contrassegnato come autenticato quando la promessa del autenticazione si risolve e poi l'applicazione reindirizza allo specifico percorso autenticato. Ma in questo caso la sessione dovrebbe essere autenticata quando il mio backend restituisce il "vero" access_token.

C'è un modo per gestire questo flusso di lavoro con ember-simple-auth-torii o devo scrivere il mio autenticatore?

+0

può cortesemente dirmi come si convalida il token ricevuto da Facebook sul tuo server personalizzato? – Ajey

+0

Vedere la risposta accettata: nel tokens_controller # create utilizzo la gemma Koala per ottenere access_token con auth_code ricevuto da Facebook. – obo

+0

Sto usando il nodo sul mio back-end. Qualsiasi libreria di nodi per fare questo? – Ajey

risposta

17

finalmente ho scritto il mio autenticatore come suggerito Beerlington. Ma do anche ai miei utenti un modo per autenticarsi usando login/password, quindi ho scavalcato l'autenticatore ember-simple-auth-oauth2, cambiando solo il metodo "authenticate" e ho usato ember-simple-auth-torii.

Ora posso utilizzare Torii per ottenere il codice di autorizzazione dall'account Facebook dell'utente, inviare questo codice al mio backend, autenticare l'utente e generare un token di accesso che sarà gestito da ember-simple-auth come un token oauth2.

Ecco il codice:

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

mio backend è in Rails e usa Doorkeeper per gestire l'access_token e mettere a punto. Ho calpestato Doorkeeper :: TokensController per passare l'user_id con il token e gestire codice di autorizzazione del facebook eventuale (che il codice dovrebbe essere refactoring):

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 

Ecco il codice che uso nel doorkeeper.rb inizializzazione per Authentify l'utente

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

C'è bisogno di sign_in (: user, user) qui? Che cosa sta facendo se comunque stai restituendo un token di accesso valido? Ho provato questo stesso identico metodo usando devise/doorkeeper sul mio server, ma il metodo sign_in genera sempre un'eccezione e causa una risposta non autenticata 401 – cjroebuck

+0

Il metodo viene utilizzato per aggiungere informazioni all'utente come last_sign_in_ip, last_sign_in_at, sign_in_count, ecc ... Ho modificato la mia risposta con la configurazione che utilizzo per Doorkeeper. – obo

+0

Yeh, è ​​bello grazie per l'aggiornamento. Mi sono appena reso conto che il motivo per cui stavo ottenendo i 401 era perché stavo usando: confermabile nella configurazione di configurazione! – cjroebuck

1

Ho trascorso alcuni giorni cercando di capire come farlo funzionare con torii e ho finito per abbandonarlo per il mio autenticatore. Questo è un mix di codice di torii ed ember-simple-auth quindi non è il più pulito, e probabilmente non gestisce tutti i casi limite. In pratica, estende l'autenticatore ober-2 di ember-simple-auth e aggiunge il codice personalizzato per passare il token di accesso all'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

Bel metodo.Ho anche scritto il mio autenticatore che estende Oauth2Authenticator ma utilizzo ancora il codice di ToriiAuthenticator per recuperare il codice di autorizzazione dal provider (vedi la mia risposta). – obo

0

ho usato questo:

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