MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

NestJS:Como implementar a autenticação de usuário baseada em sessão

Introdução


É uma realidade indiscutível que a autenticação é fundamental em qualquer aplicativo ou sistema se você deseja proteger os dados do usuário e permitir o acesso seguro às informações. Autenticação é o procedimento de estabelecer ou demonstrar que algo é verdadeiro, legítimo ou válido.

Pré-requisitos


Este tutorial é uma demonstração prática. Para acompanhar, verifique se você possui o seguinte:
  • Node.js em execução no seu sistema porque NestJS é uma estrutura Node.js
  • MongoDB instalado

O que é NestJS?


Nest (NestJS) é uma estrutura de aplicativo do lado do servidor Node.js para criar aplicativos escaláveis ​​e eficientes.

Ele é escrito em TypeScript e construído em Express, uma estrutura muito minimalista que é ótima por si só, mas carece de estrutura. Ele combina paradigmas de programação como programação orientada a objetos, programação funcional e programação funcional reativa.

É um framework para usar se você quiser muita estrutura em seu backend. Sua sintaxe e estrutura são muito semelhantes ao AngularJS, um framework front-end. E usa TypeScript, serviços e injeção de dependência da mesma forma que o AngularJS.

Ele emprega módulos e controladores, e você pode construir controladores para um arquivo usando a interface de linha de comando.

Os módulos NestJS permitem agrupar controladores e provedores de serviços relacionados em um único arquivo de código. Simplificando, um módulo NestJS é um arquivo TypeScript com o @Module anotação (). Esse decorador informa à estrutura NestJS sobre quais controladores, provedores de serviços e outros recursos associados serão instanciados e usados ​​pelo código do aplicativo posteriormente.

O que é autenticação baseada em sessão?


A autenticação baseada em sessão é um método de autenticação de usuário no qual o servidor cria uma sessão após um login bem-sucedido, com o ID da sessão armazenado em um cookie ou armazenamento local em seu navegador.

Em solicitações subsequentes, seu cookie é validado em relação ao ID de sessão armazenado no servidor. Se houver uma correspondência, a solicitação é considerada válida e processada.

Ao usar esse método de autenticação, é essencial manter em mente as seguintes práticas recomendadas de segurança:
  • Gere IDs de sessão longos e aleatórios (128 bits é o tamanho recomendado) para tornar os ataques de força bruta ineficazes
  • Evite armazenar dados confidenciais ou específicos do usuário
  • Tornar as comunicações HTTPS obrigatórias para todos os aplicativos baseados em sessão
  • Crie cookies com atributos seguros e somente HTTP

Por que autenticação baseada em sessão?


A autenticação baseada em sessão é mais segura do que a maioria dos métodos de autenticação porque é simples, segura e tem um tamanho de armazenamento limitado. Também é considerado a melhor opção para sites no mesmo domínio raiz.

Configuração do projeto


Inicie a configuração do seu projeto instalando a Nest CLI globalmente. Você não precisa fazer isso se já tiver o NestJS CLI instalado.

A Nest CLI é uma ferramenta de interface de linha de comando para configurar, desenvolver e manter aplicativos Nest.
npm i -g @nestjs/cli

Agora, vamos configurar seu projeto executando o seguinte comando:
nest new session-based-auth

O comando acima cria um aplicativo Nest com alguns clichês e, em seguida, solicita que você escolha seu gerenciador de pacotes preferido para instalar os módulos necessários para executar seu aplicativo. Para demonstração, este tutorial usa npm . Pressione a tecla Enter para continuar com npm .



Se tudo correu bem, você deverá ver uma saída como a da captura de tela abaixo em seu terminal.



Quando a instalação estiver concluída, vá para o diretório do seu projeto e execute o aplicativo com o comando abaixo:
npm run start:dev

O comando acima executa o aplicativo e observa as alterações. Seu projeto src estrutura de pastas deve ter a seguinte aparência.
└───src
│   └───app.controller.ts
│   └───app.modules.ts
│   └───app.service.ts
│   └───main.ts

Instalar dependências


Agora que seu aplicativo está configurado, vamos instalar as dependências necessárias.
npm install --save @nestjs/passport passport passport-local

O comando acima instala o Passport.js, uma biblioteca de autenticação Nest.js popular.

Além disso, instale os tipos para a estratégia com o comando abaixo:

Ele contém definições de tipo para passport-local .
npm install --save-dev @types/passport-local

Configurar o banco de dados MongoDB no NestJS


Para configurar e conectar seu banco de dados, instale o pacote Mongoose e o wrapper NestJS com o seguinte comando:
npm install --save @nestjs/mongoose mongoose

O wrapper Mongoose NestJS ajuda você a usar o Mongoose no aplicativo NestJS e oferece suporte a TypeScript aprovado.

Agora, vá para o seu app.module.ts e importe o mongoose módulo de @nestjs/mongoose . Então chame o forRoot() método, um método fornecido pelo módulo Mongoose, e passe sua string de URL do banco de dados.

Configurando sua conexão de banco de dados em app.module.ts ajuda seu aplicativo a se conectar ao banco de dados imediatamente assim que o servidor é iniciado - depois de executar seu aplicativo, pois é o primeiro módulo a ser carregado.

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 {}

Criar módulo de usuários


Por questões de separação, para tornar seu código limpo e bem organizado, crie um módulo especificamente para usuários que usam a CLI do NestJS executando o seguinte comando:
nest g module users

O comando acima cria um users pasta com users.module.ts e atualiza app.module.ts

Além disso, crie users.service.ts e users.controller.ts arquivos com os seguintes comandos:
nest g service users
nest g controller users

Observe que você pode criar suas pastas e arquivos manualmente sem usar a CLI Nest, mas usar a CLI atualiza automaticamente as pastas necessárias e facilita sua vida.

Criar esquema de usuário


O próximo passo é criar seu UserSchema, mas primeiro, adicione um users.model.ts arquivo, onde você criará UserSchema

Esta deve ser a forma do nosso aplicativo src pasta agora.
└───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 criar UserSchema , importe tudo como mangusto do pacote mangusto em users.model.ts . Em seguida, chame o novo esquema do mangusto, um blueprint do modelo do usuário, e passe um objeto JavaScript no qual você definirá o objeto e os dados do usuário.

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;
}

Além disso, crie uma interface para seu modelo que estenda o mongoose, um documento que ajuda a preencher suas coleções do MongoDB.

Vá para o seu users.module.ts e importe MongooseModule na matriz de importações. Então chame o forFeature() método fornecido por MongooseModule , e passe em uma matriz de objeto que recebe nome e esquema.

Isso permitirá que você compartilhe o arquivo em qualquer lugar com a ajuda da injeção de dependência.

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 {}

Em users.module.ts , exporte o UsersService para que você possa acessá-lo em outro 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 {}

Geralmente, é uma boa ideia encapsular a lógica de negócios em uma classe separada. Essa classe é conhecida como um serviço. O trabalho dessa classe é processar as solicitações do controlador e executar a lógica de negócios.

Em users.service.ts arquivo, importe Model de mongoose , User de users.model.ts e InjectModel de @nestjs/mongoose . Em seguida, adicione um método ao UsersService classe que recebe um nome de usuário e senha e chama o 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;
  }
}

Agora que o UsersService classe está pronta, você precisa injetá-la em seu controlador. Mas primeiro, vamos falar sobre como armazenar as senhas dos usuários de forma segura.

O aspecto mais crítico do procedimento de registro são as senhas dos usuários, que não devem ser salvas em texto simples. É responsabilidade do usuário criar uma senha forte, mas é sua obrigação como desenvolvedor manter suas senhas seguras. Se ocorrer uma violação do banco de dados, as senhas dos usuários serão expostas. E o que acontece se for armazenado em texto simples? Acredito que você saiba a resposta. Para resolver isso, faça o hash das senhas usando bcrypt.

Então, instale o bcrypt e @types/bcrypt com o seguinte comando:
npm install @types/bcrypt bcrypt

Com isso fora do caminho, configure seu controlador. Primeiro, importe seu UsersService class e tudo de bcrypt . Em seguida, adicione um construtor e um método que permita adicionar um usuário; ele lidará com solicitações de postagem recebidas, chame-o de addUser , com um corpo de função onde você fará o hash da senha.

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
    };
  }
}

O registro acontece no app.module.ts arquivo, que é obtido adicionando o UsersModule para o @Module() array de importações do decorador em 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 {}

Parabéns! Você está feito com o registro. Agora você pode registrar um usuário com um nome de usuário e senha.

Agora, com o registro fora do caminho, adicione um getUser função para o seu UsersService com o findOne método para encontrar um usuário pelo nome de usuário.

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;
  }
}

Criar módulo de autenticação


Assim como para os usuários, crie um módulo e serviço de autenticação especificamente para todas as autenticações/verificações. Para fazer isso, execute os seguintes comandos:
nest g module auth
nest g service auth

O acima criará uma pasta de autenticação, auth.module.ts e auth.service.ts e atualize o auth.module.ts e app.module.ts arquivos.

Neste ponto, a forma do seu aplicativo src pasta deve ter a seguinte aparência.
└───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

O comando de geração acima atualizará seu app.module.ts , e será semelhante ao snippet de código abaixo:

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 usuários


Vá para o seu auth.module.ts arquivo e adicione UsersModule na matriz de importações para habilitar o acesso ao UsersService exportado do users.module.ts Arquivo.

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 {}

Em seu auth.service.ts arquivo, chame o construtor para que você possa injetar o UsersService , e adicione um método de validação que terá um nome de usuário e senha.

Para adicionar algumas validações básicas, verifique se o usuário existe no banco de dados e compare a senha fornecida com a do seu banco de dados para garantir que ela corresponda. Se existir, retorne o usuário no request.user object — senão, retorna null.

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;
      }
    }

Indo além, crie um novo arquivo e nomeie-o local.strategy.ts . Este arquivo representará a estratégia de Passport.js , que você instalou anteriormente, essa é a local strategy . E dentro dele, passe a estratégia, que é a Strategy de passport-local .

Crie um construtor e injete o AuthService , chame o super() método; certifique-se de chamar o 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;
      }
    }

Volte para o seu auth.module.ts Arquivo. Em seguida, adicione PassportModule para importações e LocalStrategy aos provedores.

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 {}

Agora, adicione a rota de login ao seu 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'};
      }
    }

Agora que você tem tudo isso implementado, você ainda não pode fazer login em um usuário porque não há nada para acionar a rota de login. Aqui, use Guardas para conseguir isso.

Crie um arquivo e nomeie-o local.auth.guard.ts , então uma classe LocalAuthGuard que estende AuthGuard de NestJS/passport , onde você fornecerá o nome da estratégia e passará o nome de sua estratégia, local .

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

Adicione o UseGuard decorador para sua rota de login no users.controller.ts arquivo e passe o 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, você pode fazer o login de um usuário com um nome de usuário e senha registrados.

Proteger Rotas de Autenticação


Você configurou com sucesso a autenticação do usuário. Agora, proteja suas rotas de acesso não autorizado limitando o acesso apenas a usuários autenticados. Acesse seu users.controller.ts arquivo e adicione outra rota - nomeie-a como 'protegida' e faça-a retornar o 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;
      }
    }

A rota protegida no código acima retornará um objeto vazio em vez de retornar os detalhes do usuário quando um usuário logado fizer uma solicitação a ele porque já perdeu o login.

Para resolver isso, é aqui que entra a autenticação baseada em sessão.

Na autenticação baseada em sessão, quando um usuário efetua login, o usuário é salvo em uma sessão para que qualquer solicitação subsequente do usuário após o login obtenha os detalhes da sessão e conceda acesso fácil ao usuário. A sessão expira quando o usuário faz logout.

Para iniciar a autenticação baseada em sessão, instale a sessão expressa e os tipos NestJS usando o seguinte comando:
npm install express-session @types/express-session

Quando a instalação estiver concluída, vá para o seu main.ts file, a raiz da sua aplicação, e faça as configurações lá.

Importe tudo do passport e express-session , em seguida, adicione a inicialização do passaporte e a sessão do passaporte.

É preferível manter sua chave secreta em suas variáveis ​​de ambiente.

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()

Adicione um novo arquivo, authenticated.guard.ts , em seu auth pasta. E crie um novo Guard que verifique se há uma sessão para o usuário que está fazendo a solicitação - nomeie-o 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()
  }
}

No código acima, a solicitação é obtida do contexto e verificada se autenticada. isAuthenticated() vem de passport.js automaticamente; diz. "hey! existe uma sessão para este usuário? Se sim, continue."

Para acionar o login, em seu users.controller.ts Arquivo:
  • importar authenticated de authenticated.guard.ts;
  • adicione o useGuard decorador para o protected rota; e,
  • passar 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;
      }
    }

Neste ponto, ainda falha porque você configurou apenas express-session mas não implementou.

Quando um usuário efetua login, você precisa salvá-lo em uma sessão para que o usuário possa acessar outras rotas com a sessão.

Uma coisa a ter em mente é que, por padrão, a express-session biblioteca armazena a sessão na memória do servidor web.

Antes de entrar na sessão, você precisa serializar o usuário. Ao sair da sessão, desserialize o usuário.

Então, crie um novo arquivo na pasta auth para serializador e desserializador, nomeie-o session.serializer.ts .

Neste ponto, a forma do nosso aplicativo src pasta deve ficar assim.
    └───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)
  }
}

Volte para o seu auth.module.ts arquivo, forneça o SessionSerializer e adicione o register método para o 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 {}

Adicione alguns códigos dentro do LocalAuthGuard no local.auth.guard.ts Arquivo.

Chame o login método em super e passe a solicitação para acionar o login real criando uma sessão. 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


Parabéns! 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.

Note: 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.