# Backend Fejlesztés Gyakorló Feladat ## Authentication & Authorization Implementáció --- ## 📋 Tartalom 1. [Projekt Áttekintés](#projekt-áttekintés) 2. [Architektúra](#architektúra) 3. [Előkészületek](#előkészületek) 4. [A Feladat](#a-feladat) 5. [Implementációs Útmutató](#implementációs-útmutató) 6. [Tesztelés](#tesztelés) 7. [Hasznos Források](#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 ```bash npm install ``` ### 2. Környezeti változók beállítása Másold le a `.env.example` fájlt `.env` néven: ```bash cp .env.example .env ``` Állítsd be a környezeti változókat `.env` fájlban: ```env 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 ```bash npm run docker:up ``` Ez elindítja a PostgreSQL és Redis konténereket. ### 4. Adatbázis migráció ```bash npm run prisma:migrate npm run prisma:generate ``` ### 5. Projekt indítása (development) ```bash npm run dev ``` A szerver elindul a `http://localhost:3000` címen. Ellenőrizd a health endpoint-ot: ```bash 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 ` 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:** ```javascript 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:** ```javascript 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:** ```javascript router.post('/logout', authenticateToken, (req, res) => authController.logout(req, res)); router.get('/me', authenticateToken, (req, res) => authController.getCurrentUser(req, res)); ``` **blogRoutes.js:** ```javascript 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 ```javascript 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 ```javascript 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 ```javascript 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); ``` ### Cookie Beállítás ```javascript // 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 }); ``` ### Cookie Törlés ```javascript res.clearCookie('accessToken'); res.clearCookie('refreshToken'); ``` ### Redis Session Tárolás (Opcionális) ```javascript 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}`); ``` ### Token Kiolvasás Cookie-ból vagy Header-ből ```javascript 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ó ```bash 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:** ```json { "success": true, "data": { "user": { "id": "uuid...", "email": "test@example.com", "username": "testuser", "role": "USER" }, "accessToken": "eyJhbGc...", "refreshToken": "eyJhbGc..." } } ``` ### 2. Bejelentkezés ```bash curl -X POST http://localhost:3000/api/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "test@example.com", "password": "SecurePassword123" }' ``` ### 3. Current User ```bash curl -X GET http://localhost:3000/api/auth/me \ -H "Authorization: Bearer " ``` VAGY cookie-val: ```bash curl -X GET http://localhost:3000/api/auth/me \ --cookie "accessToken=" ``` ### 4. Blog Létrehozás (Védett endpoint) ```bash curl -X POST http://localhost:3000/api/blogs \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "title": "My First Blog", "content": "This is awesome!", "published": true }' ``` ### 5. Blog Módosítás (Ownership ellenőrzéssel) ```bash curl -X PUT http://localhost:3000/api/blogs/ \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "title": "Updated Title" }' ``` ### 6. Kijelentkezés ```bash curl -X POST http://localhost:3000/api/auth/logout \ -H "Authorization: Bearer " ``` --- ## ✅ 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 - [JWT](https://jwt.io/) - JSON Web Token - [bcrypt](https://www.npmjs.com/package/bcrypt) - Password hashing - [Prisma](https://www.prisma.io/docs) - ORM dokumentáció - [Express](https://expressjs.com/) - Web framework - [cookie-parser](https://www.npmjs.com/package/cookie-parser) - Cookie middleware ### Tananyagok - [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html) - [JWT Best Practices](https://blog.logrocket.com/jwt-authentication-best-practices/) - [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) - [CQRS Pattern](https://martinfowler.com/bliki/CQRS.html) --- ## 🐛 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! 🚀**