Contents

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.

/pt/images/nest-cli-npm.jpg

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.