Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIntermediáriocódigo testado

Guards e autenticação JWT no NestJS

Crie login com JWT, proteja rotas com AuthGuard e entenda respostas 401 no NestJS 11, sem colocar chave secreta ou token real no código.

Rodolfo Mori5 min de leitura

Um guard no NestJS decide se uma requisição pode chegar à rota. Nesta lição, você vai criar um login de demonstração, emitir um JWT, proteger GET /auth/perfil e confirmar os caminhos 401 e 200 sem expor a chave ou o token.

O projeto já deve ter o ValidationPipe da lição de DTO e validação. Aqui o foco é o fluxo de autenticação; o usuário fictício existe apenas na memória do laboratório.

A credencial emitida na recepção e lida na porta

Imagine um espaço de coworking. Na recepção, a pessoa confirma seu cadastro e recebe um crachá com validade. Na porta de uma sala, um leitor confere o crachá antes de liberar a entrada. A sala não refaz o cadastro; ela confia na decisão do leitor.

No mapa técnico, POST /auth/login é a recepção, o JWT é o crachá assinado, AuthGuard é o leitor e o controller protegido é a sala. Autenticação é confirmar a identidade. Autorização é decidir o que essa identidade pode fazer.

O limite da comparação é essencial: um JWT assinado não é uma identidade física nem uma sessão impossível de revogar. Ele comprova que o payload não foi alterado e que ainda está no prazo, desde que a chave e o algoritmo estejam corretos. Conta bloqueada, troca de senha e permissão atual podem exigir consulta adicional.

Instale o módulo JWT e gere os arquivos

Fixe a versão executada nesta lição:

bash
npm install @nestjs/jwt@11.0.2
npx nest generate module auth
npx nest generate controller auth --no-spec
npx nest generate service auth --no-spec
CREATE src/auth/auth.module.ts CREATE src/auth/auth.controller.ts CREATE src/auth/auth.service.ts UPDATE src/app.module.ts

Crie também src/auth/auth.guard.ts e src/auth/auth.types.ts. O CLI reduz digitação, mas o desenho da autenticação continua sendo nossa responsabilidade.

Configure o JWT sem publicar a chave

Edite src/auth/auth.module.ts:

ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AuthController } from './auth.controller';
import { AuthGuard } from './auth.guard';
import { AuthService } from './auth.service';

const jwtSecret = process.env.JWT_SECRET;

if (!jwtSecret) {
  throw new Error('JWT_SECRET não definida');
}

@Module({
  imports: [
    JwtModule.register({
      global: true,
      secret: jwtSecret,
      signOptions: { expiresIn: '15m' },
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService, AuthGuard],
  exports: [AuthGuard],
})
export class AuthModule {}

secret assina e verifica os tokens. O ! para fingir que a variável existe seria apenas silêncio para o TypeScript; a verificação explícita impede iniciar uma aplicação quebrada. Sem a variável, o teste parou assim:

Error: JWT_SECRET não definida Node.js v24.16.0

Em um projeto maior, ConfigModule pode validar todas as variáveis juntas. O princípio permanece: falhe no bootstrap, não na primeira requisição de um cliente.

Emita um token num login de laboratório

O service abaixo usa uma credencial fictícia e pública apenas para observar o JWT. Nunca copie a comparação literal para usuários reais.

ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
  constructor(private readonly jwtService: JwtService) {}

  async entrar(usuario: string, senha: string) {
    if (usuario !== 'mentor' || senha !== 'senha-da-aula') {
      throw new UnauthorizedException('credenciais inválidas');
    }

    return {
      access_token: await this.jwtService.signAsync({
        sub: 7,
        usuario,
      }),
    };
  }
}

sub é o subject: um identificador estável do usuário. O payload não recebe a senha. JWT assinado pode ser decodificado por quem tem o token, então coloque somente os dados mínimos necessários.

Em produção, busque o usuário no banco e compare a senha com hash. A lição de hash de senha com bcrypt mostra essa etapa; rate limiting, recuperação segura e segundo fator também não cabem dentro do guard.

Exponha login e perfil

O controller oferece uma rota pública para obter token e uma rota protegida para ler o usuário verificado:

ts
import {
  Body,
  Controller,
  Get,
  HttpCode,
  Post,
  Req,
  UseGuards,
} from '@nestjs/common';
import { AuthGuard } from './auth.guard';
import { AuthService } from './auth.service';
import type { RequestAutenticada } from './auth.types';

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @HttpCode(200)
  @Post('login')
  entrar(@Body() body: { usuario: string; senha: string }) {
    return this.authService.entrar(body.usuario, body.senha);
  }

  @UseGuards(AuthGuard)
  @Get('perfil')
  perfil(@Req() request: RequestAutenticada) {
    return request.user;
  }
}

@UseGuards(AuthGuard) associa a decisão à rota. @HttpCode(200) altera o status padrão do POST, que seria 201, porque o login não criou um recurso de domínio.

Para o TypeScript conhecer request.user, declare o formato:

ts
import type { Request } from 'express';

export type UsuarioAutenticado = {
  sub: number;
  usuario: string;
  iat: number;
  exp: number;
};

export type RequestAutenticada = Request & {
  user?: UsuarioAutenticado;
};

iat registra quando o token foi emitido e exp quando expira. Ambos são timestamps em segundos. Eles serão acrescentados pelo pacote ao assinar.

O guard lê Bearer, verifica e anexa o usuário

Crie src/auth/auth.guard.ts:

ts
import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import type { RequestAutenticada, UsuarioAutenticado } from './auth.types';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private readonly jwtService: JwtService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest<RequestAutenticada>();
    const [type, token] = request.headers.authorization?.split(' ') ?? [];

    if (type !== 'Bearer' || !token) {
      throw new UnauthorizedException('token ausente ou inválido');
    }

    try {
      request.user =
        await this.jwtService.verifyAsync<UsuarioAutenticado>(token);
      return true;
    } catch {
      throw new UnauthorizedException('token ausente ou inválido');
    }
  }
}

ExecutionContext dá acesso ao contexto atual. Como esta aplicação usa HTTP, switchToHttp() entrega a requisição. O padrão do cabeçalho é Authorization: Bearer TOKEN; o guard separa as duas partes, verifica assinatura e prazo e só então devolve true.

Usar a mesma mensagem para token ausente, alterado ou expirado evita revelar detalhes desnecessários. O servidor pode registrar uma categoria interna, sem salvar o token completo.

Importe AuthModule onde o guard será usado

Para proteger também a criação de mentorias, importe o módulo e aplique o guard:

ts
@Module({
  imports: [AuthModule],
  controllers: [MentoriasController],
  providers: [MentoriasService],
})
export class MentoriasModule {}
ts
@UseGuards(AuthGuard)
@Post()
criar(@Body() dto: CreateMentoriaDto) {
  return this.mentoriasService.criar(dto.titulo, dto.vagas);
}

AuthModule exporta AuthGuard; MentoriasModule importa essa interface pública. Esse é o mesmo caminho de providers, exports e imports estudado na lição anterior.

Inicie com um segredo efêmero

Gere uma chave apenas para o processo atual, sem mostrá-la no terminal:

bash
JWT_SECRET="$(openssl rand -hex 32)" npm run start
[InstanceLoader] JwtModule dependencies initialized [InstanceLoader] AuthModule dependencies initialized [RouterExplorer] Mapped {/auth/login, POST} route [RouterExplorer] Mapped {/auth/perfil, GET} route [NestApplication] Nest application successfully started

Ao encerrar o processo, a chave some. Tokens antigos deixam de ser aceitos na próxima chave, o que é adequado ao laboratório. Em produção, rotação precisa ser planejada para não derrubar sessões sem intenção.

Erro real: rota protegida sem token

Consulte o perfil sem Authorization:

bash
curl -i http://localhost:3000/auth/perfil
HTTP/1.1 401 Unauthorized Content-Type: application/json; charset=utf-8

{“message”:“token ausente ou inválido”,“error”:“Unauthorized”,“statusCode”:401}

O controller de perfil não executou. O guard interrompeu a requisição antes. Um login com a senha fictícia errada produz outro 401 controlado:

bash
curl -i -X POST http://localhost:3000/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"usuario":"mentor","senha":"errada"}'
HTTP/1.1 401 Unauthorized

{“message”:“credenciais inválidas”,“error”:“Unauthorized”,“statusCode”:401}

As duas respostas têm o mesmo status por motivos relacionados à autenticação, mas em pontos diferentes: uma falhou ao provar credenciais no login; a outra não apresentou uma credencial de acesso válida.

Confirme o token sem imprimi-lo

Faça o login e use jq para comprovar que o token existe e tem três segmentos, sem despejar seu conteúdo:

bash
LOGIN_JSON=$(curl -s -X POST http://localhost:3000/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"usuario":"mentor","senha":"senha-da-aula"}')

printf '%s\n' "$LOGIN_JSON" | jq \
  '{temToken: (.access_token | type == "string"), segmentos: (.access_token | split(".") | length)}'
{ "temToken": true, "segmentos": 3 }

Guarde o valor numa variável temporária e chame o perfil. O filtro remove os timestamps variáveis da saída:

bash
SESSION_TOKEN=$(printf '%s\n' "$LOGIN_JSON" | jq -r .access_token)

curl -s http://localhost:3000/auth/perfil \
  -H "Authorization: Bearer ${SESSION_TOKEN}" \
  | jq '{sub, usuario}'
{ "sub": 7, "usuario": "mentor" }

O token nunca apareceu no histórico impresso. A assinatura foi verificada, e o payload ficou disponível para as próximas decisões.

Missão: diferencie autenticação de autorização

Acrescente papel: 'instrutor' ao payload do usuário fictício. Depois crie um segundo guard que devolve 403 quando request.user.papel !== 'instrutor' e use-o no POST de mentorias depois do AuthGuard.

bash
curl -i -X POST http://localhost:3000/mentorias \
  -H 'Content-Type: application/json' \
  -d '{"titulo":"Nest com segurança","vagas":6}'

curl -i -X POST http://localhost:3000/mentorias \
  -H "Authorization: Bearer ${SESSION_TOKEN}" \
  -H 'Content-Type: application/json' \
  -d '{"titulo":"Nest com segurança","vagas":6}'

O critério de sucesso é: sem token retorna 401; token válido com papel errado retorna 403; token de instrutor retorna 201. Não logue a chave nem o token para provar o teste. A lição de autenticação JWT no Node aprofunda validade e assinatura; o próximo passo do cluster conecta a rota protegida ao banco com Prisma no NestJS. Você pode voltar ao guia de NestJS para revisar o caminho completo.

  • nestjs
  • guard
  • autenticacao
  • jwt
  • seguranca
  • typescript

Perguntas frequentes

O que é um guard no NestJS?
Guard é uma classe que implementa CanActivate e decide, com base no ExecutionContext, se uma execução pode chegar ao controller.
Qual a diferença entre 401 e 403?
401 indica que a requisição não apresentou autenticação válida. 403 indica que a identidade foi reconhecida, mas não tem permissão para aquela ação.
JWT criptografa os dados do payload?
Não por padrão. Um JWT assinado protege a integridade, mas o payload pode ser lido. Não coloque senha, segredo ou dado sensível nele.
Posso guardar JWT_SECRET no código?
Não. Injete o segredo pelo ambiente ou por um gerenciador de segredos, valide sua presença ao iniciar e nunca o imprima em logs.
Guard substitui hash de senha?
Não. O guard verifica a credencial da requisição. Senhas persistidas ainda precisam de hash adequado, comparação segura e controles contra abuso.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0, NestJS 11.2.1, @nestjs/jwt 11.0.2 e TypeScript 5.9.3, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. NestJS — autenticação — docs.nestjs.com
  2. NestJS — guards — docs.nestjs.com
  3. NestJS — autorização — docs.nestjs.com
  4. npm — pacote @nestjs/jwt — npmjs.com

Continue por aqui