Backend Fejlesztés Gyakorló Feladat
Authentication & Authorization Implementáció
📋 Tartalom
- Projekt Áttekintés
- Architektúra
- Előkészületek
- A Feladat
- Implementációs Útmutató
- Tesztelés
- 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.useralapjá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:
- JWT token kiolvasása:
- Cookie-ból:
req.cookies.accessToken - VAGY Authorization header-ből:
Bearer <token>
- Cookie-ból:
- Token validálás
jwt.verify()-val - User ID kinyerése a token payload-ból
- User betöltése repository-val
req.userbeállítása a user objektummalnext()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:
- Visszaad egy middleware függvényt
- Ellenőrzi, hogy
req.userlétezik (azauthenticateTokenután fut!) - Ellenőrzi, hogy
req.user.rolebenne van-e azallowedRolestömbben - Ha nincs jogosultság → 403 Forbidden
- 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:
- Visszaad egy middleware függvényt
- Meghívja a
getResourceOwnerIdfüggvényt, hogy megszerezze a resource tulajdonos ID-ját - Ellenőrzi, hogy:
req.user.id === ownerId(a user a tulajdonos)- VAGY
req.user.role === 'ADMIN'(ADMIN mindent módosíthat)
- Ha egyik sem teljesül → 403 Forbidden
- 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.jssrc/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);
Cookie Beállítás
// 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
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}`);
Token Kiolvasás Cookie-ból vagy Header-ből
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/mevisszaadja 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 - JSON Web Token
- bcrypt - Password hashing
- Prisma - ORM dokumentáció
- Express - Web framework
- cookie-parser - Cookie middleware
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:
-
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
-
Biztonság (30%)
- Jelszavak hash-elve vannak
- HTTP-only cookie-k használata
- JWT token biztonságos kezelése
- Input validáció
-
Kódminőség (20%)
- Letisztult, olvasható kód
- Megfelelő error handling
- Architektúra követése
-
Extra funkciók (10%)
- Redis session management
- Token refresh flow
- Role-based access control
- Ownership validation
💡 Tippek
-
Kezdd a legegyszerűbbel: Először implementáld a regisztrációt, aztán a logint.
-
Tesztelj folyamatosan: Minden metódus után tesztelj curl-lel vagy Postman-nel.
-
Nézd meg a meglévő kódot: A BlogController egy jó példa, hogyan kell használni a repository-kat és handler-eket.
-
Debug logging: Használj
console.log()-ot fejlesztés közben, hogy lásd mi történik. -
Hibakezelés: Minden async művelet legyen try-catch blokkban.
-
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! 🚀