Wprowadzenie
Nie ulega wątpliwości, że uwierzytelnianie ma kluczowe znaczenie w każdej aplikacji lub systemie, jeśli chcesz zabezpieczyć dane użytkownika i umożliwić bezpieczny dostęp do informacji. Uwierzytelnianie to procedura ustalania lub wykazywania, że coś jest prawdziwe, legalne lub ważne.
Wymagania wstępne
Ten samouczek jest praktyczną demonstracją. Aby kontynuować, upewnij się, że masz następujące:
- Node.js działa w twoim systemie, ponieważ NestJS jest frameworkiem Node.js
- MongoDB zainstalowany
Co to jest NestJS?
Nest (NestJS) to framework aplikacji Node.js po stronie serwera do tworzenia skalowalnych, wydajnych aplikacji.
Jest napisany w TypeScript i zbudowany na Express, bardzo minimalistycznej strukturze, która sama w sobie jest świetna, ale brakuje jej struktury. Łączy w sobie paradygmaty programowania, takie jak programowanie obiektowe, programowanie funkcjonalne i programowanie reaktywne.
Jest to framework do wykorzystania, jeśli chcesz mieć dużo struktury na swoim zapleczu. Jego składnia i struktura są bardzo podobne do AngularJS, frameworka front-end. I używa TypeScript, usług i wstrzykiwania zależności w taki sam sposób, jak robi to AngularJS.
Wykorzystuje moduły i kontrolery, a możesz budować kontrolery dla pliku za pomocą interfejsu wiersza poleceń.
Moduły NestJS umożliwiają grupowanie powiązanych kontrolerów i dostawców usług w jeden plik kodu. Mówiąc najprościej, moduł NestJS to plik TypeScript z @Module adnotacja (). Ten dekorator informuje strukturę NestJS o tym, które kontrolery, dostawcy usług i inne powiązane zasoby zostaną utworzone i wykorzystane przez kod aplikacji później.
Co to jest uwierzytelnianie oparte na sesji?
Uwierzytelnianie oparte na sesji to metoda uwierzytelniania użytkownika, w której serwer tworzy sesję po pomyślnym zalogowaniu, z identyfikatorem sesji przechowywanym w pliku cookie lub w pamięci lokalnej w przeglądarce.
Przy kolejnych żądaniach plik cookie jest sprawdzany na podstawie identyfikatora sesji przechowywanego na serwerze. Jeśli istnieje dopasowanie, żądanie jest uważane za prawidłowe i przetwarzane.
Korzystając z tej metody uwierzytelniania, należy pamiętać o następujących sprawdzonych metodach bezpieczeństwa:
- Generuj długie i losowe identyfikatory sesji (128 bitów to zalecana długość), aby ataki brute force były nieskuteczne
- Unikaj przechowywania poufnych lub specyficznych dla użytkownika danych
- Ustaw komunikację HTTPS jako obowiązkową dla wszystkich aplikacji opartych na sesji
- Twórz pliki cookie, które mają atrybuty bezpieczne i tylko HTTP
Dlaczego uwierzytelnianie oparte na sesji?
Uwierzytelnianie oparte na sesji jest bezpieczniejsze niż większość metod uwierzytelniania, ponieważ jest proste, bezpieczne i ma ograniczony rozmiar pamięci. Uważa się również, że jest to najlepsza opcja dla stron internetowych w tej samej domenie głównej.
Konfiguracja projektu
Rozpocznij konfigurację projektu, instalując Nest CLI na całym świecie. Nie musisz tego robić, jeśli masz już zainstalowany NestJS CLI.
Nest CLI to narzędzie interfejsu wiersza poleceń do konfigurowania, rozwijania i utrzymywania aplikacji Nest.
npm i -g @nestjs/cli
Teraz skonfigurujmy projekt, uruchamiając następujące polecenie:
nest new session-based-auth
Powyższe polecenie tworzy aplikację Nest z niektórymi schematami, a następnie wyświetla monit o wybranie preferowanego menedżera pakietów w celu zainstalowania wymaganych modułów do uruchomienia aplikacji. W celu demonstracji ten samouczek używa npm . Naciśnij klawisz Enter, aby kontynuować z npm .
Jeśli wszystko poszło dobrze, na terminalu powinieneś zobaczyć dane wyjściowe, takie jak na poniższym zrzucie ekranu.
Po zakończeniu instalacji przejdź do katalogu projektu i uruchom aplikację za pomocą poniższego polecenia:
npm run start:dev
Powyższe polecenie uruchamia aplikację i obserwuje zmiany. Twój projekt src
struktura folderów powinna wyglądać następująco.
└───src
│ └───app.controller.ts
│ └───app.modules.ts
│ └───app.service.ts
│ └───main.ts
Zainstaluj zależności
Teraz, gdy Twoja aplikacja jest już skonfigurowana, zainstalujmy potrzebne zależności.
npm install --save @nestjs/passport passport passport-local
Powyższe polecenie instaluje Passport.js, popularną bibliotekę uwierzytelniającą nest.js.
Ponadto zainstaluj typy dla strategii za pomocą poniższego polecenia:
Zawiera definicje typów dla passport-local
.
npm install --save-dev @types/passport-local
Skonfiguruj bazę danych MongoDB w NestJS
Aby skonfigurować i połączyć bazę danych, zainstaluj pakiet Mongoose i opakowanie NestJS za pomocą następującego polecenia:
npm install --save @nestjs/mongoose mongoose
Wrapper Mongoose NestJS pomaga używać Mongoose w aplikacji NestJS i zapewnia zatwierdzoną obsługę TypeScript.
Teraz przejdź do swojego app.module.ts
i zaimportuj mongoose
moduł z @nestjs/mongoose
. Następnie wywołaj forRoot()
metody, metody dostarczanej przez moduł Mongoose, i przekaż ciąg adresu URL bazy danych.
Konfigurowanie połączenia z bazą danych w app.module.ts
pomaga aplikacji połączyć się z bazą danych natychmiast po uruchomieniu serwera — po uruchomieniu aplikacji, ponieważ jest to pierwszy moduł do załadowania.
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 {}
Utwórz moduł użytkowników
W przypadku problemów z separacją, aby kod był czysty i dobrze zorganizowany, utwórz moduł specjalnie dla użytkowników korzystających z interfejsu NestJS CLI, uruchamiając następujące polecenie:
nest g module users
Powyższe polecenie tworzy users
folder z users.module.ts
i aktualizuje app.module.ts
Utwórz także users.service.ts
i users.controller.ts
pliki z następującymi poleceniami:
nest g service users
nest g controller users
Pamiętaj, że możesz tworzyć swoje foldery i pliki ręcznie bez użycia zagnieżdżonego interfejsu CLI, ale użycie CLI automatycznie aktualizuje niezbędne foldery i ułatwia życie.
Utwórz schemat użytkownika
Następnym krokiem jest utworzenie własnego schematu użytkownika, ale najpierw dodaj users.model.ts
plik, w którym utworzysz UserSchema
Powinien to być kształt naszej aplikacji src
folder teraz.
└───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
Aby utworzyć UserSchema
, zaimportuj wszystko jako mangusta z pakietu mangusty w users.model.ts
. Następnie wywołaj nowy schemat mangusty, projekt modelu użytkownika i przekaż obiekt JavaScript, w którym zdefiniujesz obiekt i dane użytkownika.
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;
}
Stwórz również interfejs dla swojego modelu, który rozszerza mongoose, dokument, który pomoże ci zapełnić twoje kolekcje MongoDB.
Przejdź do swojego users.module.ts
i zaimportuj MongooseModule
w tablicy importów. Następnie wywołaj forFeature()
metoda dostarczona przez MongooseModule
i przekaż tablicę obiektu, która przyjmuje nazwę i schemat.
Umożliwi to udostępnianie pliku w dowolnym miejscu za pomocą wstrzykiwania zależności.
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 {}
W users.module.ts
, wyeksportuj UsersService
aby umożliwić Ci dostęp do niego w innym module.
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 {}
Zwykle dobrym pomysłem jest zamknięcie logiki biznesowej w osobnej klasie. Taka klasa nazywana jest usługą. Zadaniem tej klasy jest przetwarzanie żądań kontrolera i wykonywanie logiki biznesowej.
W users.service.ts
plik, importuj Model
z mongoose
, User
z users.model.ts
i InjectModel
z @nestjs/mongoose
. Następnie dodaj metodę do UsersService
klasa, która pobiera nazwę użytkownika i hasło oraz wywołuje metodę 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;
}
}
Teraz, gdy UsersService
klasa jest gotowa, musisz ją wstrzyknąć do swojego kontrolera. Ale najpierw porozmawiajmy o bezpiecznym przechowywaniu haseł użytkowników.
Najbardziej krytycznym aspektem procedury rejestracji są hasła użytkowników, których nie wolno zapisywać w postaci zwykłego tekstu. Użytkownik jest odpowiedzialny za stworzenie silnego hasła, ale jako programista masz obowiązek dbać o bezpieczeństwo swoich haseł. W przypadku naruszenia bazy danych hasła użytkowników zostaną ujawnione. A co się stanie, jeśli zostanie zapisany w postaci zwykłego tekstu? Wierzę, że znasz odpowiedź. Aby rozwiązać ten problem, zahaszuj hasła za pomocą bcrypt.
Więc zainstaluj bcrypt
i @types/bcrypt
za pomocą następującego polecenia:
npm install @types/bcrypt bcrypt
Pomijając to, skonfiguruj kontroler. Najpierw zaimportuj UsersService
klasa i wszystko z bcrypt
. Następnie dodaj konstruktor i metodę, która pozwala dodać użytkownika; obsłuży przychodzące żądania poczty, nazwij to addUser
, z treścią funkcji, w której będziesz zaszyfrować hasło.
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
};
}
}
Rejestracja odbywa się w app.module.ts
plik, który uzyskuje się poprzez dodanie UsersModule
do @Module()
tablica importów dekoratora w 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 {}
Gratulacje! Skończyłeś z rejestracją. Możesz teraz zarejestrować użytkownika za pomocą nazwy użytkownika i hasła.
Teraz, bez rejestracji, dodaj getUser
funkcja do Twojego UsersService
z findOne
metoda wyszukiwania użytkownika według nazwy użytkownika.
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;
}
}
Utwórz moduł uwierzytelniania
Podobnie jak w przypadku użytkowników, utwórz moduł uwierzytelniania i usługę specjalnie dla wszystkich uwierzytelnień/weryfikacji. Aby to zrobić, uruchom następujące polecenia:
nest g module auth
nest g service auth
Powyższe spowoduje utworzenie folderu auth, auth.module.ts
i auth.service.ts
i zaktualizuj auth.module.ts
i app.module.ts
pliki.
W tym momencie kształt Twojej aplikacji src
folder powinien wyglądać następująco.
└───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
Powyższe polecenie generowania zaktualizuje Twój app.module.ts
, i będzie wyglądał jak poniższy fragment kodu:
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 {}
Uwierzytelnij użytkowników
Przejdź do swojego auth.module.ts
plik i dodaj UsersModule
w tablicy importów, aby umożliwić dostęp do usługi UsersService
wyeksportowane z users.module.ts
plik.
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 {}
W swoim auth.service.ts
pliku, wywołaj konstruktora, aby móc wstrzyknąć UsersService
i dodaj metodę weryfikacji, która będzie wymagała podania nazwy użytkownika i hasła.
Aby dodać kilka podstawowych walidacji, sprawdź, czy użytkownik istnieje w bazie danych i porównaj podane hasło z hasłem w Twojej bazie danych, aby upewnić się, że jest zgodne. Jeśli istnieje, zwróć użytkownika w request.user
object — else, zwróć 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;
}
}
Idąc dalej, utwórz nowy plik i nazwij go local.strategy.ts
. Ten plik będzie reprezentował strategię z Passport.js
, którą zainstalowałeś wcześniej, czyli local strategy
. I w nim podaj strategię, którą jest Strategy
z passport-local
.
Utwórz konstruktor i wstrzyknij AuthService
, wywołaj super()
metoda; upewnij się, że wywołasz super()
metoda.
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;
}
}
Wróć do swojego auth.module.ts
plik. Następnie dodaj PassportModule
do importów i LocalStrategy
dostawcom.
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 {}
Teraz dodaj ścieżkę logowania do swojego 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'};
}
}
Teraz, gdy masz już wszystkie te elementy, nadal nie możesz zalogować się do użytkownika, ponieważ nic nie uruchamia ścieżki logowania. Tutaj użyj Strażników, aby to osiągnąć.
Utwórz plik i nazwij go local.auth.guard.ts
, a następnie klasa LocalAuthGuard
która rozszerza AuthGuard
z NestJS/passport
, gdzie podasz nazwę strategii i przekażesz nazwę swojej strategii, local
.
local.auth.guard.ts.
import { Injectable } from "@nestjs/common"
import { AuthGuard } from "@nestjs/passport"
@Injectable()
export class LocalAuthGuard extends AuthGuard("local") {}
Dodaj UseGuard
dekorator do ścieżki logowania w users.controller.ts
i przekaż 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'};
}
}
Na koniec możesz zalogować użytkownika za pomocą zarejestrowanej nazwy użytkownika i hasła.
Chroń trasy uwierzytelniania
Udało Ci się skonfigurować uwierzytelnianie użytkownika. Teraz chroń swoje trasy przed nieautoryzowanym dostępem, ograniczając dostęp tylko do uwierzytelnionych użytkowników. Przejdź do swojego users.controller.ts
i dodaj kolejną trasę — nazwij ją „zabezpieczoną” i spraw, aby zwracała req.user
obiekt.
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;
}
}
Chroniona trasa w powyższym kodzie zwróci pusty obiekt zamiast zwracać dane użytkownika, gdy zalogowany użytkownik wyśle do niego żądanie, ponieważ utracił już login.
Aby to uporządkować, tutaj pojawia się uwierzytelnianie oparte na sesji.
W uwierzytelnianiu sesyjnym, gdy użytkownik się loguje, jest on zapisywany w sesji, dzięki czemu każde kolejne żądanie użytkownika po zalogowaniu pobiera szczegóły z sesji i zapewnia użytkownikowi łatwy dostęp. Sesja wygasa, gdy użytkownik się wyloguje.
Aby rozpocząć uwierzytelnianie oparte na sesji, zainstaluj express-session i typy NestJS za pomocą następującego polecenia:
npm install express-session @types/express-session
Po zakończeniu instalacji przejdź do swojego main.ts
plik, katalog główny aplikacji i tam dokonaj konfiguracji.
Importuj wszystko z passport
i express-session
, a następnie dodaj inicjalizację paszportu i sesję paszportową.
Lepiej jest zachować tajny klucz w zmiennych środowiskowych.
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()
Dodaj nowy plik, authenticated.guard.ts
, w Twoim auth
teczka. I utwórz nowego Strażnika, który sprawdzi, czy istnieje sesja dla użytkownika zgłaszającego żądanie — nazwij go 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()
}
}
W powyższym kodzie żądanie jest pobierane z kontekstu i sprawdzane, czy zostało uwierzytelnione. isAuthenticated()
pochodzi z passport.js
automatycznie; to mówi. "hej! czy istnieje sesja dla tego użytkownika? Jeśli tak, kontynuuj."
Aby uruchomić logowanie, w swoim users.controller.ts
plik:
- importuj
authenticated
zauthenticated.guard.ts
; - dodaj
useGuard
dekorator doprotected
trasa; i, - przejdź w
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;
}
}
W tym momencie nadal się nie udaje, ponieważ skonfigurowałeś tylko express-session
ale go nie zaimplementowałem.
Kiedy użytkownik się loguje, musisz zapisać go w sesji, aby mógł uzyskać dostęp do innych tras w sesji.
Należy pamiętać, że domyślnie express-session
biblioteka przechowuje sesję w pamięci serwera WWW.
Zanim przejdzie do sesji, musisz zserializować użytkownika. Po wyjściu z sesji zdeserializuj użytkownika.
Dlatego utwórz nowy plik w folderze auth dla serializatora i deserializatora, nazwij go session.serializer.ts
.
W tym momencie kształt naszej aplikacji src
folder powinien wyglądać tak.
└───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)
}
}
Wróć do swojego auth.module.ts
pliku, podaj SessionSerializer
i dodaj register
metodę do 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
plik.
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()
method. 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
Congratulations! 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.