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 {
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
bcryptdo 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.