NestJS

Nest

Tutorial Nest: Estrategia de autenticación JWT

Nest JWT: autenticación y uso. Domina la autenticación utilizando JWT en Nest con ejemplos prácticos y detallados.

JWT (JSON Web Token) es un estándar abierto (RFC 7519) para la creación de tokens de acceso que permiten la autenticación y autorización seguras entre dos partes, como un cliente y un servidor.

JWT se utiliza comúnmente para transmitir información de identidad y privilegios en aplicaciones web, APIs y servicios.

JWT es un método compacto y seguro para transmitir información entre dos partes como un objeto JSON, que se firma digitalmente y, opcionalmente, se cifra. La firma digital garantiza que el emisor del token (generalmente, un servidor de autenticación) pueda verificar la integridad de los datos y asegurarse de que no se hayan modificado durante la transmisión.

NestJS ofrece soporte para implementar autenticación JWT a través de su paquete @nestjs/jwt y el módulo Passport.

Estructura JWT

La estructura de un token JWT consta de tres partes, separadas por puntos (.):

1. Encabezado (Header): El encabezado es un objeto JSON que contiene información sobre el tipo de token (generalmente JWT) y el algoritmo de firma utilizado (como HS256 o RS256).

El encabezado se codifica en base64 y se utiliza para verificar la firma del token.

Ejemplo de encabezado:

{
  "alg": "HS256",
  "typ": "JWT"
}

2. Carga útil (Payload): La carga útil es un objeto JSON que contiene los llamados "claims" (reclamaciones), que son declaraciones sobre el sujeto (usuario) y otros metadatos.

Los claims pueden ser de tres tipos: claims registrados, públicos y privados.

Los claims registrados son un conjunto de claims predefinidos por el estándar JWT, como "iss" (emisor), "exp" (tiempo de expiración) y "sub" (sujeto).

Los claims públicos y privados son específicos de la aplicación y pueden contener información como roles de usuario, permisos, etc.

Al igual que el encabezado, la carga útil se codifica en base64.

Ejemplo de carga útil:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1516242622,
  "role": "admin"
}

3. Firma (Signature): La firma se crea utilizando el encabezado codificado en base64, la carga útil codificada en base64 y una clave secreta o un par de claves pública/privada, dependiendo del algoritmo de firma utilizado.

La firma es esencial para verificar la integridad y autenticidad del token JWT.

Ejemplo de cómo calcular la firma (usando el algoritmo HS256 y una clave secreta):

HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret_key
)

Cuando se combinan, estas tres partes forman el token JWT completo, que se ve así:

[header_base64].[payload_base64].[signature]

Cómo funciona JWT

Aquí hay un ejemplo de flujo de autenticación y autorización utilizando JWT:

  1. El cliente envía sus credenciales (nombre de usuario y contraseña) al servidor de autenticación en una solicitud de inicio de sesión.
  2. El servidor de autenticación verifica las credenciales y, si son válidas, crea un token JWT que contiene información sobre el usuario (claims) y firma el token con una clave secreta o un par de claves pública/privada.
  3. El servidor devuelve el token JWT al cliente.
  4. El cliente almacena el token JWT y lo incluye en el encabezado Authorization de las solicitudes posteriores al servidor, generalmente como un "Bearer Token".
  5. El servidor verifica la firma del token JWT en cada solicitud y comprueba si el token ha expirado. Si el token es válido, el servidor procesa la solicitud según los privilegios del usuario representados por el token. Si el token no es válido o ha expirado, el servidor deniega el acceso y puede solicitar al cliente que vuelva a autenticarse.

Es importante tener en cuenta que, aunque JWT proporciona un mecanismo seguro para transmitir información entre dos partes, los tokens deben tratarse como datos sensibles y protegerse adecuadamente.

No se debe almacenar información confidencial en la carga útil del token, ya que los datos codificados en base64 pueden decodificarse fácilmente.

JWT en NestJS

A continuación, se detalla el proceso para implementar una estrategia JWT en una aplicación NestJS.

Instalación de paquetes

Primero, es necesario instalar los siguientes paquetes:

npm install @nestjs/jwt @nestjs/passport passport passport-jwt

Configuración del módulo JWT

Crea un módulo de autenticación

Una vez que los paquetes estén instalados, se debe configurar el módulo JWT. Para esto, se crea un módulo AuthModule con un servicio y un controlador para manejar la autenticación:

nest generate module auth
nest generate service auth
nest generate controller auth

Configura el módulo JWT

// auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { UserModule } from '../user/user.module';

@Module({
    imports: [
        UserModule,
        PassportModule,
        JwtModule.register({
            secret: 'claveSecreta',
            signOptions: { expiresIn: '1h'}
        })
    ],
    providers: [AuthService],
    controllers: [AuthController]
})
export class AuthModule {}

En AuthModule, se importa UserModule, PassportModule y JwtModule. En las importaciones se configura JwtModule con la clave secreta y la duración de la sesión (1 hora en este ejemplo).

En el apartado de secret, se debe utilizar una clave secreta segura y no debe incluirse el código en un entorno de producción. En su lugar, se puede almacenar en una variable de entorno.

Creación de la estrategia JWT

Luego, dentro de la carpeta auth, se crea una estrategia para manejar la autenticación JWT:

// jwt.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { UsersService } from "src/users/users.service";

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
    constructor(private userService: UsersService) {
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration: false,
            secretOrKey: process.env.JWT_SECRET,
        });
    }

    async validate(payload: any) {
        let user = await this.userService.findById(payload.sub);

        if(!user)
            throw new UnauthorizedException('Autenticación incorrecta');
        
        let {password, ...userInfo} = user;
        return userInfo;
    }
}

En esta estrategia:

**PassportStrategy(Strategy)**: Define cómo se extrae el JWT del encabezado de autorización y cómo se valida.

**jwtFromRequest**: Especifica cómo se extraerá el JWT de la solicitud entrante.

**ignoreExpiration**: Indica si se debe ignorar la fecha de expiración del token.

**secretOrKey**: Es el secreto o clave que se usará para descifrar el token.

**validate**: Se invoca después de que el token ha sido validado y decodificado. Aquí es donde se puede hacer verificaciones adicionales, como comprobar la existencia de un usuario.

Registrar la estrategia

Después de crear la estrategia, se debe registrar en el módulo para que pueda ser inyectada:

// auth.module.ts
@Module({
    providers: [ AuthService, JwtStrategy ], // Se agrega JwtStrategy
})
export class AuthModule {}

Implementación de autenticación y protección de rutas

Para proteger rutas específicas, se utiliza el decorador @UseGuards().

Primero, dentro de la carpeta auth, se debe crear un AuthGuard para JWT:

// jwt-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

Y luego, se puede usar este guardián para proteger las rutas:

// app.controller.ts
import { Controller, UseGuards, Get } from '@nestjs/common';
import { JwtAuthGuard } from './jwt-auth.guard';

@Controller()
export class AppController {
  @UseGuards(JwtAuthGuard)
  @Get('ruta-protegida')
  rutaProtegida() {
    return 'Esta ruta está protegida con JWT';
  }
}

De esta forma, cuando se acceda a ruta-protegida, NestJS requerirá un JWT válido en el encabezado de autorización para permitir el acceso.

Generación y validación del token

Para completar el proceso de autenticación JWT, también es necesario proporcionar un mecanismo para generar el token JWT y validarlo.

Servicio de autenticación

Se puede crear un servicio que maneje la generación y validación de tokens.

// auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';

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

  async login(user: any) {
    const payload = { username: user.username, sub: user.userId };
    return {
      access_token: this.jwtService.sign(payload),
    };
  }

  // ... cualquier otro método relacionado con la autenticación
}

En el servicio anterior, se tiene un método login que genera un token JWT cuando un usuario inicia sesión en una aplicación.

Se crea un objeto payload que contiene la información que se incluirá en el token JWT. En este caso, el payload incluye el username y un identificador único (userId) del usuario que está iniciando sesión.

El método sign del JwtService se utiliza para firmar el payload y generar un token JWT. El token se devuelve en un objeto que tiene una clave access_token.

Endpoint de autenticación

Luego, se puede crear un endpoint que maneje el inicio de sesión de los usuarios y devuelva el token JWT.

// auth.controller.ts
import { Controller, Post, Request, Body } from '@nestjs/common';
import { AuthService } from './auth.service';

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

  @Post('login')
  async login(@Body() userDto: any) {
    return this.authService.login(userDto);
  }
}

El cliente ahora puede enviar una solicitud POST a /auth/login con las credenciales del usuario. Si las credenciales son correctas, el servidor devolverá un token JWT que el cliente deberá incluir en las solicitudes siguientes para acceder a rutas protegidas.

Conclusión

La estrategia de autenticación JWT en NestJS permite una gestión eficiente y segura de las sesiones de los usuarios. La combinación de JWT con NestJS proporciona una estructura modular y escalable para crear aplicaciones con autenticación robusta.

Certifícate en Nest con CertiDevs PLUS

Ejercicios de esta lección Estrategia de autenticación JWT

Evalúa tus conocimientos de esta lección Estrategia de autenticación JWT con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Todas las lecciones de Nest

Accede a todas las lecciones de Nest y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Certificados de superación de Nest

Supera todos los ejercicios de programación del curso de Nest y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  1. Comprender el propósito y la importancia de JWT.
  2. Conocer la estructura de un token JWT.
  3. Implementar una estrategia de autenticación JWT en NestJS.
  4. Proteger rutas específicas con JWT.
  5. Generar y validar tokens JWT.