Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Session Management i Cookie Auth - alternatywne drogi do fortu

Strażniku bram Imperium! Architekt Vitruvius pokazuje ci alternatywną metodę kontroli dostępu do fortu. Dotychczas poznałeś JWT - przepustki, które legionariusz nosi przy sobie. Teraz nauczysz się Session Management - systemu, w którym to fort prowadzi rejestr kto aktualnie przebywa w środku!

Session-based vs JWT Authentication

W świecie legionów mamy dwa podejścia do kontroli dostępu:

JWT (stateless - bezstanowe)

  • Legionariusz nosi przepustkę (token) przy sobie
  • Fort nie musi nic pamiętać - sprawdza tylko podpis przepustki
  • Działa niezależnie między różnymi fortami (skalowanie)

Session-based (stateful - stanowe)

  • Fort zapisuje w swoim rejestrze, kto jest w środku
  • Legionariusz dostaje tylko numer wpisu (Session ID) w ciasteczku
  • Fort musi sprawdzić swój rejestr przy każdym żądaniu
1// Porownanie JWT vs Session
2// JWT: token przechowywany po stronie klienta
3const jwtApproach = {
4 storage: 'klient (localStorage/cookie)',
5 serverState: 'brak - stateless',
6 scalability: 'latwe skalowanie - kazdy serwer weryfikuje token',
7 revocation: 'trudne - token wazny do wygasniecia',
8};
9
10// Session: dane przechowywane na serwerze
11const sessionApproach = {
12 storage: 'serwer (pamiec/Redis/MongoDB)',
13 serverState: 'sesja w storage',
14 scalability: 'wymaga wspoldzielonego storage',
15 revocation: 'latwe - usun sesje z serwera',
16};

Konfiguracja express-session w NestJS

Instalacja

1npm install express-session
2npm install -D @types/express-session
3# Opcjonalnie - session store dla produkcji:
4npm install connect-redis ioredis
5npm install connect-mongo

Konfiguracja w main.ts

1// main.ts
2import { NestFactory } from '@nestjs/core';
3import { AppModule } from './app.module';
4import * as session from 'express-session';
5
6async function bootstrap() {
7 const app = await NestFactory.create(AppModule);
8
9 // Konfiguracja session
10 app.use(
11 session({
12 secret: process.env.SESSION_SECRET || 'roma-aeterna-secret',
13 resave: false,          // Nie zapisuj sesji jesli nie zmieniona
14 saveUninitialized: false, // Nie twórz pustych sesji
15 cookie: {
16 httpOnly: true,        // Niedostepne z JavaScript
17 secure: process.env.NODE_ENV === 'production', // HTTPS w produkcji
18 maxAge: 24 * 60 * 60 * 1000, // 24 godziny
19 sameSite: 'strict',    // Ochrona przed CSRF
20 },
21 name: 'legion.sid',      // Nazwa ciasteczka
22 }),
23 );
24
25 await app.listen(3000);
26}
27bootstrap();

Dekorator @Session() w NestJS

NestJS udostępnia dekorator

@Session()
do odczytu i zapisu danych sesji:

1// auth/session-auth.controller.ts
2import { Controller, Post, Get, Body, Session, HttpCode } from '@nestjs/common';
3import { AuthService } from './auth.service';
4
5@Controller('auth')
6export class SessionAuthController {
7 constructor(private authService: AuthService) {}
8
9 @Post('login')
10 @HttpCode(200)
11 async login(
12 @Body() loginDto: { username: string; password: string },
13 @Session() session: Record<string, any>,
14 ) {
15 const user = await this.authService.validateUser(
16 loginDto.username,
17 loginDto.password,
18 );
19
20 // Zapisz dane uzytkownika w sesji
21 session.userId = user.id;
22 session.username = user.username;
23 session.role = user.role;
24 session.loginAt = new Date().toISOString();
25
26 return {
27 message: \`Witaj w forcie, \${user.username}!\`,
28 user: { id: user.id, username: user.username, role: user.role },
29 };
30 }
31
32 @Get('profile')
33 async getProfile(@Session() session: Record<string, any>) {
34 if (!session.userId) {
35 throw new UnauthorizedException('Brak aktywnej sesji!');
36 }
37
38 return {
39 userId: session.userId,
40 username: session.username,
41 role: session.role,
42 loginAt: session.loginAt,
43 };
44 }
45
46 @Post('logout')
47 @HttpCode(200)
48 async logout(@Session() session: Record<string, any>) {
49 const username = session.username;
50
51 // Zniszcz sesje
52 session.destroy((err) => {
53 if (err) console.error('Blad niszczenia sesji:', err);
54 });
55
56 return { message: \`Do zobaczenia, \${username}!\` };
57 }
58}

Session Store - przechowywanie sesji

W produkcji nie mozna przechowywac sesji w pamieci (gubiane przy restartach). Popularne opcje:

Redis Session Store

1// main.ts z Redis
2import * as session from 'express-session';
3import RedisStore from 'connect-redis';
4import { Redis } from 'ioredis';
5
6const redisClient = new Redis({
7 host: process.env.REDIS_HOST || 'localhost',
8 port: parseInt(process.env.REDIS_PORT) || 6379,
9});
10
11app.use(
12 session({
13 store: new RedisStore({ client: redisClient }),
14 secret: process.env.SESSION_SECRET,
15 resave: false,
16 saveUninitialized: false,
17 cookie: {
18 httpOnly: true,
19 secure: process.env.NODE_ENV === 'production',
20 maxAge: 24 * 60 * 60 * 1000,
21 },
22 }),
23);

MongoDB Session Store

1// main.ts z MongoDB
2import * as session from 'express-session';
3import MongoStore from 'connect-mongo';
4
5app.use(
6 session({
7 store: MongoStore.create({
8 mongoUrl: process.env.DATABASE,
9 collectionName: 'sessions',
10 ttl: 24 * 60 * 60, // 24 godziny
11 }),
12 secret: process.env.SESSION_SECRET,
13 resave: false,
14 saveUninitialized: false,
15 }),
16);

Session Guard

1// auth/guards/session.guard.ts
2import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
3
4@Injectable()
5export class SessionGuard implements CanActivate {
6 canActivate(context: ExecutionContext): boolean {
7 const request = context.switchToHttp().getRequest();
8 const session = request.session;
9
10 if (!session || !session.userId) {
11 throw new UnauthorizedException(
12 'Sesja wygasla lub nie istnieje - zaloguj sie ponownie!'
13 );
14 }
15
16 // Dodaj dane uzytkownika do request
17 request.user = {
18 id: session.userId,
19 username: session.username,
20 role: session.role,
21 };
22
23 return true;
24 }
25}

Kiedy uzyc JWT, a kiedy Session?

| Aspekt | JWT | Session | |--------|-----|---------| | Skalowanie | Latwe (stateless) | Wymaga shared store | | Uniewaznanie | Trudne | Latwe (usun z bazy) | | Rozmiar | Wieksze ciasteczko | Maly Session ID | | Microservices | Idealne | Trudniejsze | | SPA + API | Idealne | Mozliwe | | SSR (Next.js) | Mozliwe | Naturalne | | Mobile apps | Idealne | Mniej wygodne |

Kazde podejscie ma swoje zalety - w Imperium Rzymskim warto znac oba sposoby strazowania dostepy do fortu!

Ir a CodeWorlds