- Para utilizar os JSON Web Tokens na aplicação vamos instalar a biblioteca jsonwebtoken:
npm i jsonwebtoken@~8.5.1
- Antes de implementarmos o login precisaremos de um método para verificar que a senha digitada pelo usuário é igual à senha salva como hash no banco de dados. E para isso vamos utilizar o método compare do bcrypt. Mas o método compare utiliza um callback para o qual iremos primeiro criar o tipo CheckPasswordCallback. Crie esse tipo no arquivo de modelo user.ts:
- Repare que o callback é uma função que recebe como argumentos o erro, que se não existir será undefined e um valor booleano confirmando que as senhas são iguais.
// src/models/user.ts
import { database } from '../database'
import { DataTypes, Model, Optional } from 'sequelize'
import bcrypt from 'bcrypt'
type CheckPasswordCallback = (err: Error | undefined, isSame: boolean) => void
export interface UserAttributes {
id: number
// ...
- Também vamos adicionar o método checkPassword que iremos criar à interface UserInstance, assim o typescript saberá que todas as instâncias de user possuem esse método:
- Repare que o método checkPassword é uma função que recebe a senha que deve ser comparada com a senha salva e o callback que será executado após a comparação.
// src/models/user.ts
// ...
type CheckPasswordCallback = (err: Error | undefined, isSame: boolean) => void
// ...
export interface UserInstance extends Model<UserAttributes, UserCreationAttributes>, UserAttributes {
checkPassword: (password: string, callbackfn: CheckPasswordCallback) => void
}
// ...
- Como queremos implementar um método a nível de instância podemos utilizar o prototype do javascript para isso. Vamos criar o método checkPassword após a criação do modelo:
// src/models/user.ts
// ...
}, {
hooks: {
beforeSave: async (user) => {
if (user.isNewRecord || user.changed('password')) {
user.password = await bcrypt.hash(user.password.toString(), 10);
}
}
}
})
User.prototype.checkPassword = function (password: string, callbackfn: (err: Error | undefined, isSame: boolean) => void) {
bcrypt.compare(password, this.password, (err, isSame) => {
if (err) {
callbackfn(err, false)
} else {
callbackfn(err, isSame)
}
})
}
- Agora que temos um método para comprar as senhas vamos precisar de um método para gerar um token para o usuário que permitirá que a nossa aplicação reconheça o seu login. Crie um serviço para o jwt chamado jwt-service.ts e adicione o método signPayload:
- Obs.: O JWT necessita de uma chave secreta para assinar o payload do token. Por enquanto usaremos um valor hard-coded, mas eventualmente faremos isso de uma forma mais correta e segura.
// src/services/jwtService.ts
import jwt from 'jsonwebtoken'
const secret = 'chave-jwt'
export const jwtService = {
signToken: (payload: string | object | Buffer, expiration: string) => {
return jwt.sign(payload, secret, { expiresIn: expiration })
}
}
- Agora só resta criar o método de login no controlador. Ele deverá obter email e senha digitados pelo usuário no corpo da requisição, verificar se o usuário existe, comparar a senha digitada com a do banco de dados e então assinar o payload e retornar o token:
// src/controllers/authController.ts
import { Request, Response } from 'express'
import { jwtService } from '../services/jwtService'
// ...
},
// POST /auth/login
login: async (req: Request, res: Response) => {
const { email, password } = req.body
try {
const user = await userService.findByEmail(email)
if (!user) {
return res.status(401).json({ message: 'E-mail não registrado' })
}
user.checkPassword(password, (err, isSame) => {
if (err) {
return res.status(400).json({ message: err.message })
}
if (!isSame) {
return res.status(401).json({ message: 'Senha incorreta' })
}
const payload = {
id: user.id,
firstName: user.firstName,
email: user.email
}
const token = jwtService.signPayload(payload, '7d')
return res.json({ authenticated: true, user, token })
})
} catch (err) {
if (err instanceof Error) {
return res.status(400).json({ message: err.message })
}
}
}
}
- Por fim, crie a rota e adicione o método do controlador:
// src/routes.ts
// ...
router.post('/auth/register', authController.register)
router.post('/auth/login', authController.login)
// ...