ember authorizers facebook authentication ember.js ember-simple-auth

facebook - ember authorizers



Flujo de trabajo para Ember-simple-auth, Torii y Facebook Oauth2 (3)

Finalmente escribí mi propio autenticador como sugirió Beerlington. Pero también les doy a mis usuarios una forma de autenticarse usando el inicio de sesión / contraseña, así que anulé el autenticador de ember-simple-auth-oauth2, cambiando solo el método de "autentificación" y usé ember-simple-auth-torii.

Ahora puedo usar Torii para obtener el código de autorización de la cuenta de Facebook del usuario, enviar este código a mi backend, autenticar al usuario y generar un token de acceso que será administrado por ember-simple-auth como un token oauth2.

Aquí está el código:

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

Mi backend está en Rails y usa Doorkeeper para administrar access_token y Devise. Anulé Doorkeeper :: TokensController para pasar el user_id con el token y administrar el código de autorización de Facebook, si hubiera alguno (ese código debería ser refactorizado):

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

Aquí está el código que uso en el inicializador doorkeeper.rb para autenticar al usuario

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

Después de mi pregunta anterior acerca de ember-simple-auth y torii , autentico exitosamente a mis usuarios con sus cuentas de Facebook.

Pero actualmente, el proveedor de torii facebook-oauth2 está devolviendo un código de autorización de Facebook; cuando la promesa se resuelve, envío este código de autorización a mi backend donde realizo una solicitud contra Facebook para obtener la identificación y el correo electrónico del usuario: luego autentico al usuario en mi backend, generando un token de acceso específico y enviándolo de vuelta a mi aplicación de brasa.

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

El problema es: la sesión de ember-simple-auth está marcada como autenticada cuando la promesa de la autenticación se resuelve y luego la aplicación redirige a la ruta autenticada específica. Pero en este caso, la sesión debe autenticarse cuando mi backend devuelva el access_token "real".

¿Hay alguna manera de administrar este flujo de trabajo con ember-simple-auth-torii o debo escribir mi propio autenticador?


Pasé unos días tratando de averiguar cómo hacer que funcionara con torii y terminé abandonándolo para mi propio autenticador. Esta es una mezcla de código de torii y ember-simple-auth, por lo que no es la más limpia y probablemente no maneja todos los casos de borde. Básicamente, amplía el autenticador auth-simple-auth oauth2 y agrega el código personalizado para pasar el token de acceso a la 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'') });


Utilicé esto:

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