Node

Microserviço para Autenticação e Gerenciamento de Usuários - REST API

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);
                                }  
                            }
                        

Debug no Visual Studio Code

Ao passar o mouse por cima das variáveis irá aparecer o valor correspondente.