Bezpieczeństwo i Autoryzacja w NestJS

NestJS, jako framework oparty na Node.js, oferuje solidne rozwiązania do tworzenia skalowalnych i bezpiecznych aplikacji backendowych. Jednym z kluczowych aspektów aplikacji webowych jest autoryzacja i bezpieczeństwo. W tym artykule przyjrzymy się, jak skonfigurować autoryzację z użyciem JWT (JSON Web Tokens) oraz zabezpieczyć aplikację, stosując odpowiednie narzędzia i najlepsze praktyki.

Autoryzacja za pomocą JWT

JWT (JSON Web Token) jest popularnym mechanizmem uwierzytelniania opartym na tokenach, używanym do przekazywania informacji między dwiema stronami. Stosowanie JWT w aplikacji NestJS umożliwia autoryzację użytkowników w sposób efektywny i bezpieczny. Poniżej pokażemy krok po kroku, jak skonfigurować JWT do autoryzacji w NestJS.

1. Instalacja potrzebnych pakietów

Najpierw musimy zainstalować potrzebne zależności:

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

				
			

Dzięki @nestjs/jwt i passport możemy zintegrować JWT z aplikacją NestJS, natomiast bcrypt pozwoli nam na bezpieczne hashowanie haseł użytkowników.

2. Tworzenie modułu autoryzacji

Najpierw stworzymy moduł, który będzie odpowiedzialny za autoryzację:

				
					import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';
import { UsersModule } from '../users/users.module';

@Module({
  imports: [
    UsersModule,
    PassportModule.register({ defaultStrategy: 'jwt' }),
    JwtModule.register({
      secret: 'superSecretKey', // Najlepiej, aby ta wartość była trzymana w zmiennych środowiskowych
      signOptions: { expiresIn: '60s' },
    }),
  ],
  providers: [AuthService, JwtStrategy],
  exports: [AuthService],
})
export class AuthModule {}

				
			

W powyższym przykładzie konfigurowana jest strategia JWT z kluczem tajnym (secret) oraz czasem ważności tokenu (expiresIn). W rzeczywistej aplikacji klucz ten powinien być przechowywany w bezpieczny sposób, najlepiej w zmiennych środowiskowych.

3. Tworzenie serwisu do autoryzacji

Kolejnym krokiem jest implementacja serwisu, który będzie odpowiedzialny za logikę autoryzacji:

				
					import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';

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

  async validateUser(username: string, pass: string): Promise<any> {
    const user = await this.usersService.findOne(username);
    if (user && user.password === pass) {
      const { password, ...result } = user;
      return result;
    }
    return null;
  }

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

				
			

Serwis autoryzacyjny AuthService wykorzystuje JwtService do generowania tokenów JWT, a serwis użytkownika (UsersService) do wyszukiwania użytkowników.

4. Tworzenie kontrolera do rejestracji i logowania

Aby użytkownicy mogli się logować i rejestrować, stworzymy odpowiednie endpointy:

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

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

  @Post('login')
  async login(@Body() req) {
    return this.authService.login(req);
  }
}

				
			

Endpoint /auth/login pozwala użytkownikowi na zalogowanie się, co w efekcie zwraca token JWT.

5. Strategia JWT

Tworzymy klasę JwtStrategy, która będzie sprawdzać poprawność tokenów:

				
					import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: 'superSecretKey',
    });
  }

  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username };
  }
}

				
			

Strategia JWT pobiera token z nagłówka Authorization i weryfikuje jego poprawność.

Bezpieczeństwo aplikacji

Po skonfigurowaniu autoryzacji JWT warto zadbać o bezpieczeństwo całej aplikacji. NestJS oferuje kilka narzędzi, które pomagają chronić aplikację przed różnymi rodzajami ataków.

1. Helmet – ochrona przed atakami XSS

Helmet to middleware zabezpieczający aplikację poprzez ustawienie odpowiednich nagłówków HTTP, które chronią przed atakami typu XSS, clickjacking, oraz ograniczają inne luki w bezpieczeństwie. Instalacja Helmet jest bardzo prosta:

				
					npm install helmet

				
			

Następnie należy dodać Helmet do głównej aplikacji NestJS:

				
					import * as helmet from 'helmet';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(helmet());
  await app.listen(3000);
}
bootstrap();

				
			

Helmet dodaje odpowiednie nagłówki, które zmniejszają ryzyko ataków XSS i innych rodzajów zagrożeń.

2. Rate Limiter – ochrona przed atakami typu DoS

Rate limiting to technika, która ogranicza liczbę żądań, jakie użytkownik może wysłać w określonym czasie, co jest przydatne do zapobiegania atakom typu Denial of Service (DoS). Aby zaimplementować rate limiter w NestJS, należy zainstalować express-rate-limit:

				
					npm install express-rate-limit

				
			

Następnie dodajemy rate limiter do aplikacji:

				
					import * as rateLimit from 'express-rate-limit';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minut
    max: 100, // maksymalnie 100 żądań na 15 minut na IP
  }));
  await app.listen(3000);
}
bootstrap();

				
			

Rate limiter zapobiega przeciążeniu serwera poprzez ograniczenie liczby żądań od jednego użytkownika.

3. Najlepsze praktyki bezpieczeństwa

Kilka ogólnych wskazówek dotyczących bezpieczeństwa aplikacji:

  • Hashowanie haseł: Używaj bcrypt do hashowania haseł przed zapisaniem ich w bazie danych.
  • Trzymanie kluczy w bezpieczny sposób: Nigdy nie przechowuj kluczy tajnych bezpośrednio w kodzie. Zamiast tego używaj zmiennych środowiskowych.
  • Uwierzytelnienie i autoryzacja: Upewnij się, że odpowiednie endpointy są chronione za pomocą mechanizmów takich jak JWT.

Podsumowanie

Bezpieczeństwo i autoryzacja są kluczowymi elementami każdej aplikacji webowej. W artykule pokazaliśmy, jak skonfigurować autoryzację za pomocą JWT w NestJS oraz jak zabezpieczyć aplikację przed atakami typu XSS, CSRF i DoS. Warto pamiętać, że bezpieczeństwo to proces, który wymaga stałej uwagi i regularnych przeglądów, aby aplikacja była odporna na nowe zagrożenia.

Dzięki narzędziom takim jak JWT, Helmet oraz rate limiter, NestJS pozwala tworzyć bezpieczne i skalowalne aplikacje, które skutecznie chronią dane użytkowników.