Endpoint de Autorização
Basic Auth é uma forma de autenticação baseada no Protocolo HTTP ( é a forma mais simples).
O padrão do HTTP é a palavra "Basic", um espaço e um token base64.
Token base64
Log adicionado em src/routes/users.route.ts
console.log(req.headers['authorization']);
// Return: Basic YWRtaW46cGFzc3dvcmQ=
// Base64 Decode: admin:password
Com a ferramenta Base64 Decode decodificamos o token base64.O padrão do token base64 é "usuário" + ":" + "senha".
Essa é a autenticação mais simples dentro do protocolo HTTP.
Autenticação
Na primeira conexão deve ser enviado "usuário" e "senha" para autenticação e o retorno será um token base64.Todas as outras requisições serão validadas/autenticadas com base nesse token base64.
Criar a Rota de Autenticação
Criar a rota de autenticação em src/routes/authorization.route.ts
import { Router, Request, Response, NextFunction } from 'express';
import ForbiddenError from '../models/errors/forbidden.error.model';
const authorizationRouter = Router();
authorizationRouter.post('/token', (req: Request, res: Response, next: NextFunction) => {
try{
const authorizationHeader = req.headers['authorization'];
if (!authorizationHeader) {
throw new ForbiddenError('Credenciais não informadas');
} else{
console.log(authorizationHeader);
}
}catch(error){
next(error);
}
});
export default authorizationRouter;
Criar um tipo de erro especifico para autenticação em src/models/errors/forbidden.error.model.ts
export default class ForbiddenError extends Error {
constructor(
public message: string,
public error?: any,
) {
super(message);
}
}
Adicionar o novo tipo de erro forbidden.error.model.ts no Handler Error em src/middlewares/error-handler.ts
import { Request, Response, NextFunction } from 'express';
import { StatusCodes } from 'http-status-codes';
import DatabaseError from "../models/errors/database.error.model";
import ForbiddenError from '../models/errors/forbidden.error.model';
function errorHandler(error: any, req: Request, res: Response, next: NextFunction){
if(error instanceof DatabaseError){
res.send(StatusCodes.BAD_REQUEST);
}else if(error instanceof ForbiddenError){
res.send(StatusCodes.FORBIDDEN);
}else{
res.send(StatusCodes.INTERNAL_SERVER_ERROR);
}
}
export default errorHandler;
Configurar o server src/index.ts para adicionar a nova rota criada em src/routes/authorization.route.ts
import express from 'express';
import usersRoute from './routes/users.route';
import statusRoute from './routes/status.route';
import errorHandler from './middlewares/error-handler';
import authorizationRouter from './routes/authorization.route';
// Instanciando o express
const app = express();
// ======================== Configurações
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(authorizationRouter);
// ======================== Configuração das Rotas
app.use(statusRoute);
app.use(usersRoute);
// ======================== Configurações dos Handlers
app.use(errorHandler);
// ======================== Inicialização do servidor (porta 3000)
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Enviar os dados de usuário e senha para autenticação
Refatorar a rota em src/routes/authorization.route.ts para incluir as validações e tratamentos dos dados do usuário.
import { Router, Request, Response, NextFunction } from 'express';
import ForbiddenError from '../models/errors/forbidden.error.model';
const authorizationRouter = Router();
authorizationRouter.post('/token', (req: Request, res: Response, next: NextFunction) => {
try{
const authorizationHeader = req.headers['authorization'];
if (!authorizationHeader) {
throw new ForbiddenError('Credenciais não informadas');
}
const [authorizationType, token] = authorizationHeader?.split(" "); // Basic YWRtaW46cGFzc3dvcmQ=
if(authorizationType !== 'Basic' || !token){
throw new ForbiddenError('Tipo de autenticação inválida');
}
const tokenContent = Buffer.from(token, 'base64').toString('utf-8');
const [userName, userPassword] = tokenContent.split(":");
if(!userName || !userPassword){
throw new ForbiddenError('Credenciais não preenchidas');
}
const user = await userRepository.byUserNameAndPassword(userName, userPassword);
}catch(error){
next(error);
}
});
export default authorizationRouter;
Coloque breakpoint na linha: const tokenContent = Buffer.from(token, 'base64').toString('utf-8'); Configure no Insomnia os dados para autenticação, Aba "Auth", opção "Basic Auth" e informe userName e password.
Teste executando o endpoint http://localhost:3000/token com o método POST.
Método para Buscar na Base de Dados
Criar o método byUserNameAndPassword() no src/repositories/users.repository.ts
async byUserNameAndPassword(userName: string, userPassword: string): Promise {
try{
const query = `
SELECT uuid, userName
FROM users
WHERE userName = $1 and userPassword = crypt($2, 'my_salt')`;
const values = [userName, userPassword];
const { rows } = await db.query<User>(query, values);
const [user] = rows;
return user || null;
}catch (error) {
console.log('ERRO: ', error);
throw new DatabaseError('Erro na consulta por userName e userPassword', error);
}
}