Registro y autorización de usuarios en Node.js con Twitter y Facebook
¿Ves alguna errata o quieres modificar algo? Haz una Pull Request
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.
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:
Y ya está, el flujo de la aplicación sería el siguiente: Primero nos pide autenticarnos con Twitter o Facebook
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
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
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 :)