/ JavaScript

Registro y autorización de usuarios en Node.js con Twitter y Facebook

El pasado viernes me ofrecieron impartir una clase BONUS en la plataforma de estudiantes de los cursos de Mejorando.la.

El tema que elegimos fue como implementar un sistema de login de usuarios utilizando plataformas sociales (Twitter y Facebook) y empleando Node.js en el Backend.

Esta fue la presentación que utilicé en la exposición.

Y a continuación explico a modo de tutorial los pasos para implementar este sistema en un proyecto que tengamos.

Lo primero de todo, necesitamos tener Node.js y MongoDB instalados en nuestro equipo. Node es el backend que utlizaremos y MongoDB es una base de datos no relacional, basada en documentos JSON. Podemos utlizar cualquier otra base de datos (por ejemplo, MySQL o cualquier otra). Yo lo hago con Mongo porque me parece más sencillo y porque esta basada en JavaScript del cual soy un talifán :P

A continuación creamos una estructura base de aplicación con el framework Express. De esta forma nos crea un “esqueleto” con archivos y carpetas de una especie de “hola mundo” con Express.

$ sudo npm install -g express
$ express passport-example
$ cd passport-example
$ npm install

Instalamos las dependencias que vamos a utilizar. Mongoose es el driver que nos ayuda a conectarnos a MongoDB e implementar esquemas de los modelos que salvemos en la base de datos. Passport es la librería que nos permite agilizar el proceso de autenticado y registro de usuarios con Node.js. Passport-twitter y Passport-facebook son las librerías específicas para el login con Twitter y Facebook ya que podemos utilizar Passport sin redes sociales implementando un método local con Passport-Local.

Con el prefijo –-save logramos que las dependencias se queden grabadas en el package.json

$ npm install --save mongoose
$ npm install --save passport
$ npm install --save passport-twitter
$ npm install --save passport-facebook

Creamos un modelo usuario /models/user.js donde indicaremos que datos vamos a querer almacenar en la base de datos para nuestros usuarios. En este ejemplo vamos a salvar el nombre, el proveedor, un ID, la foto del usuario y un campo dónde almacenaremos la fecha en la que el usuario se registró en nuestra aplicación.

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var UserSchema = new Schema({
	name: String,
	provider: String,
	provider_id: {type: String, unique: true},
	photo: String,
	createdAt: {type: Date, default: Date.now}
});

var User = mongoose.model('User', UserSchema);

Configuramos Passport /passport.js* importando las librerías que utilizamos y las funciones que nos permiten el login.

Con seriealizeUser y deserializeUser logramos que el objeto usuario quede almacenado en la sesión de la aplicación y asi poder utilizarlo a lo largo de ella.

Con TwitterStrategy y FacebookStrategy utilizamos las estrategias de autenticación que nos proporciona Passport, les pasamos como parámetros los API Key y API secret que nos dan las plataformas cuando registramos una aplicación en ellas, y nos devuelven varios objetos, entre ellos el objeto profile que contiene toda la información del usuario que devuelve Twitter o Facebook y del que podemos sacar los atributos que queramos para nuestra aplicación (nombre, ID, foto, etc..)

var mongoose = require('mongoose');
var User = mongoose.model('User');
var TwitterStrategy = require('passport-twitter').Strategy;
var FacebookStrategy = require('passport-facebook').Strategy;

module.exports = function(passport) {

     passport.serializeUser(function(user, done) {
		done(null, user);
	});

     passport.deserializeUser(function(obj, done) {
		done(null, obj);
	});
     passport.use(new TwitterStrategy({
		consumerKey: 'TWITTER_CONSUMER_KEY',
		consumerSecret: 'TWITTER_CONSUMER_SECRET',
		callbackURL: '/auth/twitter/callback'
	}, function(accessToken, refreshToken, profile, done) {
		User.findOne({provider_id: profile.id}, function(err, user) {
			if(err) throw(err);
			if(!err && user!= null) return done(null, user);

			var user = new User({
				provider_id: profile.id,
				provider: profile.provider,
				name: profile.displayName,
				photo: profile.photos[0].value
			});
			user.save(function(err) {
				if(err) throw err;
				done(null, user);
			});
		});
	}));
     passport.use(new FacebookStrategy({
		clientID: 'FACEBOOK_APP_CLIENT_ID',
		clientSecret: 'FACEBOOK_APP_SECRET_ID',
		callbackURL: '/auth/twitter/callback'
	}, function(accessToken, refreshToken, profile, done) {
		User.findOne({provider_id: profile.id}, function(err, user) {
			if(err) throw(err);
			if(!err && user!= null) return done(null, user);

			var user = new User({
				provider_id: profile.id,
				provider: profile.provider,
				name: profile.displayName,
				photo: profile.photos[0].value
			});
			user.save(function(err) {
				if(err) throw err;
				done(null, user);
			});
		});
	}));

Una buena práctica es mantener las API Keys separadas del código fuente que subimos al repositorio, en un archivo config.js (o en las variables de entorno) que luego importamos desde donde lo necesitemos.

var config = {
  twitter: {
    key: TWITTER_API_KEY€,
    secret: TWITTER_API_SECRET
  },
  facebook: {
    id: FACEBOOK_APP_ID,
    secret: FACEBOOK_APP_SECRET
  }
};

module.exports = config;

El archivo principal de la aplicación Express y por tanto del servidor quedaría mas o menos así app.js

var mongoose = require('mongoose');
var passport = require('passport');
require('./models/user');
require('./passport')(passport);

mongoose.connect('mongodb://localhost/passport-example', 
  function(err, res) {
    if(err) throw err;
    console.log('Conectado con éxito a la BD');
});

app.use(express.cookieParser());
app.use(express.urlencoded());
app.use(express.json());
app.use(express.methodOverride());
app.use(express.session({ secret: 'secretkey' }));

// Configuración de Express
app.use(passport.initialize());
app.use(passport.session());
...
// Rutas de Passport
app.get('/logout', function(req, res) {
  req.logout();
  res.redirect('/');
});
app.get('/auth/twitter', passport.authenticate('twitter'));
app.get('/auth/facebook', passport.authenticate('facebook'));
app.get('/auth/twitter/callback', passport.authenticate('twitter',
  { successRedirect: '/', 
    failureRedirect: '/login' }));
app.get('/auth/facebook/callback', passport.authenticate('facebook',
  { successRedirect: '/', 
    failureRedirect: '/login' }));
...

exports.index = function(req, res){
  res.render('index', { 
    title: 'Passport-Example',
    user: req.user
  });
};

Esta sería la plantilla Jade que renderiza el index: /views/index.jade. Como véis le aplicamos una lógica dentro de la plantilla. Si existe el objeto usuario es que estamos logueados en la aplicación y por tanto le pedimos que nos muestre la foto y el nombre y un enlace a salir. En caso de que no exista el objeto usuario es que no estamos logueados, por tanto indicamos que nos muestre los enlaces al registro/login con Twitter y Facebook

if(user)
    ul
      li
        img(src="#{user.photo}")
      li Bienvenido #{user.name}
      li
        a(href='logout') Salir
else
    ul
      li
        a(href='auth/twitter') Login con Twitter
      li
        a(href='auth/facebook') Login con Facebook

Para crear una aplicación en Twitter podemos ir a http://dev.twitter.com y registrar una nueva aplicación. Indicamos el nombre y en los campos website y callback URL le ponemos example.com ya que no nos permite poner Localhost. Con facebook es similar, la URL es http://developers.facebook.com y en este caso si podemos indicarle que nuestra aplicación funciona en localhost.

Aquí están las capturas para ambas plataformas:

Screen Shot 2014-03-02 at 22.42.12.png

Screen Shot 2014-03-02 at 22.44.15.png

Screen Shot 2014-03-02 at 22.43.12.png

Screen Shot 2014-03-03 at 10.29.37.png

Screen Shot 2014-03-02 at 22.46.37.png

Screen Shot 2014-03-03 at 09.10.27.png

Y ya está, el flujo de la aplicación sería el siguiente: Primero nos pide autenticarnos con Twitter o Facebook

Screen Shot 2014-03-03 at 10.45.01.png

Si elegimos Twitter nos redirige a la página de autenticación con Twitter. En el caso de no estar logueados nos pediría el usuario y la contraseña de Twitter

Screen Shot 2014-03-03 at 10.45.05.png

Y si todo sale correctamente, nos redirige a nuestra aplicación con el usuario logueado, mostrándonos el nombre de usuario y la foto de perfil

Screen Shot 2014-03-03 at 10.45.09.png

Tenéis todo el código utilizado disponible y comentado en español en este respositorio de GitHub. Espero que os sirva en vuestros proyectos y desarrollos :)

Carlos Azaustre

Carlos Azaustre

CTO y Cofundador de Chefly. Formador en tecnologías web: JavaScript, Node, Firebase, React y Vue.

Leer más