sql >> Base de Datos >  >> NoSQL >> MongoDB

NestJS:cómo implementar la autenticación de usuario basada en sesiones

Introducción

Es una realidad indiscutible que la autenticación es fundamental en cualquier aplicación o sistema si desea proteger los datos del usuario y permitir el acceso seguro a la información. La autenticación es el procedimiento para establecer o demostrar que algo es verdadero, legítimo o válido.

Requisitos previos

Este tutorial es una demostración práctica. Para seguir, asegúrese de tener lo siguiente en su lugar:

  • Node.js ejecutándose en su sistema porque NestJS es un marco de Node.js
  • MongoDB instalado

¿Qué es NestJS?

Nest (NestJS) es un marco de aplicación del lado del servidor Node.js para crear aplicaciones escalables y eficientes.

Está escrito en TypeScript y se basa en Express, un marco muy minimalista que es excelente por sí solo pero carece de estructura. Combina paradigmas de programación como la programación orientada a objetos, la programación funcional y la programación reactiva funcional.

Es un marco para usar si desea mucha estructura en su backend. Su sintaxis y estructura son muy similares a AngularJS, un framework front-end. Y usa TypeScript, servicios e inyección de dependencia de la misma manera que lo hace AngularJS.

Emplea módulos y controladores, y puede crear controladores para un archivo mediante la interfaz de línea de comandos.

Los módulos de NestJS le permiten agrupar controladores y proveedores de servicios relacionados en un solo archivo de código. En pocas palabras, un módulo NestJS es un archivo TypeScript con el @Module anotación (). Este decorador informa al marco NestJS sobre qué controladores, proveedores de servicios y otros recursos asociados serán instanciados y utilizados por el código de la aplicación más adelante.

¿Qué es la autenticación basada en sesión?

La autenticación basada en sesión es un método de autenticación de usuario en el que el servidor crea una sesión después de un inicio de sesión exitoso, con la ID de sesión almacenada en una cookie o almacenamiento local en su navegador.

Tras solicitudes posteriores, su cookie se valida con el ID de sesión almacenado en el servidor. Si hay una coincidencia, la solicitud se considera válida y procesada.

Al utilizar este método de autenticación, es fundamental tener en cuenta las siguientes mejores prácticas de seguridad:

  • Genera ID de sesión largos y aleatorios (128 bits es la longitud recomendada) para que los ataques de fuerza bruta sean ineficaces
  • Evite almacenar datos confidenciales o específicos del usuario
  • Hacer que las comunicaciones HTTPS sean obligatorias para todas las aplicaciones basadas en sesiones
  • Cree cookies que tengan atributos seguros y solo HTTP

¿Por qué autenticación basada en sesión?

La autenticación basada en sesiones es más segura que la mayoría de los métodos de autenticación porque es simple, segura y tiene un tamaño de almacenamiento limitado. También se cree que es la mejor opción para sitios web en el mismo dominio raíz.

Configuración del proyecto

Comience la configuración de su proyecto instalando Nest CLI globalmente. No necesita hacer esto si ya tiene instalada la CLI de NestJS.

Nest CLI es una herramienta de interfaz de línea de comandos para configurar, desarrollar y mantener aplicaciones Nest.

npm i -g @nestjs/cli

Ahora, configuremos su proyecto ejecutando el siguiente comando:

nest new session-based-auth

El comando anterior crea una aplicación Nest con algunos modelos, luego le pide que elija su administrador de paquetes preferido para instalar los módulos necesarios para ejecutar su aplicación. Para demostración, este tutorial usa npm . Pulse la tecla Intro para continuar con npm .

Si todo salió bien, debería ver un resultado como el de la captura de pantalla a continuación en su terminal.

Una vez que se complete la instalación, muévase al directorio de su proyecto y ejecute la aplicación con el siguiente comando:

npm run start:dev

El comando anterior ejecuta la aplicación y observa los cambios. Tu proyecto src la estructura de carpetas debe tener el siguiente aspecto.

└───src
│   └───app.controller.ts
│   └───app.modules.ts
│   └───app.service.ts
│   └───main.ts

Instalar dependencias

Ahora que su aplicación está configurada, instalemos las dependencias necesarias.

npm install --save @nestjs/passport passport passport-local

El comando anterior instala Passport.js, una popular biblioteca de autenticación nest.js.

Además, instale los tipos para la estrategia con el siguiente comando:

Contiene definiciones de tipos para passport-local .

npm install --save-dev @types/passport-local

Configurar la base de datos MongoDB en NestJS

Para configurar y conectar su base de datos, instale el paquete Mongoose y el contenedor NestJS con el siguiente comando:

npm install --save @nestjs/mongoose mongoose

El contenedor Mongoose NestJS lo ayuda a usar Mongoose en la aplicación NestJS y brinda compatibilidad aprobada con TypeScript.

Ahora, dirígete a tu app.module.ts e importa la mongoose módulo de @nestjs/mongoose . Luego llame al forRoot() método, un método proporcionado por el módulo Mongoose, y pase la cadena URL de su base de datos.

Configurando su conexión de base de datos en app.module.ts ayuda a que su aplicación se conecte a la base de datos inmediatamente cuando se inicia el servidor, después de ejecutar su aplicación, ya que es el primer módulo que se carga.

app.module.ts

import { Module } from "@nestjs/common"
import { MongooseModule } from "@nestjs/mongoose"
import { AppController } from "./app.controller"
import { AppService } from "./app.service"

@Module({
  imports: [
    MongooseModule.forRoot(
      "mongodb+srv://<username>:<password>@cluster0.kngtf.mongodb.net/session-auth?retryWrites=true&w=majority"
    ),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Crear módulo de usuarios

Por cuestiones de separación, para que su código esté limpio y bien organizado, cree un módulo específico para usuarios que usen la CLI de NestJS ejecutando el siguiente comando:

nest g module users

El comando anterior crea un users carpeta con users.module.ts y actualiza app.module.ts

Además, cree users.service.ts y users.controller.ts archivos con los siguientes comandos:

nest g service users
nest g controller users

Tenga en cuenta que puede crear sus carpetas y archivos manualmente sin usar la CLI de nest, pero usar la CLI actualiza automáticamente las carpetas necesarias y le facilita la vida.

Crear esquema de usuario

El siguiente paso es crear su UserSchema, pero primero, agregue un users.model.ts archivo, donde creará UserSchema

Esta debería ser la forma de nuestra aplicación src carpeta ahora.

└───src
│   └───users
│   │   └───users.controller.ts
│   │   └───users.model.ts
│   │   └───users.module.ts
│   │   └───users.service.ts
│   └───app.controller.ts
│   └───app.module.ts
│   └───app.service.ts
│   └───main.ts

Para crear UserSchema , importe todo como mongoose desde el paquete mongoose en users.model.ts . Luego llame al nuevo esquema de mongoose, un modelo del modelo de usuario, y pase un objeto de JavaScript donde definirá el objeto y los datos del usuario.

users.model.ts

import * as mongoose from "mongoose"
export const UserSchema = new mongoose.Schema(
  {
    username: {
      type: String,
      required: true,
      unique: true,
    },
    password: {
      type: String,
      required: true,
    },
  },
  { timestamps: true }
)

export interface User extends mongoose.Document {
  _id: string;
  username: string;
  password: string;
}

Además, cree una interfaz para su modelo que amplíe mongoose, un documento que lo ayude a completar sus colecciones de MongoDB.

Dirígete a tu users.module.ts e importar MongooseModule en la matriz de importaciones. Luego llame al forFeature() método proporcionado por MongooseModule , y pase una matriz de objetos que toman el nombre y el esquema.

Esto le permitirá compartir el archivo en cualquier lugar con la ayuda de la inyección de dependencia.

users.module.ts

import { Module } from "@nestjs/common"
import { MongooseModule } from "@nestjs/mongoose"
import { UsersController } from "./users.controller"
import { UserSchema } from "./users.model"
import { UsersService } from "./users.service"
@Module({
  imports: [MongooseModule.forFeature([{ name: "user", schema: UserSchema }])],
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

En users.module.ts , exporte el UsersService para permitirle acceder a él en otro módulo.

users.module.ts

import { Module } from "@nestjs/common"
import { MongooseModule } from "@nestjs/mongoose"
import { UsersController } from "./users.controller"
import { UserSchema } from "./users.model"
import { UsersService } from "./users.service"
@Module({
  imports: [MongooseModule.forFeature([{ name: "user", schema: UserSchema }])],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

Por lo general, es una buena idea encapsular la lógica comercial en una clase separada. Tal clase se conoce como un servicio. El trabajo de esta clase es procesar las solicitudes del controlador y realizar la lógica empresarial.

En users.service.ts archivo, importar Model de mongoose , User de users.model.ts y InjectModel de @nestjs/mongoose . Luego agregue un método al UsersService clase que toma un nombre de usuario y una contraseña, y llama al método insertUser() .

users.service.ts

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './users.model';
@Injectable()
export class UsersService {
  constructor(@InjectModel('user') private readonly userModel: Model<User>) {}
  async insertUser(userName: string, password: string) {
    const username = userName.toLowerCase();
    const newUser = new this.userModel({
      username,
      password,
    });
    await newUser.save();
    return newUser;
  }
}

Ahora que UsersService la clase está lista, debe inyectarla en su controlador. Pero primero, hablemos sobre el almacenamiento seguro de las contraseñas de los usuarios.

El aspecto más crítico del procedimiento de registro son las contraseñas de los usuarios, que no deben guardarse en texto sin formato. Es responsabilidad del usuario crear una contraseña segura, pero es su obligación como desarrollador mantener sus contraseñas seguras. Si se produce una violación de la base de datos, las contraseñas de los usuarios quedarían expuestas. ¿Y qué sucede si se almacena en texto sin formato? Creo que sabes la respuesta. Para solucionar esto, hash las contraseñas usando bcrypt.

Entonces, instala bcrypt y @types/bcrypt con el siguiente comando:

npm install @types/bcrypt bcrypt

Con eso fuera del camino, configure su controlador. Primero, importa tu UsersService clase y todo desde bcrypt . Luego agregue un constructor y un método que le permita agregar un usuario; manejará las solicitudes de publicación entrantes, llámelo addUser , con un cuerpo de función en el que codificarás la contraseña.

users.controller.ts

import { Body, Controller, Post } from '@nestjs/common';
import { UsersService } from './users.service';
import * as bcrypt from 'bcrypt';
@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}
  //post / signup
  @Post('/signup')
  async addUser(
    @Body('password') userPassword: string,
    @Body('username') userName: string,
  ) {
    const saltOrRounds = 10;
    const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
    const result = await this.usersService.insertUser(
      userName,
      hashedPassword,
    );
    return {
      msg: 'User successfully registered',
      userId: result.id,
      userName: result.username
    };
  }
}

El registro ocurre en el app.module.ts archivo, que se logra agregando el UsersModule al @Module() matriz de importaciones del decorador en app.module.ts .

app.module.ts

import { Module } from "@nestjs/common"
import { MongooseModule } from "@nestjs/mongoose"
import { AppController } from "./app.controller"
import { AppService } from "./app.service"
import { UsersModule } from "./users/users.module"

@Module({
  imports: [
    MongooseModule.forRoot(
      "mongodb+srv://<username>:<password>@cluster0.kngtf.mongodb.net/session-auth?retryWrites=true&w=majority"
    ),
    UsersModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

¡Felicidades! Ha terminado con el registro. Ahora puede registrar un usuario con un nombre de usuario y contraseña.

Ahora, con el registro fuera del camino, agregue un getUser función a su UsersService con el findOne método para encontrar un usuario por nombre de usuario.

users.service.ts

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './users.model';
@Injectable()
export class UsersService {
  constructor(@InjectModel('user') private readonly userModel: Model<User>) {}
  async insertUser(userName: string, password: string) {
    const username = userName.toLowerCase();
    const newUser = new this.userModel({
      username,
      password,
    });
    await newUser.save();
    return newUser;
  }
  async getUser(userName: string) {
    const username = userName.toLowerCase();
    const user = await this.userModel.findOne({ username });
    return user;
  }
}

Crear módulo de autenticación

Al igual que para los usuarios, cree un módulo y un servicio de autenticación específicamente para todas las autenticaciones/verificaciones. Para hacerlo, ejecute los siguientes comandos:

nest g module auth
nest g service auth

Lo anterior creará una carpeta de autenticación, auth.module.ts y auth.service.ts y actualice el auth.module.ts y app.module.ts archivos.

En este punto, la forma de su aplicación src la carpeta debería tener el siguiente aspecto.

└───src
│   └───auth
│   │   └───auth.module.ts
│   │   └───auth.service.ts
│   └───users
│   │   └───users.controller.ts
│   │   └───users.model.ts
│   │   └───users.module.ts
│   │   └───users.service.ts
│   └───app.controller.ts
│   └───app.module.ts
│   └───app.service.ts
│   └───main.ts

El comando generar anterior actualizará su app.module.ts , y se verá como el fragmento de código a continuación:

app.module.ts

import { Module } from "@nestjs/common"
import { MongooseModule } from "@nestjs/mongoose"
import { AppController } from "./app.controller"
import { AppService } from "./app.service"
import { UsersModule } from "./users/users.module"
import { AuthModule } from './auth/auth.module';


@Module({
  imports: [UsersModule, AuthModule, MongooseModule.forRoot(
    //database url string
    'mongodb://localhost:27017/myapp'
    )],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Autenticar usuarios

Vaya a su auth.module.ts archivo y agregue UsersModule en la matriz de importaciones para habilitar el acceso a UsersService exportado desde users.module.ts archivo.

auth.module.ts

import { Module } from "@nestjs/common"
import { UsersModule } from "src/users/users.module"
import { AuthService } from "./auth.service"

@Module({
  imports: [UsersModule],
  providers: [AuthService],
})
export class AuthModule {}

En su auth.service.ts archivo, llame al constructor para que pueda inyectar el UsersService y agregue un método de validación que tomará un nombre de usuario y una contraseña.

Para agregar algunas validaciones básicas, verifique si el usuario existe en la base de datos y compare la contraseña dada con la de su base de datos para asegurarse de que coincida. Si existe, devuelve el usuario en el request.user objeto — de lo contrario, devuelve nulo.

auth.service.ts

    import { Injectable, NotAcceptableException } from '@nestjs/common';
    import { UsersService } from 'src/users/users.service';
    import * as bcrypt from 'bcrypt';

    @Injectable()
    export class AuthService {
      constructor(private readonly usersService: UsersService) {}
      async validateUser(username: string, password: string): Promise<any> {
        const user = await this.usersService.getUser(username);
        const passwordValid = await bcrypt.compare(password, user.password)
        if (!user) {
            throw new NotAcceptableException('could not find the user');
          }
        if (user && passwordValid) {
          return {
            userId: user.id,
            userName: user.username
          };
        }
        return null;
      }
    }

Yendo más allá, cree un nuevo archivo y asígnele el nombre local.strategy.ts . Este archivo representará la estrategia de Passport.js , que instaló anteriormente, esa es la local strategy . Y dentro de ella, pasar la estrategia, que es la Strategy de passport-local .

Cree un constructor e inyecte el AuthService , llama al super() método; asegúrese de llamar al super() método.

local.strategy.ts

    import { Injectable, UnauthorizedException } from '@nestjs/common';
    import { PassportStrategy } from '@nestjs/passport';
    import { Strategy } from 'passport-local';
    import { AuthService } from './auth.service';
    @Injectable()
    export class LocalStrategy extends PassportStrategy(Strategy) {
      constructor(private readonly authService: AuthService) {
        super();
      }
      async validate(username: string, password: string): Promise<any> {
        const userName = username.toLowerCase();
        const user = await this.authService.validateUser(userName, password);
        if (!user) {
          throw new UnauthorizedException();
        }
        return user;
      }
    }

Regrese a su auth.module.ts expediente. Luego agrega PassportModule a las importaciones y LocalStrategy a los proveedores.

auth.module.ts

import { Module } from "@nestjs/common"
import { PassportModule } from "@nestjs/passport"
import { UsersModule } from "src/users/users.module"
import { AuthService } from "./auth.service"
import { LocalStrategy } from "./local.strategy"

@Module({
  imports: [UsersModule, PassportModule],
  providers: [AuthService, LocalStrategy],
})
export class AuthModule {}

Ahora, agregue la ruta de inicio de sesión a su users.controller.ts :

users.controller.ts

    import {
      Body,
      Controller,
      Post,
      Request,
    } from '@nestjs/common';
    import * as bcrypt from 'bcrypt';
    import { UsersService } from './users.service';
    @Controller('users')
    export class UsersController {
      constructor(private readonly usersService: UsersService) {}
      //post / signup
      @Post('/signup')
      async addUser(
        @Body('password') userPassword: string,
        @Body('username') userName: string,
      ) {
        const saltOrRounds = 10;
        const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
        const result = await this.usersService.insertUser(
          userName,
          hashedPassword,
        );
        return {
          msg: 'User successfully registered',
          userId: result.id,
          userName: result.username
        };
      }
      //Post / Login
      @Post('/login')
      login(@Request() req): any {
        return {User: req.user,
                msg: 'User logged in'};
      }
    }

Ahora que ha implementado todo esto, aún no puede iniciar sesión como usuario porque no hay nada que active la ruta de inicio de sesión. Toma, usa Guardias para lograrlo.

Cree un archivo y asígnele el nombre local.auth.guard.ts , luego una clase LocalAuthGuard que extiende AuthGuard de NestJS/passport , donde proporcionará el nombre de la estrategia y pasará el nombre de su estrategia, local .

local.auth.guard.ts.

import { Injectable } from "@nestjs/common"
import { AuthGuard } from "@nestjs/passport"
@Injectable()
export class LocalAuthGuard extends AuthGuard("local") {}

Agregue el UseGuard decorador a su ruta de inicio de sesión en users.controller.ts y pase el LocalAuthGuard .

users.controller.ts

    import {
      Body,
      Controller,
      Post,
      UseGuards,
      Request,
    } from '@nestjs/common';
    import * as bcrypt from 'bcrypt';
    import { LocalAuthGuard } from 'src/auth/local.auth.guard';
    import { UsersService } from './users.service';
    @Controller('users')
    export class UsersController {
      constructor(private readonly usersService: UsersService) {}
      //post / signup
      @Post('/signup')
      async addUser(
        @Body('password') userPassword: string,
        @Body('username') userName: string,
      ) {
        const saltOrRounds = 10;
        const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
        const result = await this.usersService.insertUser(
          userName,
          hashedPassword,
        );
        return {
          msg: 'User successfully registered',
          userId: result.id,
          userName: result.username
        };
      }
      //Post / Login
      @UseGuards(LocalAuthGuard)
      @Post('/login')
      login(@Request() req): any {
        return {User: req.user,
                msg: 'User logged in'};
      }
    }

Finalmente, puede iniciar sesión como usuario con un nombre de usuario y contraseña registrados.

Proteger rutas de autenticación

Ha configurado correctamente la autenticación de usuario. Ahora, proteja sus rutas del acceso no autorizado limitando el acceso solo a usuarios autenticados. Vaya a su users.controller.ts y agregue otra ruta:asígnele el nombre 'protegido' y haga que devuelva el req.user objeto.

users.controller.ts

    import {
      Body,
      Controller,
      Get,
      Post,
      UseGuards,
      Request,
    } from '@nestjs/common';
    import * as bcrypt from 'bcrypt';
    import { LocalAuthGuard } from 'src/auth/local.auth.guard';
    import { UsersService } from './users.service';
    @Controller('users')
    export class UsersController {
      constructor(private readonly usersService: UsersService) {}
      //signup
      @Post('/signup')
      async addUser(
        @Body('password') userPassword: string,
        @Body('username') userName: string,
      ) {
        const saltOrRounds = 10;
        const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
        const result = await this.usersService.insertUser(
          userName,
          hashedPassword,
        );
        return {
          msg: 'User successfully registered',
          userId: result.id,
          userName: result.username
        };
      }
      //Post / Login
      @UseGuards(LocalAuthGuard)
      @Post('/login')
      login(@Request() req): any {
        return {User: req.user,
                msg: 'User logged in'};
      }
    // Get / protected
      @Get('/protected')
      getHello(@Request() req): string {
        return req.user;
      }
    }

La ruta protegida en el código anterior devolverá un objeto vacío en lugar de devolver los detalles del usuario cuando un usuario que haya iniciado sesión le haga una solicitud porque ya perdió el inicio de sesión.

Para solucionarlo, aquí es donde entra en juego la autenticación basada en sesiones.

En la autenticación basada en sesión, cuando un usuario inicia sesión, el usuario se guarda en una sesión para que cualquier solicitud posterior del usuario después del inicio de sesión obtenga los detalles de la sesión y le otorgue un fácil acceso. La sesión expira cuando el usuario cierra la sesión.

Para iniciar la autenticación basada en sesiones, instale express-session y los tipos NestJS con el siguiente comando:

npm install express-session @types/express-session

Cuando se complete la instalación, vaya a su main.ts archivo, la raíz de su aplicación, y haga las configuraciones allí.

Importa todo desde passport y express-session , luego agregue la inicialización del pasaporte y la sesión del pasaporte.

Es preferible mantener su clave secreta en sus variables de entorno.

main.ts

import { NestFactory } from "@nestjs/core"
import { AppModule } from "./app.module"
import * as session from "express-session"
import * as passport from "passport"
async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.use(
    session({
      secret: "keyboard",
      resave: false,
      saveUninitialized: false,
    })
  )
  app.use(passport.initialize())
  app.use(passport.session())

  await app.listen(3000)
}
bootstrap()

Agregue un nuevo archivo, authenticated.guard.ts , en su auth carpeta. Y cree un nuevo Guard que verifique si hay una sesión para el usuario que realiza la solicitud; asígnele el nombre authenticatedGuard .

authenticated.guard.ts

import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"

@Injectable()
export class AuthenticatedGuard implements CanActivate {
  async canActivate(context: ExecutionContext) {
    const request = context.switchToHttp().getRequest()
    return request.isAuthenticated()
  }
}

En el código anterior, la solicitud se obtiene del contexto y se verifica si está autenticada. isAuthenticated() proviene de passport.js automáticamente; dice. "¡Oye! ¿Existe una sesión para este usuario? Si es así, continúa".

Para activar el inicio de sesión, en su users.controller.ts archivo:

  • importar authenticated de authenticated.guard.ts;
  • agregue el useGuard decorador al protected ruta; y,
  • pasar AuthenticatedGuard .

users.controller.ts

    import {
      Body,
      Controller,
      Get,
      Post,
      UseGuards,
      Request,
    } from '@nestjs/common';
    import * as bcrypt from 'bcrypt';
    import { AuthenticatedGuard } from 'src/auth/authenticated.guard';
    import { LocalAuthGuard } from 'src/auth/local.auth.guard';
    import { UsersService } from './users.service';
    @Controller('users')
    export class UsersController {
      constructor(private readonly usersService: UsersService) {}
      //signup
      @Post('/signup')
      async addUser(
        @Body('password') userPassword: string,
        @Body('username') userName: string,
      ) {
        const saltOrRounds = 10;
        const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
        const result = await this.usersService.insertUser(
          userName,
          hashedPassword,
        );
        return {
          msg: 'User successfully registered',
          userId: result.id,
          userName: result.username
        };
      }
      //Post / Login
      @UseGuards(LocalAuthGuard)
      @Post('/login')
      login(@Request() req): any {
        return {User: req.user,
                msg: 'User logged in'};
      }
      //Get / protected
      @UseGuards(AuthenticatedGuard)
      @Get('/protected')
      getHello(@Request() req): string {
        return req.user;
      }
    }

En este punto, todavía falla porque solo configuró express-session pero no lo implementó.

Cuando un usuario inicia sesión, debe guardarlo en una sesión para que pueda acceder a otras rutas con la sesión.

Una cosa a tener en cuenta es que, de forma predeterminada, express-session biblioteca almacena la sesión en la memoria del servidor web.

Antes de entrar en la sesión, debe serializar al usuario. A medida que sale de la sesión, deserializa al usuario.

Por lo tanto, cree un nuevo archivo en la carpeta de autenticación para serializador y deserializador, asígnele el nombre session.serializer.ts .

En este punto, la forma de nuestra aplicación src la carpeta debería tener este aspecto.

    └───src
    │   └───auth
    │   │   └───auth.module.ts
    │   │   └───auth.service.ts
    │   │   └───authenticated.guard.ts
    │   │   └───local.auth.guard.ts
    │   │   └───local.strategy.ts
    │   │   └───session.serializer.ts
    │   └───users
    │   │   └───users.controller.ts
    │   │   └───users.model.ts
    │   │   └───users.module.ts
    │   │   └───users.service.ts
    │   └───app.controller.ts
    │   └───app.module.ts
    │   └───app.service.ts
    │   └───main.ts

session.serializer.ts

import { Injectable } from "@nestjs/common"
import { PassportSerializer } from "@nestjs/passport"

@Injectable()
export class SessionSerializer extends PassportSerializer {
  serializeUser(user: any, done: (err: Error, user: any) => void): any {
    done(null, user)
  }
  deserializeUser(
    payload: any,
    done: (err: Error, payload: string) => void
  ): any {
    done(null, payload)
  }
}

Regrese a su auth.module.ts archivo, proporcione el SessionSerializer y agregue el register método al PassportModule .

auth.module.ts

import { Module } from "@nestjs/common"
import { PassportModule } from "@nestjs/passport"
import { UsersModule } from "src/users/users.module"
import { AuthService } from "./auth.service"
import { LocalStrategy } from "./local.strategy"
import { SessionSerializer } from "./session.serializer"

@Module({
  imports: [UsersModule, PassportModule.register({ session: true })],
  providers: [AuthService, LocalStrategy, SessionSerializer],
})
export class AuthModule {}

Add some codes within the LocalAuthGuard in the local.auth.guard.ts archivo.

Call the login method in super and pass in the request to trigger the actual login by creating a session. If you want to use sessions, you must remember to trigger the super.login() .

local.auth.guard.ts

    import { ExecutionContext, Injectable } from '@nestjs/common';
    import { AuthGuard } from '@nestjs/passport';
    @Injectable()
    export class LocalAuthGuard extends AuthGuard('local') {
      async canActivate(context: ExecutionContext) {
        const result = (await super.canActivate(context)) as boolean;
        const request = context.switchToHttp().getRequest();
        await super.logIn(request);
        return result;
      }
    }

If you log in now, you will see the session ID stored in a cookie, which is just a key to the session store, and the cookie gets saved in the browser. The cookie is automatically attached to the rest of the request.

Now that the session is working, you can access the protected route; it will return the expected user’s details.

Logout Users

As mentioned earlier, once a user logs out, you destroy all sessions.

To log out a user, go to the users.controller.ts file, add a logout route, and call the req.session.session() método. You can return a message notifying that the user’s session has ended.

    import {
      Body,
      Controller,
      Get,
      Post,
      UseGuards,
      Request,
    } from '@nestjs/common';
    import * as bcrypt from 'bcrypt';
    import { AuthenticatedGuard } from 'src/auth/authenticated.guard';
    import { LocalAuthGuard } from 'src/auth/local.auth.guard';
    import { UsersService } from './users.service';
    @Controller('users')
    export class UsersController {
      constructor(private readonly usersService: UsersService) {}
      //signup
      @Post('/signup')
      async addUser(
        @Body('password') userPassword: string,
        @Body('username') userName: string,
      ) {
        const saltOrRounds = 10;
        const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
        const result = await this.usersService.insertUser(
          userName,
          hashedPassword,
        );
        return {
          msg: 'User successfully registered',
          userId: result.id,
          userName: result.username
        };
      }
      //Post / Login
      @UseGuards(LocalAuthGuard)
      @Post('/login')
      login(@Request() req): any {
        return {User: req.user,
                msg: 'User logged in'};
      }
       //Get / protected
      @UseGuards(AuthenticatedGuard)
      @Get('/protected')
      getHello(@Request() req): string {
        return req.user;
      }
       //Get / logout
      @Get('/logout')
        logout(@Request() req): any {
          req.session.destroy();
          return { msg: 'The user session has ended' }
        }
    }

So, once you log out, it returns a message notifying you that the user session has ended. The code for this tutorial is hosted here on my Github repository.

Test Your Application

You have successfully implemented user signup, authentication, and protected the route to enable authorized access only.

It’s time to test the application. If everything is in order, your server should be running. Else, restart your server with the following command:

npm run start:dev

Head over to your Postman. And let’s finally test our application.

Sign Up As a User

Log In As a User

Request the Protected Route

User Logout

Alternatively, Implement User Authentication with LoginRadius

LoginRadius provides a variety of registration and authentication services to assist you in better connecting with your consumers.

On any web or mobile application, LoginRadius is the developer-friendly Identity Platform that delivers a complete set of APIs for authentication, identity verification, single sign-on, user management, and account protection capabilities like multi-factor authentication.

To implement LoginRadius in your NestJS application, follow this tutorial:NestJS User Authentication with LoginRadius API.

Conclusion

¡Felicidades! In this tutorial, you've learned how to implement session-based authentication in a NestJS application with the MongoDB database. You've created and authenticated a user and protected your routes from unauthorized access.

You can access the sample code used in this tutorial on GitHub.

Nota: Session storage is saved by default in 'MemoryStore,' which is not intended for production use. So, while no external datastore is required for development, once in production, a data store such as Redis or another is suggested for stability and performance. You can learn more about session storage here.