Desenvolvimento Back-end Node

Autenticação: Protegendo Rotas com NestJS e Passport

Neste artigo vamos aprender a proteger rotas com o NestJS e a biblioteca passport, utilizando a estratégia de autenticação com Tokens JWT.

há 1 ano 4 meses

Formação Desenvolvedor Node Full-Stack
Conheça a formação em detalhes

No artigo Autenticação: Geração de Token JWT com NestJS e Passport aprendemos a gerar Tokens JWT utilizando o NestJS e a biblioteca Passport. Agora vamos dar continuidade em relação à autenticação com NestJS e Passport.

Vale ressaltar que este artigo será uma continuação do artigo citado acima, portanto, é importante que você tenha o ambiente e a aplicação exemplo funcionando. Você pode baixar a aplicação no repositório do GitHub.

Node.js - Fundamentos
Curso Node.js - Fundamentos
Conhecer o curso

Criando rota lista de usuários

Exemplificando, vamos criar uma rota onde há uma lista de usuários, esta rota irá retornar esta lista, porém, somente usuários autenticados terão acesso a essa rota. Para isto vamos criar o método findAll() dentro do users.service.ts, este método irá retornar um JSON com alguns usuários, conforme abaixo:

//users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
  ) {}
  create(createUserDto: CreateUserDto) {
    return this.userRepository.save(createUserDto);
  }

  findAll() {
    return {
      usuarios: [
        {
          nome: 'Paulo',
          endereco: 'av paulista',
          telefone: '1155555555',
        },
        {
          nome: 'Maria',
          endereco: 'av faria lima',
          telefone: '1155555580',
        },
        {
          nome: 'Samantha',
          endereco: 'av paulista',
          telefone: '1155555590',
        },
      ],
    };
  }
	
	async findOneByEmail(username: string) {
    return await this.userRepository.findOneBy({ email: username });
  }
}

Agora vamos criar a rota no users.controller.ts:

//users.controller.ts
import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { AuthGuard } from '@nestjs/passport';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  async create(@Body() createUserDto: CreateUserDto) {
    return await this.usersService.create(createUserDto);
  }

  @Get()
  @UseGuards(AuthGuard('jwt')) // Decorator responsável pelo Guard
  listarUsuarios() {
    return this.usersService.findAll();
  }
}

Criamos uma rota GET e retornamos a lista de usuários, porém há uma particularidade, o uso do decorator @UseGuards(), é necessário usar este decorator para passar os Guards que serão responsáveis por verificar as regras de autenticação e autorização desta rota.

TypeScript - Fundamentos
Curso TypeScript - Fundamentos
Conhecer o curso

Guards

O recurso “Guards” como o nome diz, são “Guardiões”, é o recurso que irá verificar se o usuário está autenticado para a rota em específico.

É importante saber que podemos criar guards específicos para cada necessidade. Neste caso o passport disponibiliza o AuthGuard, onde podemos apontar a estratégia que estamos utilizando, no caso JWT conforme exemplo acima.

Com a rota e os guards devidamente configurados precisamos atualizar o arquivo local.auth.ts. Neste momento é necessário fazer uma refatoração já que estamos utilizando a estratégia JWT para a autenticação. Devemos trocar o nome do arquivo para jwt-strategy.ts e o nome da classe para JwtStrategy. Em seguida, faremos algumas alterações conforme abaixo:

//jwt-strategy.ts
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { JwtPayload } from './jwt-payload';
import { UsersService } from 'src/users/users.service';
import { User } from 'src/users/entities/user.entity';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
  constructor(private usersService: UsersService) {
    super({
      secretOrKey: 'sua-chave',
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
    });
  }
  async validate(payload: JwtPayload): Promise<User> {
    const { email } = payload;
    const user = await this.usersService.findOneByEmail(email);
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}

Obs.: Confira onde estava a importação do LocalStrategy para atualizar para JwtStrategy.

JWT Strategy

Nós fizemos algumas alterações importantes, passamos como parâmetro a estratégia JWT no PassportStrategy. Vale salientar que devemos prestar atenção no import correto, o Strategy deve ser importado da biblioteca passport-jwt, caso seja importado de uma biblioteca referente a outra estratégia teremos um erro na autenticação.

No super(), temos três propriedades, secretOrKey que deve ser a mesma chave que utilizamos ao gerar o Token JWT, jwtFromRequest aqui é onde iremos passar o Token na requisição, utilizando padrão Bearer Token no Header, e por último para que o Token expire no tempo determinado na criação do mesmo.

Devemos passar o payload no método validate(), que é o que passamos no corpo da requisição onde geramos o token relacionado a autenticação, esse payload é composto pelo email do usuário, em outros casos pode também possuir outros parâmetros. O passport também gera automaticamente as propriedades iat e exp que são, respectivamente, sobre a criação do token e expiração. Você pode consultar o site jwt.io e ver exemplos até criar tokens para ficar mais claro como eles são gerados.

Perceba que o payload é do tipo JwtPayload, mas não temos nenhuma referência ainda a ele, portanto vamos criar uma interface, que terá como propriedade o email. Assim, vamos mudar um pouco a estrutura da aplicação, onde vamos criar o diretório strategies, dentro do diretório auth , nele vamos adicionar o arquivo jwt-strategy.ts e criar o arquivo jwt-payload.ts.

//jwt-payload.ts
export interface JwtPayload {
  email: string;
}

A estrutura da pasta auth ficará da seguinte forma:

Estrutura de arquivos da past auth

Agora vamos conferir se os arquivos auth.module.ts e users.module.ts estão devidamente configurados conforme abaixo:

//auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from 'src/users/entities/user.entity';
import { UsersService } from 'src/users/users.service';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtStrategy } from './strategies/jwt-strategy';

@Module({
  imports: [TypeOrmModule.forFeature([User]), JwtModule.register({})],
  controllers: [AuthController],
  providers: [AuthService, UsersService, JwtStrategy],
})
export class AuthModule {}
//users.module.ts
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { JwtModule } from '@nestjs/jwt';
import { AuthService } from 'src/auth/auth.service';
import { JwtStrategy } from 'src/auth/strategies/jwt-strategy';

@Module({
  imports: [TypeOrmModule.forFeature([User]), JwtModule.register({})],
  controllers: [UsersController],
  providers: [UsersService, AuthService, JwtStrategy],
})
export class UsersModule {}

Testando proteção de rotas com Token JWT

Podemos testar a autenticação utilizando o insomnia, primeiramente vamos gerar o token na rota http://localhost:3000/auth/login, lembrando que é uma rota POST.

Requisição para o endpoint de autenticação com NestJS e Passsport retornando token com sucesso

Agora vamos acessar a rota de listagem de usuários (http://localhost:3000/users), para isso precisamos copiar o token e passar na requisição, lembrando que é uma rota GET:

Requisição para o endpoist de lisagem de usuários da aplicação NestJS e Passsport utilizando token para autenticação com sucesso

Selecionaremos Bearer na segunda aba, adicionaremos o Token no campo respectivo e clicar em Send. Veja que obtemos a lista de usuários como retorno, porém caso o token esteja expirado, incorreto ou inexistente, retornará o seguinte erro:

Simulando erro de autenticação com token inválido retornando erro 401 Unauthorized

O usuário terá acesso às rotas que necessitam de autenticação somente enquanto o Token for válido. Outro ponto que devemos nos atentar é o conceito de autorização, pois o usuário pode ter gerado um token, mas uma rota determinada pode ser acessada por um tipo de usuário específico, por exemplo, veremos sobre este assunto no próximo artigo.

Nest.js - Fundamentos
Curso Nest.js - Fundamentos
Conhecer o curso

Conclusão

Nos aprendemos no artigo anterior a gerar o Token JWT com o NestJS e a biblioteca Passport, já neste criamos uma rota protegida, necessário então a autenticação por um Token JWT válido. Esses são alguns conceitos e recursos que podemos utilizar para criar aplicações mais robustas e seguras, outros pontos que vamos ver nos próximos artigos serão sobre autorização e “refresh tokens”. Desta forma abrangendo outros aspectos de uma aplicação que utiliza estes recursos.

O código do exemplo pode ser consultado neste diretório do github :)

Por fim, caso queira aprender mais sobre NestJS saiba que aqui na TreinaWeb temos o curso Nest.js - Fundamentos que possui 02h07 de vídeos e um total de 18 exercícios. Conheça também nossos outros cursos de TypeScript.

Veja quais são os tópicos abordados durante o curso de Nest.js - Fundamentos:

  • Conhecendo a estrutura;
  • Utilizando Nest CLI
  • Entendendo Rotas, Controllers e Views;;
  • Conexão com banco de dados;
  • Usando TypeORM;
  • Template Engine.

Autor(a) do artigo

Wesley Gado
Wesley Gado

Formado em Análise e Desenvolvimento de Sistemas pelo Instituto Federal de São Paulo, atuou em projetos como desenvolvedor Front-End. Nas horas vagas grava Podcast e arrisca uns três acordes no violão.

Todos os artigos

Artigos relacionados Ver todos