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
deauthenticated.guard.ts
; - adicione o
useGuard
decorador para oprotected
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
Logged-in User’s Cookie ID
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.