Artigo original: Learn how to handle authentication with Node using Passport.js
Escrito por: Antonio Erdeljac
Ajude o autor lendo o texto no original: ORIGINAL (em inglês)
Neste artigo, você aprenderá a lidar com a autenticação para um servidor do Node usando o Passport.js. O artigo não tratará da autenticação no front-end. Use-o para configurar a autenticação no back-end (geração de token para cada usuário e proteção das rotas).
Lembre-se de que, se tiver problemas em alguma das etapas, é possível consultar a qualquer momento o repositório do GitHub.
Neste artigo, ensinarei o seguinte:
- Tratamento de rotas protegidas
- Tratamento de tokens do JWT
- Tratamento de respostas não autorizadas
- Criação de uma API básica
- Criação de modelos e esquemas
Introdução
O que é o Passport.js?
O Passport é um middleware de autenticação para o Node.js. Por ser extremamente flexível e modular, o Passport pode ser colocado sem problemas em qualquer aplicação para a web baseada no Express. Um conjunto extenso de estratégias dá suporte à autenticação usando um nome de usuário e senha, Facebook, Twitter e muito mais. Saiba mais sobre o Passport aqui (documentação em inglês).
Tutorial
Criação do servidor do Node desde o início
Crie um diretório com este arquivo "app.js" dentro dele:
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const session = require('express-session');
const cors = require('cors');
const mongoose = require('mongoose');
const errorHandler = require('errorhandler');
//Configure mongoose's promise to global promise
mongoose.promise = global.Promise;
//Configure isProduction variable
const isProduction = process.env.NODE_ENV === 'production';
//Initiate our app
const app = express();
//Configure our app
app.use(cors());
app.use(require('morgan')('dev'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({ secret: 'passport-tutorial', cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false }));
if(!isProduction) {
app.use(errorHandler());
}
//Configure Mongoose
mongoose.connect('mongodb://localhost/passport-tutorial');
mongoose.set('debug', true);
//Error handlers & middlewares
if(!isProduction) {
app.use((err, req, res) => {
res.status(err.status || 500);
res.json({
errors: {
message: err.message,
error: err,
},
});
});
}
app.use((err, req, res) => {
res.status(err.status || 500);
res.json({
errors: {
message: err.message,
error: {},
},
});
});
app.listen(8000, () => console.log('Server running on http://localhost:8000/'));
Instalaremos o nodemon para facilitar o desenvolvimento.
npm install -g nodemon
Em seguida, vamos executar nosso "app.js" com ele.
$ nodemon app.js

Criação do modelo de usuário
Crie uma pasta chamada "models" e um arquivo "Users.js" dentro dessa pasta. É aí que definiremos nosso "UsersSchema", ou esquema de usuário. Usaremos JWT
e Crypto
para gerar o hash
e o salt
da string password
recebida. Isso será usado mais tarde para a validação do usuário.
const mongoose = require('mongoose');
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
const { Schema } = mongoose;
const UsersSchema = new Schema({
email: String,
hash: String,
salt: String,
});
UsersSchema.methods.setPassword = function(password) {
this.salt = crypto.randomBytes(16).toString('hex');
this.hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex');
};
UsersSchema.methods.validatePassword = function(password) {
const hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex');
return this.hash === hash;
};
UsersSchema.methods.generateJWT = function() {
const today = new Date();
const expirationDate = new Date(today);
expirationDate.setDate(today.getDate() + 60);
return jwt.sign({
email: this.email,
id: this._id,
exp: parseInt(expirationDate.getTime() / 1000, 10),
}, 'secret');
}
UsersSchema.methods.toAuthJSON = function() {
return {
_id: this._id,
email: this.email,
token: this.generateJWT(),
};
};
mongoose.model('Users', UsersSchema);

Vamos adicionar nosso modelo recém-criado ao "app.js".
Adicione a linha seguinte ao "app.js" após configurar o Mongoose
:
require('./models/Users');

Configuração do Passport
Crie uma pasta "config" com o arquivo "passport.js" dentro dela:
const mongoose = require('mongoose');
const passport = require('passport');
const LocalStrategy = require('passport-local');
const Users = mongoose.model('Users');
passport.use(new LocalStrategy({
usernameField: 'user[email]',
passwordField: 'user[password]',
}, (email, password, done) => {
Users.findOne({ email })
.then((user) => {
if(!user || !user.validatePassword(password)) {
return done(null, false, { errors: { 'email or password': 'is invalid' } });
}
return done(null, user);
}).catch(done);
}));
Nesse arquivo, usamos o método validatePassword
que definimos no modelo de usuário
. Com base no resultado, retornamos um resultado diferente a partir da LocalStrategy
(estratégia local) do Passport.

Vamos conectar o "passport.js" ao arquivo "app.js". Adicione a linha a seguir abaixo de todos os models
:
require('./config/passport');

Roas e opções de autenticação
Crie uma pasta chamada "routes" com o arquivo "auth.js" dentro dela.
Nesse arquivo, usaremos a função getTokenFromHeaders
para obter um token do JWT que será enviado do lado do client nos cabeçalhos (headers) da solicitação. Também criaremos um objeto auth
com as propriedades optional
e required
. Nós as usaremos mais tarde em nossas rotas.
const jwt = require('express-jwt');
const getTokenFromHeaders = (req) => {
const { headers: { authorization } } = req;
if(authorization && authorization.split(' ')[0] === 'Token') {
return authorization.split(' ')[1];
}
return null;
};
const auth = {
required: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders,
}),
optional: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders,
credentialsRequired: false,
}),
};
module.exports = auth;
Na mesma pasta "routes", criamos um arquivo "index.js":
const express = require('express');
const router = express.Router();
router.use('/api', require('./api'));
module.exports = router;
Agora, precisamos de uma pasta "api" dentro da pasta "routes", com outro arquivo "index.js" dentro dela.
const express = require('express');
const router = express.Router();
router.use('/users', require('./users'));
module.exports = router;

Agora, criaremos o arquivo "users.js" que solicitamos em "api/index.js".
Primeiro, criaremos uma rota de autorização opcional "/"
, que será usada para a criação de um modelo (registro).
router.post('/', auth.optional, (req, res, next) ...
Depois disso, criaremos outra rota de autorização opcional "/login"
. Ela será usada para ativar nossa configuração do Passport e validar uma senha recebida com e-mail.
router.post('/login', auth.optional, (req, res, next) ...
Por fim, criaremos uma rota de autorização obrigatória, que será usada para retornar o usuário que está logado atualmente. Somente usuários logados (usuários com o token enviado com sucesso por meio dos cabeçalhos de solicitação) têm acesso a essa rota.
router.get('/current', auth.required, (req, res, next) ...
const mongoose = require('mongoose');
const passport = require('passport');
const router = require('express').Router();
const auth = require('../auth');
const Users = mongoose.model('Users');
//POST new user route (optional, everyone has access)
router.post('/', auth.optional, (req, res, next) => {
const { body: { user } } = req;
if(!user.email) {
return res.status(422).json({
errors: {
email: 'is required',
},
});
}
if(!user.password) {
return res.status(422).json({
errors: {
password: 'is required',
},
});
}
const finalUser = new Users(user);
finalUser.setPassword(user.password);
return finalUser.save()
.then(() => res.json({ user: finalUser.toAuthJSON() }));
});
//POST login route (optional, everyone has access)
router.post('/login', auth.optional, (req, res, next) => {
const { body: { user } } = req;
if(!user.email) {
return res.status(422).json({
errors: {
email: 'is required',
},
});
}
if(!user.password) {
return res.status(422).json({
errors: {
password: 'is required',
},
});
}
return passport.authenticate('local', { session: false }, (err, passportUser, info) => {
if(err) {
return next(err);
}
if(passportUser) {
const user = passportUser;
user.token = passportUser.generateJWT();
return res.json({ user: user.toAuthJSON() });
}
return status(400).info;
})(req, res, next);
});
//GET current route (required, only authenticated users have access)
router.get('/current', auth.required, (req, res, next) => {
const { payload: { id } } = req;
return Users.findById(id)
.then((user) => {
if(!user) {
return res.sendStatus(400);
}
return res.json({ user: user.toAuthJSON() });
});
});
module.exports = router;

Vamos adicionar nossa pasta "routes" ao "app.js". Adicione esta linha abaixo do require
do Passport:
app.use(require('./routes'));

Teste das rotas
Usaremos o Postman para enviar solicitações ao nosso servidor.
Nosso servidor aceita o body (corpo da solicitação) seguinte:
{
"user": {
"email": String,
"password": String
}
}
Criação de uma solicitação de POST para a criação de um usuário
body
do teste:

Resposta:
{
"user": {
"_id": "5b0f38772c46910f16a058c5",
"email": "erdeljac.antonio@gmail.com",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImVyZGVsamFjLmFudG9uaW9AZ21haWwuY29tIiwiaWQiOiI1YjBmMzg3NzJjNDY5MTBmMTZhMDU4YzUiLCJleHAiOjE1MzI5MDgxNTEsImlhdCI6MTUyNzcyNDE1MX0.4TWc1TzY6zToHx_O1Dl2I9Hf9krFTqPkNLHI5U9rn8c"
}
}
Agora, usaremos este token e o adicionaremos aos nossos "Headers" na configuração do Postman.

Vamos, agora, testar nossa rota acessível somente por autorização.
Criação de uma solicitação de GET para retornar o usuário logado atualmente
URL de solicitação:
GET http://localhost:8000/api/users/current
Resposta:
{
"user": {
"_id": "5b0f38772c46910f16a058c5",
"email": "erdeljac.antonio@gmail.com",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImVyZGVsamFjLmFudG9uaW9AZ21haWwuY29tIiwiaWQiOiI1YjBmMzg3NzJjNDY5MTBmMTZhMDU4YzUiLCJleHAiOjE1MzI5MDgzMTgsImlhdCI6MTUyNzcyNDMxOH0.5UnA2mpS-_puPwwxZEb4VxRGFHX6qJ_Fn3pytgGaJT0"
}
}
Vamos tentar fazer o mesmo sem o token nos "Headers".
Resposta:

Conclusão
Agradeço a leitura deste tutorial. Se perceber erros, informe-os ao autor. Se tiver problemas em qualquer uma das etapas, consulte este repositório do GitHub.
Você também pode entrar em contato com o autor por: