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