Como criar uma API REST Nest.js segura usando JWT e MongoDB
O Express.js é uma excelente ferramenta para a construção de interfaces de programação de aplicações (APIs) RESTful fiáveis e seguras, embora não ofereça uma arquitetura predeterminada. A simplicidade do seu design permite que os programadores abordem elementos fundamentais como o encaminhamento, a organização do código e os protocolos de segurança através da implementação manual ou da utilização de middleware e bibliotecas disponíveis.
O Nest.js, que é construído com base no Express.js e no Node.js, apresenta um nível mais elevado de abstração que inclui uma estrutura bem definida, uma metodologia eficaz para organizar o código e especificidades de implementação simplificadas. Fundamentalmente, o Nest.js oferece um design mais sistemático para criar APIs e serviços back-end eficientes e inexpugnáveis.
Configurando um projeto Nest.js
Siga este guia passo a passo para configurar a CLI do Nest.js globalmente em seu sistema usando o Node.js. Primeiramente, certifique-se de ter o Node.js instalado em seu computador. Caso contrário, transfira e instale-o a partir do sítio Web oficial em
https://nodejs.org/en/
. Após a instalação, abra um terminal ou uma janela de prompt de comando e execute o seguinte comando:javanpm install -g @nestjs/cliIsso instalará a CLI do Nest.js globalmente em sua máquina. Quando a instalação estiver concluída, você pode começar a criar novos projetos com o seguinte comando:csharpnest new project_nameSubstitua
project\_name
pelo nome desejado do seu novo projeto. O comando acima cria uma nova pasta com o nome especificado
npm i -g @nestjs/cli
Após a conclusão do processo de instalação, por favor, inicie a criação de um novo projeto executando o seguinte comando:
nest new nest-jwt-api
O passo seguinte consiste em selecionar um gestor de pacotes para instalar as dependências necessárias. Neste caso, vamos utilizar o npm, que é o gerenciador de pacotes do Node. Depois de escolher o npm, a interface de linha de comando (CLI) do Nest.js criará um projeto Nest.js fundamental e instalará todos os ficheiros de configuração essenciais, juntamente com o conjunto inicial de dependências necessárias para o funcionamento da aplicação.
Após a criação do projeto, vá para o diretório do projeto e inicie o servidor de desenvolvimento.
cd nest-jwt-api
npm run start
Por fim, execute o seguinte comando para instalar os pacotes necessários para este projeto.
npm install mongodb mongoose @nestjs/mongoose @types/bcrypt bcrypt jsonwebtoken @nestjs/jwt
O código-fonte do projeto acima referido está acessível através do repositório GitHub especificado.
Configurar conexão de banco de dados MongoDB
O processo para estabelecer um banco de dados MongoDB envolve configurá-lo localmente ou configurar um cluster MongoDB por meio de um provedor de serviços em nuvem. Assim que a base de dados estiver configurada, obtenha a cadeia URI de ligação à base de dados, crie um ficheiro
.env
no diretório raiz do projeto e copie e cole a cadeia de ligação no ficheiro.
MONGO_URI="connection string"
Para integrar o Mongoose à nossa aplicação, precisamos atualizar o arquivo
app.module.ts
localizado no diretório
src
. A configuração do Mongoose deve incluir as importações e definições necessárias que nos permitam utilizar as suas funcionalidades na nossa aplicação.
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserAuthModule } from './user-auth/user-auth.module';
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: '.env',
isGlobal: true,
}),
MongooseModule.forRoot(process.env.MONGO_URI),
UserAuthModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
o ConfigModule para lidar com as configurações ambientais, o MongooseModule para configurar uma conexão com o MongoDB e o UserAuthModule para gerenciar a autenticação do usuário. De notar que
Criação do módulo de autenticação de utilizadores
O processo de garantir a preservação de um código fonte imaculado e ordenado envolve a implementação de um módulo para autenticação de utilizadores através da execução de uma instrução específica.
nest g module user-auth
O utilitário Nest.js Command Line Interface (CLI) automatiza o processo de criação dos ficheiros de módulo necessários e actualiza o ficheiro app.module.ts em conformidade, fazendo os ajustes necessários relativos ao módulo de autenticação do utilizador.
Em alternativa, pode optar-se por criar conscientemente os ficheiros de configuração do projeto primário por conta própria; no entanto, o instrumento de linha de comandos agiliza este processo, tratando da geração dos componentes necessários e das actualizações do ficheiro app.module.ts prontamente.
Criar um esquema de utilizador
Na pasta
user-auth
recentemente criada, situada no diretório
src
, gere um novo documento
schemas/user-auth.schema.ts
e incorpore o código subsequente para produzir um esquema Mongoose que represente o modelo
User
.
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
@Schema({ timestamps: true })
export class User {
@Prop()
username: string;
@Prop()
password: string;
}
export type UserDocument = User & Document;
export const UserSchema = SchemaFactory.createForClass(User);
Criando o serviço de autenticação de usuário
nest g service user-auth
O procedimento acima mencionado deve gerar um documento “user-auth.service.ts” dentro da pasta “user-auth”. Abra esse ficheiro e actualize-o com o código que se segue.
⭐ Primeiro, faça as seguintes importações.
import { Injectable, NotFoundException, Logger, UnauthorizedException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './schemas/user-auth.schema';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
Executar o processo de criação de um utilitário consolidado para autenticação de utilizadores, designado por “UserAuthService”, que engloba operações relativas à criação de contas, início de sessão e obtenção de informações sobre todos os utilizadores do sistema.
@Injectable()
export class UserAuthService {
private readonly logger = new Logger(UserAuthService.name);
constructor( @InjectModel(User.name) private userModel: Model<User>, private jwtService: JwtService) {}
async registerUser(username: string, password: string): Promise<{ message: string }> {
try {
const hash = await bcrypt.hash(password, 10);
await this.userModel.create({ username, password: hash });
return { message: 'User registered successfully' };
} catch (error) {
throw new Error('An error occurred while registering the user');
}
}
async loginUser(username: string, password: string): Promise<string> {
try {
const user = await this.userModel.findOne({ username });
if (!user) {
throw new NotFoundException('User not found');
}
const passwordMatch = await bcrypt.compare(password, user.password);
if (!passwordMatch) {
throw new UnauthorizedException('Invalid login credentials');
}
const payload = { userId: user._id };
const token = this.jwtService.sign(payload);
return token;
} catch (error) {
console.log(error);
throw new UnauthorizedException('An error occurred while logging in');
}
}
async getUsers(): Promise<User[]> {
try {
const users = await this.userModel.find({});
return users;
} catch (error) {
this.logger.error(`An error occurred while retrieving users: ${error.message}`);
throw new Error('An error occurred while retrieving users');
}
}
}
O
UserAuthService
é uma classe que engloba a funcionalidade de registo de utilizadores, início de sessão e acesso a informações sobre os utilizadores. Esta implementação utiliza o
userModel
como interface para comunicar com a base de dados e realizar operações como o hashing da palavra-passe durante o registo, a verificação das credenciais de início de sessão e a geração do JSON Web Token (JWT) após uma autenticação bem sucedida.
Implementação do Authentication Guard
Para garantir a confidencialidade de bens sensíveis, é imperativo restringir o acesso apenas a utilizadores autorizados. Isso pode ser feito implementando um mecanismo de segurança que requer a inclusão de um JSON Web Token (JWT) legítimo em todas as solicitações de API subsequentes direcionadas a pontos de extremidade protegidos, como ousersroute. No diretóriouser-authdirectory, crie um novo arquivoauth.guard.ts e incorpore o seguinte código nele:
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
import { secretKey } from './config';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException();
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: secretKey.secret,
});
request['user'] = payload;
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
A implementação desse código segue as diretrizes descritas na documentação oficial, empregando uma proteção para proteger as rotas designadas, restringindo o acesso apenas a usuários autenticados que possuem um JSON Web Token (JWT) legítimo.
export const secretKey = {
secret: 'SECTRET VALUE.',
};
A chave de assinatura confidencial serve como um token de autenticação para JWTs, sendo imperativo salvaguardar o seu valor para impedir o acesso não aprovado e manter a veracidade dos JWTs.
Definir o controlador da API
É necessária a implementação de um controlador para gerir os pontos finais da interface de programação de aplicações (API) relativos à autenticação do utilizador.
nest g controller user-auth
Insira o código fornecido no ficheiro
user-auth.controller.ts
e certifique-se de que contém a lógica necessária para gerir o registo do utilizador, o início de sessão e a recuperação de dados. Além disso, incorpore o decorador
UseGuards(AuthGuard)
para restringir o acesso não autorizado ao ponto de extremidade
getUsers
.
Atualizar o ficheiro user-auth.module.ts
Atualizar o ficheiro user-auth.module.ts para configurar os componentes necessários para a autenticação do utilizador de acordo com as actualizações efectuadas no projeto.
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { UserAuthController } from './user-auth.controller';
import { UserAuthService } from './user-auth.service';
import { MongooseModule } from '@nestjs/mongoose';
import { UserSchema } from './schemas/user-auth.schema';
import { secretKey } from './config';
@Module({
imports: [
MongooseModule.forFeature([{ name: 'User', schema: UserSchema }]),
JwtModule.register({
secret: secretKey.secret,
signOptions: { expiresIn: '1h' },
}),
],
controllers: [UserAuthController],
providers: [UserAuthService],
})
export class UserAuthModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
}
}
Por fim, inicie o servidor de desenvolvimento executando os comandos apropriados no terminal e utilize o Postman para verificar a funcionalidade dos pontos de extremidade da API.
npm run start
Criando APIs REST Nest.js seguras
É necessária uma estratégia abrangente para a criação de APIs REST Nest.js seguras que se estenda além da simples dependência de JWTs para autenticação e autorização. A implementação de outras medidas de segurança é igualmente vital.
Ao longo de todo o processo de desenvolvimento de uma API, é imperativo atribuir uma elevada prioridade à segurança, de modo a proteger os sistemas de backend de potenciais ameaças.