Files
GKNB_MSTM071/Backend/harmadik gyakorlat/README.md
T
2026-02-25 20:16:03 +01:00

17 KiB

Backend Fejlesztés Gyakorló Feladat

Authentication & Authorization Implementáció


📋 Tartalom

  1. Projekt Áttekintés
  2. Architektúra
  3. Előkészületek
  4. A Feladat
  5. Implementációs Útmutató
  6. Tesztelés
  7. Hasznos Források

🎯 Projekt Áttekintés

Ez egy gyakorló projekt backend fejlesztők számára, amely az Authentication és Authorization implementálására fókuszál. A projekt egy blog platformot szimulál, ahol a felhasználók regisztrálhatnak, bejelentkezhetnek, és blogokat írhatnak.

Technológiai Stack

  • Node.js + Express.js - Backend framework
  • Prisma ORM - Database ORM
  • PostgreSQL - Relációs adatbázis
  • Redis - Cache és session management
  • JWT - Token-based authentication
  • bcrypt - Jelszó hash-elés
  • Cookie-parser - Cookie kezelés
  • Docker - Konténerizáció

Amit MEG KELL implementálni (FELADAT):

Authentication (Hitelesítés)

  • Regisztráció
  • Bejelentkezés
  • Kijelentkezés
  • Token refresh
  • Current user lekérés

Authorization (Jogosultság kezelés)

  • Authentication middleware
  • Role-based access control (RBAC)
  • Resource ownership ellenőrzés

Amit NEM kell implementálni (már kész):

Prisma schema és migrációk Repository pattern implementáció CQRS command/query handlers Blog CRUD műveletek Docker konfiguráció Projekt struktúra


🏗️ Architektúra

A projekt Clean Architecture és CQRS mintákat követ:

src/
├── domain/              # Domain réteg (business logic)
│   ├── models/         # Domain modellek
│   │   ├── User.js
│   │   └── Blog.js
│   └── repositories/   # Repository interfészek
│       ├── IUserRepository.js
│       └── IBlogRepository.js
│
├── application/        # Application réteg (use cases)
│   ├── commands/       # Command objektumok
│   │   ├── CreateBlogCommand.js
│   │   ├── UpdateBlogCommand.js
│   │   └── DeleteBlogCommand.js
│   ├── queries/        # Query objektumok
│   │   └── BlogQueries.js
│   └── handlers/       # Command/Query handlerek
│       ├── CreateBlogHandler.js
│       ├── UpdateBlogHandler.js
│       ├── DeleteBlogHandler.js
│       └── BlogQueryHandler.js
│
├── infrastructure/     # Infrastructure réteg (külső függőségek)
│   ├── database/       # Database kapcsolatok
│   │   ├── prisma.js
│   │   └── redis.js
│   ├── repositories/   # Repository implementációk
│   │   ├── UserRepository.js
│   │   └── BlogRepository.js
│   └── config/         # Konfigurációk
│       └── index.js
│
├── api/                # API réteg (HTTP)
│   ├── controllers/    # Controller-ek
│   │   ├── AuthController.js      # ⚠️ IMPLEMENTÁLANDÓ
│   │   └── BlogController.js
│   ├── routes/         # Route definíciók
│   │   ├── authRoutes.js
│   │   ├── blogRoutes.js
│   │   └── userRoutes.js
│   └── middlewares/    # Middleware-ek
│       ├── authMiddleware.js      # ⚠️ IMPLEMENTÁLANDÓ
│       └── errorHandler.js
│
└── index.js            # Alkalmazás belépési pont

Rétegek Felelősségei

Domain réteg:

  • Domain modellek és üzleti logika
  • Repository interfészek (dependency inversion)

Application réteg:

  • Use case-ek implementációja
  • Command/Query objektumok és handlerek
  • Validációs logika

Infrastructure réteg:

  • Külső rendszerek integrációja (DB, Redis)
  • Repository implementációk
  • Konfigurációk

API réteg:

  • HTTP kérések kezelése
  • Routing
  • Middleware-ek
  • Authentication & Authorization ⚠️

🚀 Előkészületek

1. Függőségek telepítése

npm install

2. Környezeti változók beállítása

Másold le a .env.example fájlt .env néven:

cp .env.example .env

Állítsd be a környezeti változókat .env fájlban:

DATABASE_URL="postgresql://postgres:postgres@localhost:5432/blog_db?schema=public"
REDIS_HOST=localhost
REDIS_PORT=6379

JWT_SECRET=valami-nagyon-titkos-kulcs-ide
JWT_EXPIRES_IN=7d
JWT_REFRESH_SECRET=masik-nagyon-titkos-kulcs-ide
JWT_REFRESH_EXPIRES_IN=30d

PORT=3000
NODE_ENV=development

COOKIE_SECRET=cookie-titkos-kulcs-ide

⚠️ FONTOS: Production környezetben használj erős, random generált kulcsokat!

3. Docker konténerek indítása

npm run docker:up

Ez elindítja a PostgreSQL és Redis konténereket.

4. Adatbázis migráció

npm run prisma:migrate
npm run prisma:generate

5. Projekt indítása (development)

npm run dev

A szerver elindul a http://localhost:3000 címen.

Ellenőrizd a health endpoint-ot:

curl http://localhost:3000/health

📝 A Feladat

A feladatod három fő komponens implementálása:

1️⃣ AuthController implementálása

Fájl: src/api/controllers/AuthController.js

Implementálandó metódusok:

register(req, res)

  • Új felhasználó regisztrációja
  • Input validáció (email, username, password)
  • Uniqueness ellenőrzés (email és username)
  • Jelszó hash-elés bcrypt-tel
  • User létrehozása repository-n keresztül
  • JWT token generálás
  • HTTP-only cookie beállítás
  • User publikus adatainak visszaadása (jelszó nélkül!)

login(req, res)

  • Bejelentkezés email vagy username alapján
  • User keresése repository-val
  • Jelszó ellenőrzés bcrypt.compare()-val
  • Access és refresh token generálás
  • Cookie-k beállítása (httpOnly, secure, sameSite)
  • Opcionális: Session tárolás Redis-ben
  • Sikeres bejelentkezés esetén user adatok és tokenek visszaadása

logout(req, res)

  • Cookie-k törlése
  • Redis session törlése (ha van)
  • Refresh token invalidálás

refreshToken(req, res)

  • Refresh token kiolvasása cookie-ból
  • Token validálás
  • Új access token generálás
  • Új cookie beállítás

getCurrentUser(req, res)

  • Bejelentkezett user adatainak visszaadása
  • req.user alapján (amit az auth middleware állít be)

2️⃣ AuthMiddleware-ek implementálása

Fájl: src/api/middlewares/authMiddleware.js

authenticateToken(req, res, next)

Ez a middleware minden védett route előtt fut.

Feladatai:

  1. JWT token kiolvasása:
    • Cookie-ból: req.cookies.accessToken
    • VAGY Authorization header-ből: Bearer <token>
  2. Token validálás jwt.verify()-val
  3. User ID kinyerése a token payload-ból
  4. User betöltése repository-val
  5. req.user beállítása a user objektummal
  6. next() hívása

Hibakezelés:

  • Ha nincs token → 401 Unauthorized
  • Ha érvénytelen token → 401 Unauthorized
  • Ha user nem létezik → 401 Unauthorized

requireRole(allowedRoles)

Ez egy higher-order middleware, ami role-based access control-t implementál.

Feladatai:

  1. Visszaad egy middleware függvényt
  2. Ellenőrzi, hogy req.user létezik (az authenticateToken után fut!)
  3. Ellenőrzi, hogy req.user.role benne van-e az allowedRoles tömbben
  4. Ha nincs jogosultság → 403 Forbidden
  5. Ha van jogosultság → next()

Példa használat:

router.delete('/admin/users/:id', 
  authenticateToken, 
  requireRole(['ADMIN']), 
  deleteUserController
);

checkOwnership(getResourceOwnerId)

Ez a middleware resource ownership ellenőrzést implementál.

Feladatai:

  1. Visszaad egy middleware függvényt
  2. Meghívja a getResourceOwnerId függvényt, hogy megszerezze a resource tulajdonos ID-ját
  3. Ellenőrzi, hogy:
    • req.user.id === ownerId (a user a tulajdonos)
    • VAGY req.user.role === 'ADMIN' (ADMIN mindent módosíthat)
  4. Ha egyik sem teljesül → 403 Forbidden
  5. Ha OK → next()

Példa használat:

router.put('/blogs/:id', 
  authenticateToken,
  checkOwnership(async (req) => {
    const blog = await blogRepository.findById(req.params.id);
    return blog.authorId;
  }),
  (req, res) => blogController.updateBlog(req, res)
);

3️⃣ Middleware-ek integrálása a route-okba

Fájlok:

  • src/api/routes/authRoutes.js
  • src/api/routes/blogRoutes.js

Add hozzá az authenticateToken middleware-t a védett endpoint-okhoz:

authRoutes.js:

router.post('/logout', authenticateToken, (req, res) => authController.logout(req, res));
router.get('/me', authenticateToken, (req, res) => authController.getCurrentUser(req, res));

blogRoutes.js:

router.post('/', authenticateToken, (req, res) => blogController.createBlog(req, res));
router.put('/:id', authenticateToken, checkOwnership(...), (req, res) => blogController.updateBlog(req, res));
router.delete('/:id', authenticateToken, checkOwnership(...), (req, res) => blogController.deleteBlog(req, res));

🛠️ Implementációs Útmutató

JWT Token Generálás

import jwt from 'jsonwebtoken';

// Access token generálás
const accessToken = jwt.sign(
  { 
    userId: user.id,
    email: user.email,
    role: user.role
  },
  process.env.JWT_SECRET,
  { expiresIn: process.env.JWT_EXPIRES_IN }
);

// Refresh token generálás
const refreshToken = jwt.sign(
  { userId: user.id },
  process.env.JWT_REFRESH_SECRET,
  { expiresIn: process.env.JWT_REFRESH_EXPIRES_IN }
);

JWT Token Validálás

import jwt from 'jsonwebtoken';

try {
  const decoded = jwt.verify(token, process.env.JWT_SECRET);
  // decoded.userId, decoded.email, decoded.role
} catch (error) {
  // Token érvénytelen vagy lejárt
  throw new Error('Invalid token');
}

Jelszó Hash-elés

import bcrypt from 'bcrypt';

// Hash-elés (regisztrációnál)
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds);

// Ellenőrzés (bejelentkezésnél)
const isValid = await bcrypt.compare(password, user.password);
// Access token cookie
res.cookie('accessToken', accessToken, {
  httpOnly: true,          // JavaScript nem férhet hozzá
  secure: process.env.NODE_ENV === 'production', // Csak HTTPS-en
  sameSite: 'strict',      // CSRF védelem
  maxAge: 7 * 24 * 60 * 60 * 1000 // 7 nap
});

// Refresh token cookie
res.cookie('refreshToken', refreshToken, {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  sameSite: 'strict',
  maxAge: 30 * 24 * 60 * 60 * 1000 // 30 nap
});
res.clearCookie('accessToken');
res.clearCookie('refreshToken');

Redis Session Tárolás (Opcionális)

import { redis } from '../infrastructure/database/redis.js';

// Session mentése
await redis.set(
  `session:${userId}`,
  JSON.stringify({ userId, email, loginTime: new Date() }),
  'EX',
  60 * 60 * 24 * 7 // 7 nap TTL
);

// Session lekérése
const session = await redis.get(`session:${userId}`);
const sessionData = JSON.parse(session);

// Session törlése
await redis.del(`session:${userId}`);
function getTokenFromRequest(req) {
  // Cookie-ból
  if (req.cookies && req.cookies.accessToken) {
    return req.cookies.accessToken;
  }
  
  // Authorization header-ből
  const authHeader = req.headers.authorization;
  if (authHeader && authHeader.startsWith('Bearer ')) {
    return authHeader.substring(7);
  }
  
  return null;
}

🧪 Tesztelés

1. Regisztráció

curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "username": "testuser",
    "password": "SecurePassword123"
  }'

Elvárt válasz:

{
  "success": true,
  "data": {
    "user": {
      "id": "uuid...",
      "email": "test@example.com",
      "username": "testuser",
      "role": "USER"
    },
    "accessToken": "eyJhbGc...",
    "refreshToken": "eyJhbGc..."
  }
}

2. Bejelentkezés

curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "password": "SecurePassword123"
  }'

3. Current User

curl -X GET http://localhost:3000/api/auth/me \
  -H "Authorization: Bearer <access_token>"

VAGY cookie-val:

curl -X GET http://localhost:3000/api/auth/me \
  --cookie "accessToken=<token>"

4. Blog Létrehozás (Védett endpoint)

curl -X POST http://localhost:3000/api/blogs \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <access_token>" \
  -d '{
    "title": "My First Blog",
    "content": "This is awesome!",
    "published": true
  }'

5. Blog Módosítás (Ownership ellenőrzéssel)

curl -X PUT http://localhost:3000/api/blogs/<blog_id> \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <access_token>" \
  -d '{
    "title": "Updated Title"
  }'

6. Kijelentkezés

curl -X POST http://localhost:3000/api/auth/logout \
  -H "Authorization: Bearer <access_token>"

Ellenőrző Lista

Implementációd akkor teljes, ha:

  • Sikeres regisztráció email + username + password-dal
  • Email és username uniqueness ellenőrzés működik
  • Jelszó hash-elve van tárolva (bcrypt)
  • Sikeres bejelentkezés
  • Rossz jelszóval nem lehet bejelentkezni
  • JWT token generálódik és cookie-ban tárolódik
  • AuthMiddleware validálja a tokent
  • Védett endpoint-ok csak token-nal elérhetők
  • /api/auth/me visszaadja a bejelentkezett user adatait
  • Kijelentkezés törli a cookie-kat
  • Token refresh működik
  • Role-based access control működik (ADMIN vs USER)
  • Ownership check működik (csak saját blog módosítható)
  • Hibakezelés 401/403 válaszokkal

📚 Hasznos Források

Dokumentációk

Tananyagok


🐛 Gyakori Hibák és Megoldások

"JWT must be provided"

  • Ellenőrizd, hogy a token szerepel-e a cookie-ban vagy Authorization header-ben
  • Ellenőrizd a cookie nevét (accessToken)

"Invalid token"

  • Ellenőrizd a JWT_SECRET változót
  • Lehet, hogy a token lejárt - próbálj újra bejelentkezni

"User already exists"

  • Email vagy username már foglalt
  • Használj egyedi értékeket

"Incorrect password"

  • Ellenőrizd a bcrypt.compare() használatát
  • A paraméterek sorrendje: bcrypt.compare(plainPassword, hashedPassword)

"403 Forbidden"

  • Nincs jogosultságod az adott művelethez
  • Ellenőrizd a role-t vagy az ownership-et

🎓 Értékelési Szempontok

A feladat sikeres megoldása:

  1. Működőképesség (40%)

    • Regisztráció működik
    • Bejelentkezés/kijelentkezés működik
    • Védett endpoint-ok hozzáférés-védelme
  2. Biztonság (30%)

    • Jelszavak hash-elve vannak
    • HTTP-only cookie-k használata
    • JWT token biztonságos kezelése
    • Input validáció
  3. Kódminőség (20%)

    • Letisztult, olvasható kód
    • Megfelelő error handling
    • Architektúra követése
  4. Extra funkciók (10%)

    • Redis session management
    • Token refresh flow
    • Role-based access control
    • Ownership validation

💡 Tippek

  1. Kezdd a legegyszerűbbel: Először implementáld a regisztrációt, aztán a logint.

  2. Tesztelj folyamatosan: Minden metódus után tesztelj curl-lel vagy Postman-nel.

  3. Nézd meg a meglévő kódot: A BlogController egy jó példa, hogyan kell használni a repository-kat és handler-eket.

  4. Debug logging: Használj console.log()-ot fejlesztés közben, hogy lásd mi történik.

  5. Hibakezelés: Minden async művelet legyen try-catch blokkban.

  6. Token lejárat: Fejlesztés közben használj rövid lejárati időt (pl. 15 perc), hogy tesztelhesd a refresh flow-t.


Jó munkát és kellemes kódolást! 🚀