This commit is contained in:
magdo
2026-02-24 01:30:22 +01:00
parent 31d4479c63
commit a837f5ecba
5 changed files with 0 additions and 2688 deletions
-470
View File
@@ -1,470 +0,0 @@
\section{HTTP Cookies}
\begin{frame}{Mi az a Cookie?}
\begin{block}{HTTP Cookie definíció}
Kis méretű adat, amit a szerver küld a böngészőnek, és automatikusan vissza küldődik.
\end{block}
\begin{itemize}
\item \textbf{Méret:} Max 4KB
\item \textbf{Tárolás:} Böngésző
\item \textbf{Élettartam:} Beállítható
\item \textbf{Használat:}
\begin{itemize}
\item Session kezelés
\item Felhasználói preferenciák
\item Tracking
\end{itemize}
\end{itemize}
\begin{exampleblock}{Authentikációban}
Cookie-k ideálisak session ID és token tárolására.
\end{exampleblock}
\end{frame}
\begin{frame}[fragile]{Cookie beállítása}
\begin{block}{Set-Cookie HTTP header}
A szerver a \texttt{Set-Cookie} header segítségével küldi a cookie-t a kliensnek.
\end{block}
\vspace{0.3cm}
\begin{columns}
\begin{column}{0.48\textwidth}
\textbf{HTTP válasz:}
\small
\begin{verbatim}
HTTP/1.1 200 OK
Set-Cookie: sessionId=abc123
Set-Cookie: theme=dark
Content-Type: application/json
{"success": true}
\end{verbatim}
\end{column}
\begin{column}{0.48\textwidth}
\textbf{Node.js/Express:}
\small
\begin{verbatim}
res.cookie('sessionId', 'abc123');
res.cookie('theme', 'dark', {
maxAge: 900000,
httpOnly: true
});
\end{verbatim}
\end{column}
\end{columns}
\vspace{0.5cm}
\begin{itemize}
\item A böngésző automatikusan eltárolja a cookie-t
\item Minden későbbi kérésben a böngésző automatikusan visszaküldi
\end{itemize}
\end{frame}
\begin{frame}[fragile]{Cookie olvasása}
\begin{block}{Cookie HTTP header}
A böngésző minden kérésben visszaküldi a releváns cookie-kat.
\end{block}
\vspace{0.3cm}
\begin{columns}
\begin{column}{0.48\textwidth}
\textbf{HTTP kérés:}
\small
\begin{verbatim}
GET /api/user HTTP/1.1
Host: example.com
Cookie: sessionId=abc123; theme=dark
\end{verbatim}
\end{column}
\begin{column}{0.48\textwidth}
\textbf{Node.js/Express:}
\small
\begin{verbatim}
// cookie-parser middleware
const cookieParser = require('cookie-parser');
app.use(cookieParser());
// Cookie olvasás
app.get('/api/user', (req, res) => {
const sessionId = req.cookies.sessionId;
const theme = req.cookies.theme;
// ...
});
\end{verbatim}
\end{column}
\end{columns}
\end{frame}
\begin{frame}{Cookie típusok}
\begin{columns}
\begin{column}{0.48\textwidth}
\begin{block}{Session Cookie}
\begin{itemize}
\item Nincs Expires/Max-Age
\item Böngésző bezárásakor törlődik
\end{itemize}
\textbf{Használat:}
\begin{itemize}
\item Ideiglenes session
\end{itemize}
\end{block}
\end{column}
\begin{column}{0.48\textwidth}
\begin{block}{Persistent Cookie}
\begin{itemize}
\item Van Expires/Max-Age
\item Megmarad újraindítás után
\end{itemize}
\textbf{Használat:}
\begin{itemize}
\item "Remember me"
\item Preferenciák
\end{itemize}
\end{block}
\end{column}
\end{columns}
\begin{exampleblock}{Példa}
\texttt{Set-Cookie: sessionId=abc123; Max-Age=3600}
\end{exampleblock}
\end{frame}
\begin{frame}[shrink=5]{Cookie attribútumok}
\begin{enumerate}
\item \textbf{Expires / Max-Age}
\begin{itemize}
\item Expires: Dátum, Max-Age: mper
\end{itemize}
\item \textbf{Domain}
\begin{itemize}
\item Mely domain-ekhez küldje
\end{itemize}
\item \textbf{Path}
\begin{itemize}
\item Mely URL path-okhoz
\end{itemize}
\item \textbf{Secure}
\begin{itemize}
\item Csak HTTPS-en
\end{itemize}
\end{enumerate}
\end{frame}
\begin{frame}{Biztonsági attribútumok}
\begin{alertblock}{HttpOnly}
\begin{itemize}
\item JavaScript NEM férhet hozzá
\item \textbf{XSS védelem!}
\end{itemize}
\end{alertblock}
\begin{alertblock}{Secure}
\begin{itemize}
\item Csak HTTPS-en
\item \textbf{MITM védelem!}
\end{itemize}
\end{alertblock}
\begin{alertblock}{SameSite}
\begin{itemize}
\item Strict - Csak same-site
\item Lax - GET same-site
\item None - Mindenhol (Secure kötelező!)
\item \textbf{CSRF védelem!}
\end{itemize}
\end{alertblock}
\end{frame}
\begin{frame}[fragile]{Biztonságos cookie beállítás}
\begin{block}{Production környezetben ajánlott beállítások}
\small
\begin{verbatim}
res.cookie('token', jwtToken, {
httpOnly: true, // XSS védelem - JS nem férhet hozzá
secure: true, // Csak HTTPS - production-ben!
sameSite: 'strict', // CSRF védelem
maxAge: 3600000, // 1 óra (milliszekundumban)
path: '/', // Teljes alkalmazásban elérhető
domain: '.example.com' // Domain és aldomain-ek
});
\end{verbatim}
\end{block}
\vspace{0.5cm}
\begin{alertblock}{Kritikus!}
Authentikációs token-ek mindig legyenek \texttt{httpOnly}, \texttt{secure} és \texttt{sameSite} attribútumokkal ellátva!
\end{alertblock}
\end{frame}
\begin{frame}[shrink=10]{SameSite részletesen}
\begin{block}{CSRF védelem cookie szinten}
SameSite határozza meg, cookie mikor küldhető cross-site kérésekben.
\end{block}
\begin{enumerate}
\item \textbf{Strict}
\begin{itemize}
\item Csak same-site
\item Legjobb védelem
\end{itemize}
\item \textbf{Lax}
\begin{itemize}
\item Top-level GET küldődik
\item POST/PUT/DELETE nem
\end{itemize}
\item \textbf{None}
\begin{itemize}
\item Mindenhol
\item Securekel kötelező!
\end{itemize}
\end{enumerate}
\end{frame}
\begin{frame}[fragile]{SameSite példák}
\begin{exampleblock}{Példa forgatókönyv}
\texttt{bank.com} oldalon be vagy jelentkezve cookie-val.
Megnyitsz egy \texttt{evil.com} oldalt, ami próbál kérést küldeni \texttt{bank.com}-ra.
\end{exampleblock}
\vspace{0.3cm}
\begin{columns}
\begin{column}{0.48\textwidth}
\textbf{SameSite=Strict:}
\small
\begin{verbatim}
Set-Cookie: session=xyz;
SameSite=Strict
\end{verbatim}
\begin{itemize}
\item \textcolor{red}{$\times$} evil.com $\to$ bank.com POST
\item \textcolor{red}{$\times$} evil.com link $\to$ bank.com
\item \textcolor{green}{$\checkmark$} bank.com $\to$ bank.com
\end{itemize}
\end{column}
\begin{column}{0.48\textwidth}
\textbf{SameSite=Lax:}
\small
\begin{verbatim}
Set-Cookie: session=xyz;
SameSite=Lax
\end{verbatim}
\begin{itemize}
\item \textcolor{red}{$\times$} evil.com $\to$ bank.com POST
\item \textcolor{green}{$\checkmark$} evil.com link $\to$ bank.com GET
\item \textcolor{green}{$\checkmark$} bank.com $\to$ bank.com
\end{itemize}
\end{column}
\end{columns}
\end{frame}
\begin{frame}[fragile,shrink=10]{Cookie törlése}
\begin{block}{Logout - cookie invalidálás}
\small
\begin{verbatim}
app.post('/api/logout', (req, res) => {
res.clearCookie('token', {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
res.json({ message: 'Logged out' });
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[shrink=10]{Cookie biztonság - XSS vs CSRF}
\begin{columns}
\begin{column}{0.48\textwidth}
\begin{alertblock}{XSS}
\textbf{Támadás:}
\begin{itemize}
\item JS injektálás
\item Cookie ellopás
\end{itemize}
\textbf{Védelem:}
\begin{itemize}
\item HttpOnly
\item Input validáció
\item CSP
\end{itemize}
\end{alertblock}
\end{column}
\begin{column}{0.48\textwidth}
\begin{alertblock}{CSRF}
\textbf{Támadás:}
\begin{itemize}
\item Jogosulatlan kérés
\item Automatikus cookie
\end{itemize}
\textbf{Védelem:}
\begin{itemize}
\item SameSite
\item CSRF token
\item Origin check
\end{itemize}
\end{alertblock}
\end{column}
\end{columns}
\begin{block}{Kombinált}
HttpOnly + Secure + SameSite + CSRF token
\end{block}
\end{frame}
\begin{frame}[fragile,shrink=10]{CSRF token}
\begin{block}{Double submit pattern}
\small
\begin{verbatim}
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: { httpOnly: true } });
app.use(csrfProtection);
app.get('/api/csrf-token', (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});
app.post('/api/transfer', (req, res) => {
// CSRF valid automatikus
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile,shrink=10]{Cookie és CORS}
\begin{block}{Cross-Origin}
Cookie-k alapból NEM küldődnek cross-origin-ben.
\end{block}
\begin{columns}
\begin{column}{0.48\textwidth}
\textbf{Szerver:}
\small
\begin{verbatim}
app.use(cors({
origin: 'https://frontend.com',
credentials: true
}));
\end{verbatim}
\end{column}
\begin{column}{0.48\textwidth}
\textbf{Kliens:}
\small
\begin{verbatim}
fetch('/api/user', {
credentials: 'include'
})
\end{verbatim}
\end{column}
\end{columns}
\begin{alertblock}{Fontos!}
credentials: true + 'include' + SameSite=None; Secure
\end{alertblock}
\end{frame}
\begin{frame}[shrink=10]{Cookie vs localStorage vs sessionStorage}
\begin{small}
\begin{tabular}{|l|c|c|c|}
\hline
& \textbf{Cookie} & \textbf{localStorage} & \textbf{sessionStorage} \\
\hline
Kapacitás & 4KB & 5-10MB & 5-10MB \\
\hline
Lejárat & Beállítható & Nincs & Tab bezárás \\
\hline
HTTP-ben & \textcolor{green}{Igen} & \textcolor{red}{Nem} & \textcolor{red}{Nem} \\
\hline
XSS védelem & HttpOnly & \textcolor{red}{Nem} & \textcolor{red}{Nem} \\
\hline
\end{tabular}
\end{small}
\begin{exampleblock}{Ajánlás}
\textbf{Érzékeny:} HttpOnly Cookie, \textbf{Publikus:} localStorage
\end{exampleblock}
\end{frame}
\begin{frame}[fragile,shrink=10]{Signed Cookies}
\begin{block}{Integritás védelem}
HMAC aláírással elliatott cookie, megakadályozza manipulációt.
\end{block}
\begin{verbatim}
const cookieParser = require('cookie-parser');
app.use(cookieParser('secret-key'));
app.get('/set', (req, res) => {
res.cookie('userId', '12345', { signed: true });
res.send('Set');
});
app.get('/get', (req, res) => {
const userId = req.signedCookies.userId;
res.json({ userId });
});
\end{verbatim}
\end{frame}
\begin{frame}[shrink=5]{Cookie best practices}
\begin{block}{Authentikációs cookie-k}
\begin{enumerate}
\item \textbf{HttpOnly:} JS nem fér hozzá
\item \textbf{Secure:} Csak HTTPS
\item \textbf{SameSite:} Strict/Lax
\item \textbf{Rövid Max-Age:} 15-60 perc
\item \textbf{Path:} Csak szükséges
\item \textbf{Signed cookie:} Manipuláció ellen
\end{enumerate}
\end{block}
\begin{alertblock}{Production template}
\small
\texttt{res.cookie('token', value, \{httpOnly: true, secure: true, sameSite: 'strict', maxAge: 3600000\})}
\end{alertblock}
\end{frame}
\begin{frame}[shrink=5]{\u00d6sszefoglalás}
\begin{itemize}
\item \textbf{Cookie} = Kis adat szerver és kliens között
\item \textbf{Típusok:} Session (ideiglenes) vs Persistent
\item \textbf{Attribútumok:} Expires, Domain, Path, Secure, HttpOnly, SameSite
\item textbf{HttpOnly:} XSS védelem
\item \textbf{Secure:} HTTPS - MITM védelem
\item \textbf{SameSite:} CSRF védelem
\item \textbf{CORS:} credentials: true + 'include'
\item \textbf{Biztonság:} HttpOnly + Secure + SameSite
\end{itemize}
\begin{exampleblock}{Authentikációban}
Cookie a \textbf{legbiztonságosabb} módszer token tárolására.
\end{exampleblock}
\end{frame}
-549
View File
@@ -1,549 +0,0 @@
\section{JWT}
\begin{frame}{Mi az a JWT?}
\begin{block}{JSON Web Token (RFC 7519)}
Nyílt szabványú, kompakt és önálló módszer információ biztonságos továbbítására.
\end{block}
\begin{itemize}
\item \textbf{Kompakt:} Kis méret, URL/header-ben is
\item \textbf{Önálló:} Minden információt tartalmaz
\item \textbf{Biztonságos:} Digitálisan aláírt
\item \textbf{Használat:}
\begin{itemize}
\item Authentikáció
\item Információ csere
\item Stateless session
\end{itemize}
\end{itemize}
\end{frame}
\begin{frame}[fragile,shrink=10]{JWT formátum}
\begin{block}{Három részből áll, ponttal elválasztva}
\texttt{\textcolor{red}{HEADER}.\textcolor{purple}{PAYLOAD}.\textcolor{blue}{SIGNATURE}}
\end{block}
\begin{exampleblock}{Példa JWT}
\tiny
\begin{verbatim}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZ
SI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36P
OkmLXOUqhfKJ4
\end{verbatim}
\end{exampleblock}
\begin{itemize}
\item \textcolor{red}{\textbf{Header:}} Token típusa és algoritmus
\item \textcolor{purple}{\textbf{Payload:}} Claims (állítások) - felhasználói adatok
\item \textcolor{blue}{\textbf{Signature:}} Aláírás - integritás ellenőrzés
\end{itemize}
\begin{alertblock}{Fontos!}
A JWT Base64URL kódolású, \textbf{NEM} titkosított! A payload tartalma olvasható!
\end{alertblock}
\end{frame}
\begin{frame}[fragile]{JWT Header}
\begin{block}{Token metaadatok}
Token típusa és aláírási algoritmus.
\end{block}
\begin{columns}
\begin{column}{0.48\textwidth}
\textbf{JSON:}
\begin{verbatim}
{
"alg": "HS256",
"typ": "JWT"
}
\end{verbatim}
\begin{itemize}
\item \texttt{alg}: Algoritmus
\item \texttt{typ}: Token típus
\end{itemize}
\end{column}
\begin{column}{0.48\textwidth}
\textbf{Kódolt:}
\begin{verbatim}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
\end{verbatim}
\textbf{Algoritmusok:}
\begin{itemize}
\item HS256 - HMAC
\item RS256 - RSA
\item ES256 - ECDSA
\end{itemize}
\end{column}
\end{columns}
\end{frame}
\begin{frame}[fragile,shrink=5]{JWT Payload (Claims)}
\begin{block}{Felhasználói adatok és meta}
Claims (allítoltak) a felhasználóról és tokenről.
\end{block}
\begin{columns}
\begin{column}{0.48\textwidth}
\textbf{Registered:}
\begin{itemize}
\item \texttt{iss} - Kibocsátó
\item \texttt{sub} - Tárgy/user ID
\item \texttt{aud} - Címzett
\item \texttt{exp} - Lejárat
\item \texttt{iat} - Kiállítás
\end{itemize}
\end{column}
\begin{column}{0.48\textwidth}
\textbf{Példa:}
\small
\begin{verbatim}
{
"sub": "1234567890",
"name": "John Doe",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}
\end{verbatim}
\end{column}
\end{columns}
\begin{alertblock}{Figyelem!}
Ne tároljunk érzékeny adatokat (jelszó) payload-ban!
\end{alertblock}
\end{frame}
\begin{frame}[fragile,shrink=5]{JWT Signature}
\begin{block}{Digitális aláírás}
Biztosítja a token integritását és hitelességét.
\end{block}
\textbf{Signature (HMAC SHA256):}
\begin{verbatim}
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
\end{verbatim}
\begin{columns}
\begin{column}{0.48\textwidth}
\textbf{HMAC (Symmetric):}
\begin{itemize}
\item Egy kulcs
\item Gyorsabb
\item HS256
\end{itemize}
\end{column}
\begin{column}{0.48\textwidth}
\textbf{RSA (Asymmetric):}
\begin{itemize}
\item Privát + publikus
\item Biztonságosabb
\item RS256
\end{itemize}
\end{column}
\end{columns}
\end{frame}
\begin{frame}{JWT működése}
\begin enumerate}
\item \textbf{Bejelentkezés:}
\begin{itemize}
\item Username + password
\item Szerver validál
\end{itemize}
\item \textbf{Token generálás:}
\begin{itemize}
\item JWT létrehozás user adatokkal
\item Aláírás titkos kulccsal
\end{itemize}
\item \textbf{Tárolás:}
\begin{itemize}
\item localStorage / cookie
\end{itemize}
\item \textbf{API kérések:}
\begin{itemize}
\item \texttt{Authorization: Bearer <token>}
\item Szerver validálja
\end{itemize}
\end{enumerate}
\end{frame}
\begin{frame}[fragile,shrink=10]{JWT generálás Node.js-ben}
\begin{block}{jsonwebtoken könyvtár használata}
\small
\begin{verbatim}
const jwt = require('jsonwebtoken');
const payload = {
userId: user.id,
email: user.email,
role: user.role
};
const token = jwt.sign(
payload,
process.env.JWT_SECRET,
{ expiresIn: '1h', issuer: 'myapp' }
);
res.json({ token, expiresIn: 3600 });
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile,shrink=10]{JWT validálás Node.js-ben}
\begin{block}{Token ellenőrzés}
\small
\begin{verbatim}
const authHeader = req.headers.authorization;
const token = authHeader?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(403).json({ error: 'Invalid' });
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile,shrink=10]{JWT middleware Express-ben}
\begin{block}{Védett route-ok}
\small
\begin{verbatim}
const authenticateJWT = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'Token required' });
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.status(403).json({ error: 'Invalid token' });
req.user = user;
next();
});
};
app.get('/api/protected', authenticateJWT, (req, res) => {
res.json({ message: 'Protected', user: req.user });
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}{JWT vs Session}
\begin{columns}
\begin{column}{0.48\textwidth}
\begin{block}{JWT (Stateless)}
\textbf{Előnyök:}
\begin{itemize}
\item Skálázható
\item Nincs szerver-oldali tárolás
\item Cross-domain működés
\item Mobile-friendly
\end{itemize}
\vspace{0.3cm}
\textbf{Hátrányok:}
\begin{itemize}
\item Nehéz visszavonni
\item Token méret
\item XSS sebezhetőség (localStorage)
\end{itemize}
\end{block}
\end{column}
\begin{column}{0.48\textwidth}
\begin{block}{Session (Stateful)}
\textbf{Előnyök:}
\begin{itemize}
\item Azonnali visszavonás
\item Kis cookie méret
\item Biztonságosabb token tárolás
\end{itemize}
\vspace{0.3cm}
\textbf{Hátrányok:}
\begin{itemize}
\item Szerver-oldali memória
\item Nehezebb skálázás
\item CORS problémák
\item Session store szükséges
\end{itemize}
\end{block}
\end{column}
\end{columns}
\vspace{0.5cm}
\begin{alertblock}{Melyiket válasszuk?}
Függ az alkalmazás követelményeitől: méret, skálázhatóság, biztonság, komplexitás.
\end{alertblock}
\end{frame}
\begin{frame}{Token tárolás a kliens oldalon}
\begin{columns}
\begin{column}{0.48\textwidth}
\begin{block}{localStorage}
\begin{itemize}
\item Egyszerű
\item JS hozzáférés
\item Megosztott
\end{itemize}
\textcolor{red}{\textbf{XSS!}}
\begin{itemize}
\item \textcolor{red}{JS hozzáférhet}
\end{itemize}
\end{block}
\end{column}
\begin{column}{0.48\textwidth}
\begin{block}{HttpOnly Cookie}
\begin{itemize}
\item JS NEM fér hozzá
\item Automatikus
\item XSS védett
\end{itemize}
\textcolor{red}{\textbf{CSRF}}
\begin{itemize}
\item \textcolor{red}{Védelem kell}
\end{itemize}
\end{block}
\end{column}
\end{columns}
\begin{exampleblock}{Ajánlás}
HttpOnly, Secure, SameSite cookie a legbiztonságosabb.
\end{exampleblock}
\end{frame}
\begin{frame}[fragile]{JWT tárolás HttpOnly Cookie-ban}
\begin{block}{Token beállítás cookie-ban}
\small
\begin{verbatim}
// Login endpoint
app.post('/api/login', (req, res) => {
// ... authentikáció logika ...
const token = jwt.sign(payload, secret, { expiresIn: '1h' });
res.cookie('token', token, {
httpOnly: true, // JavaScript nem férhet hozzá
secure: true, // Csak HTTPS-en keresztül
sameSite: 'strict',// CSRF védelem
maxAge: 3600000 // 1 óra milliszekundumban
});
res.json({ success: true, message: 'Logged in' });
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{JWT olvasás Cookie-ból}
\begin{block}{Cookie-parser middleware}
\small
\begin{verbatim}
const cookieParser = require('cookie-parser');
app.use(cookieParser());
const authenticateJWT = (req, res, next) => {
// Token a cookie-ból
const token = req.cookies.token;
if (!token) {
return res.status(401).json({ error: 'Not authenticated' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(403).json({ error: 'Invalid token' });
}
};
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[shrink=5]{Refresh Token stratégia}
\begin{block}{Miért szükséges?}
Access tokenek rövidek (15 perc). Refresh tokennel új access token kérhető.
\end{block}
\begin{itemize}
\item \textbf{Access Token:}
\begin{itemize}
\item Rövid (15-60 perc)
\item API kérésekhez
\end{itemize}
\item \textbf{Refresh Token:}
\begin{itemize}
\item Hosszú (7-30 nap)
\item Új access tokenhez
\item HttpOnly cookie-ban
\item DB-ben tárolva
\end{itemize}
\end{itemize}
\begin{alertblock}{Best Practice}
Refresh token rotation: minden refresh-nél új refresh token is.
\end{alertblock}
\end{frame}
\begin{frame}[fragile]{Refresh Token implementáció}
\begin{block}{Token refresh endpoint}
\small
\begin{verbatim}
app.post('/api/refresh', (req, res) => {
const refreshToken = req.cookies.refreshToken;
if (!refreshToken) {
return res.status(401).json({ error: 'No refresh token' });
}
try {
// Refresh token validálás
const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET);
// Ellenőrizzük az adatbázisban
const storedToken = await RefreshToken.findOne({
token: refreshToken,
userId: decoded.userId
});
if (!storedToken) {
return res.status(403).json({ error: 'Invalid refresh token' });
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Refresh Token implementáció (folyt.)}
\begin{block}{Új tokenek generálása}
\small
\begin{verbatim}
// Új access token
const accessToken = jwt.sign(
{ userId: decoded.userId, role: decoded.role },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
// Új refresh token (rotation)
const newRefreshToken = jwt.sign(
{ userId: decoded.userId },
process.env.REFRESH_SECRET,
{ expiresIn: '7d' }
);
// Régi token törlése, új mentése az adatbázisba
await RefreshToken.deleteOne({ token: refreshToken });
await RefreshToken.create({ token: newRefreshToken, userId: decoded.userId });
res.cookie('refreshToken', newRefreshToken, { httpOnly: true, ... });
res.json({ accessToken });
} catch (error) {
res.status(403).json({ error: 'Invalid refresh token' });
}
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[shrink=5]{JWT biztonság best practices}
\begin{alertblock}{Kritikus pontok}
\begin{itemize}
\item \textbf{Titkos kulcs:} Erős, random, env-ben
\item \textbf{HTTPS:} Mindig!
\item \textbf{Lejárat:} Max 1 óra
\item \textbf{Érzeky adatok:} Ne JWT-ben
\item \textbf{Algoritmus:} Ellenőrizd (ne 'none')
\end{itemize}
\end{alertblock}
\begin{block}{További ajánlások}
\begin{itemize}
\item HttpOnly cookie
\item Refresh token rotation
\item Rate limiting
\item Aud és Iss validálás
\end{itemize}
\end{block}
\end{frame}
\begin{frame}{Gyakori JWT hibák és támadások}
\begin{alertblock}{Gyakori sebezhetőségek}
\begin{enumerate}
\item \textbf{None algoritmus támadás}
\begin{itemize}
\item Header: \texttt{"alg": "none"} - aláírás nélküli token
\item Védelem: Mindig ellenőrizzük az algoritmust
\end{itemize}
\item \textbf{Gyenge titkos kulcs}
\begin{itemize}
\item Rövid vagy közismert kulcs
\item Védelem: Min. 256-bit random kulcs
\end{itemize}
\item \textbf{Token kiszivárogtatás}
\begin{itemize}
\item XSS támadás localStorage-ból
\item Védelem: HttpOnly cookie használata
\end{itemize}
\item \textbf{Algoritmus konfúzió}
\begin{itemize}
\item RS256 publikus kulcs HMAC secret-ként használva
\item Védelem: Explicit algoritmus megadása validáláskor
\end{itemize}
\end{enumerate}
\end{alertblock}
\end{frame}
\begin{frame}{Összefoglalás}
\begin{itemize}
\item \textbf{JWT} = Kompakt, önálló, biztonságos token formátum
\item \textbf{Struktúra:} Header.Payload.Signature
\item \textbf{Előnyök:} Stateless, skálázható, cross-domain
\item \textbf{Claims:} Registered (exp, iat, sub) és Custom (role, email)
\item \textbf{Tárolás:} HttpOnly cookie a legbiztonságosabb
\item \textbf{Refresh token:} Új access token kéréshez hosszú élettartammal
\item \textbf{Biztonság:} HTTPS, erős secret, rövid lejárat, validálás
\end{itemize}
\vspace{0.5cm}
\begin{exampleblock}{Mikor használjuk a JWT-t?}
\begin{itemize}
\item RESTful API authentikáció
\item Microservice architektúra
\item Mobile alkalmazások
\item Single Page Applications (SPA)
\item Stateless session kezelés
\end{itemize}
\end{exampleblock}
\end{frame}
-555
View File
@@ -1,555 +0,0 @@
\section{Middleware}
\begin{frame}{Mi az a middleware?}
\begin{block}{Definíció}
Függvény, amely HTTP kérés és válasz között fut, hozzáfér \texttt{req}, \texttt{res}, \texttt{next}-hez.
\end{block}
\begin{itemize}
\item Láncolható függvények
\item Kérés előfeldolgozás
\item Válasz utófeldolgozás
\item Kérés megszakítás
\end{itemize}
\begin{exampleblock}{Express middleware}
\texttt{function middleware(req, res, next) \{ ... \}}
\end{exampleblock}
\end{frame}
\begin{frame}[shrink=5]{Middleware működése}
\begin{center}
\begin{tikzpicture}[node distance=1.5cm, auto]
\node (browser) [rectangle, draw, text width=2cm, text centered] {Browser};
\node (mw1) [rectangle, draw, right of=browser, xshift=1.5cm, text width=2.5cm, text centered] {Middleware 1\\(Logger)};
\node (mw2) [rectangle, draw, right of=mw1, xshift=2cm, text width=2.5cm, text centered] {Middleware 2\\(Auth)};
\node (route) [rectangle, draw, right of=mw2, xshift=2cm, text width=2cm, text centered] {Route Handler};
\draw[->, thick] (browser) -- node[above] {Request} (mw1);
\draw[->, thick] (mw1) -- node[above] {\texttt{next()}} (mw2);
\draw[->, thick] (mw2) -- node[above] {\texttt{next()}} (route);
\draw[->, thick, dashed] (route) -- node[below] {Response} (browser);
\end{tikzpicture}
\end{center}
\begin{alertblock}{Fontos!}
Ha nem hívod meg a \texttt{next()}-et, a kérés megáll és nem jut el a következő middleware-hez vagy route handler-hez!
\end{alertblock}
\end{frame}
\begin{frame}[shrink=5]{Middleware típusok}
\begin{enumerate}
\item \textbf{Application-level}
\begin{itemize}
\item Egész app-ra
\end{itemize}
\item \textbf{Router-level}
\begin{itemize}
\item Adott routerre
\end{itemize}
\item \textbf{Error-handling}
\begin{itemize}
\item 4 param: \texttt{(err, req, res, next)}
\end{itemize}
\item \textbf{Built-in}
\begin{itemize}
\item \texttt{express.json()}
\end{itemize}
\item \textbf{Third-party}
\begin{itemize}
\item \texttt{cors}, \texttt{helmet}
\end{itemize}
\end{enumerate}
\end{frame}
\begin{frame}[fragile,shrink=10]{Egyszerű logger middleware}
\begin{block}{Request logging - 1. rész}
\small
\begin{verbatim}
const express = require('express');
const app = express();
const logger = (req, res, next) => {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] ${req.method} ${req.url}`);
next();
};
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile,shrink=10]{Egyszerű logger middleware (folyt.)}
\begin{block}{Request logging - 2. rész (használat)}
\small
\begin{verbatim}
app.use(logger);
app.get('/', (req, res) => {
res.json({ message: 'Hello World' });
});
app.get('/users', (req, res) => {
res.json({ users: ['Alice', 'Bob'] });
});
app.listen(3000);
// Kimenet:
// [2026-02-23T10:30:15.000Z] GET /
// [2026-02-23T10:30:20.000Z] GET /users
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile,shrink=10]{Request timing middleware}
\begin{block}{Válaszidő mérése}
\small
\begin{verbatim}
const requestTimer = (req, res, next) => {
req.startTime = Date.now();
res.on('finish', () => {
const duration = Date.now() - req.startTime;
console.log(`${req.method} ${req.url} - ${duration}ms`);
});
next();
};
app.use(requestTimer);
// Kimenet:
// GET /api/users - 145ms
// POST /api/login - 523ms
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Validation middleware}
\begin{block}{Input validáció - 1. rész}
\small
\begin{verbatim}
// email validáló middleware
const validateEmail = (req, res, next) => {
const { email } = req.body;
if (!email) {
return res.status(400).json({
error: 'Email is required'
});
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return res.status(400).json({
error: 'Invalid email format'
});
}
next(); // Validáció sikeres, tovább
};
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Validation middleware (folyt.)}
\begin{block}{Input validáció - 2. rész (használat)}
\small
\begin{verbatim}
app.use(express.json()); // Body parser middleware
// Route-specific middleware
app.post('/register', validateEmail, (req, res) => {
const { email, password } = req.body;
// Email már validálva van a middleware által
// Regisztráció logika...
res.json({ message: 'User registered', email });
});
// Több middleware egyszerre
app.post('/login',
validateEmail,
validatePassword, // Másik validator
(req, res) => {
// Login logika...
}
);
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Authentikációs middleware - JWT}
\begin{block}{Token validáció - 1. rész}
\small
\begin{verbatim}
const jwt = require('jsonwebtoken');
const authenticateJWT = (req, res, next) => {
// Token a Authorization header-ből
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({
error: 'Access token required'
});
}
// Bearer TOKEN formátum
const token = authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({
error: 'Token not found'
});
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Authentikációs middleware - JWT (folyt.)}
\begin{block}{Token validáció - 2. rész}
\small
\begin{verbatim}
// Token verify
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Felhasználói adat hozzáadása a request objektumhoz
req.user = decoded; // { userId, email, role, ... }
next(); // Sikeres authentikáció
} catch (error) {
return res.status(403).json({
error: 'Invalid or expired token'
});
}
};
// Használat
app.get('/api/profile', authenticateJWT, (req, res) => {
// req.user elérhető itt
res.json({ user: req.user });
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Autorizációs middleware - Role-based}
\begin{block}{Szerepkör ellenőrzés}
\small
\begin{verbatim}
// Role-based access control middleware factory
const requireRole = (role) => {
return (req, res, next) => {
// Feltételezzük, hogy az authenticateJWT már futott
if (!req.user) {
return res.status(401).json({
error: 'Not authenticated'
});
}
if (req.user.role !== role) {
return res.status(403).json({
error: `Forbidden: ${role} role required`
});
}
next(); // Jogosultság OK
};
};
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Autorizációs middleware (folyt.)}
\begin{block}{Használat szerepkör ellenőrzésre}
\small
\begin{verbatim}
// Admin-only endpoint
app.delete('/api/users/:id',
authenticateJWT, // 1. Authentikáció
requireRole('admin'), // 2. Autorizáció
(req, res) => {
// Csak admin férhet ide
const userId = req.params.id;
// Delete user logika...
res.json({ message: 'User deleted' });
}
);
// Több szerepkör támogatása
const requireAnyRole = (...roles) => {
return (req, res, next) => {
if (!req.user || !roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
};
app.get('/api/reports', authenticateJWT, requireAnyRole('admin', 'manager'), ...);
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Rate limiting middleware}
\begin{block}{API hívások korlátozása}
\small
\begin{verbatim}
const rateLimit = require('express-rate-limit');
// Rate limiter konfiguráció
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 perc
max: 100, // Max 100 kérés 15 percenként
message: 'Too many requests, please try again later',
standardHeaders: true, // RateLimit-* headers
legacyHeaders: false
});
// Application-level
app.use('/api/', limiter);
// Strict limiter login-hoz
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Max 5 login kísérlet 15 percenként
skipSuccessfulRequests: true // Sikeres login nem számít bele
});
app.post('/api/login', loginLimiter, (req, res) => { ... });
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Error handling middleware}
\begin{block}{Központi hibakezelés}
\small
\begin{verbatim}
// Error handling middleware (4 paraméter!)
const errorHandler = (err, req, res, next) => {
// Logolás
console.error('Error:', err);
// Custom error osztályok kezelése
if (err.statusCode) {
return res.status(err.statusCode).json({
error: err.name,
message: err.message
});
}
// Váratlan hibák
res.status(500).json({
error: 'InternalServerError',
message: 'Something went wrong'
});
};
// Error handler middleware UTOLJÁRA kell regisztrálni!
app.use(errorHandler);
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Error handling használata}
\begin{block}{Hibák továbbítása a next()-el}
\small
\begin{verbatim}
// Async route handler
app.get('/api/users/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
const error = new Error('User not found');
error.statusCode = 404;
throw error; // vagy: return next(error);
}
res.json({ user });
} catch (error) {
next(error); // Error handler middleware-nek továbbítja
}
});
// A errorHandler middleware automatikusan feldolgozza
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{CORS middleware}
\begin{block}{Cross-Origin Resource Sharing}
\small
\begin{verbatim}
const cors = require('cors');
// Egyszerű CORS - minden origin engedélyezve
app.use(cors());
// Konfigurált CORS
const corsOptions = {
origin: 'https://frontend.example.com', // Engedélyezett origin
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // Cookie-k engedélyezése
maxAge: 86400 // Preflight cache 24 óra
};
app.use(cors(corsOptions));
// Route-specific CORS
app.get('/api/public', cors(), (req, res) => {
res.json({ message: 'Public data' });
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Security middleware - Helmet}
\begin{block}{HTTP header biztonság}
\small
\begin{verbatim}
const helmet = require('helmet');
// Helmet middleware - biztonságos HTTP headerek
app.use(helmet());
// Helmet beállítja:
// - Content-Security-Policy
// - X-DNS-Prefetch-Control
// - X-Frame-Options (SAMEORIGIN)
// - X-Content-Type-Options (nosniff)
// - X-XSS-Protection
// stb.
// Egyedi konfiguráció
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"]
}
}
}));
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Middleware láncolás}
\begin{block}{Több middleware egyidejű használata}
\small
\begin{verbatim}
// Middleware láncolás egy route-on
app.post('/api/posts',
authenticateJWT, // 1. Authentikáció
requireRole('user'), // 2. Autorizáció
validatePost, // 3. Validáció
uploadImages, // 4. Képfeltöltés
(req, res) => { // 5. Route handler
// Post létrehozás logika
res.json({ message: 'Post created' });
}
);
// Middleware tömb
const postMiddlewares = [
authenticateJWT,
requireRole('user'),
validatePost
];
app.post('/api/posts', postMiddlewares, (req, res) => {
// ...
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}{Middleware Best Practices}
\begin{enumerate}
\item \textbf{Sorrend számít:}
\begin{itemize}
\item CORS és security middleware-ek ELŐRE
\item Error handler middleware HÁTRA
\end{itemize}
\vspace{0.2cm}
\item \textbf{Mindig hívd meg a next()-et:}
\begin{itemize}
\item Kivéve ha response-t küldesz vagy hibát dobsz
\end{itemize}
\vspace{0.2cm}
\item \textbf{Error handling:}
\begin{itemize}
\item Async függvényekben try-catch + next(error)
\item 4 paraméteres error handler middleware
\end{itemize}
\vspace{0.2cm}
\item \textbf{Teljesítmény:}
\begin{itemize}
\item Ne futtass middleware-t szükségtelenül (route-specific)
\item Async műveletek csak ha szükséges
\end{itemize}
\vspace{0.2cm}
\item \textbf{Újrafelhasználhatóság:}
\begin{itemize}
\item Middleware factory pattern (pl. requireRole)
\item Konfigurálható middleware-ek
\end{itemize}
\end{enumerate}
\end{frame}
\begin{frame}{Middleware execution order}
\begin{block}{Tipikus middleware sorrend Express alkalmazásban}
\small
\begin{enumerate}
\item \textbf{helmet()} - Security headers
\item \textbf{cors()} - CORS beállítások
\item \textbf{morgan/logger} - Request logging
\item \textbf{express.json()} - Body parser
\item \textbf{express.urlencoded()} - URL-encoded body
\item \textbf{cookie-parser()} - Cookie parsing
\item \textbf{Rate limiter} - API rate limiting
\item \textbf{Custom middlewares} - Saját middleware-ek
\item \textbf{Routes} - Route handler-ek
\item \textbf{404 handler} - Not found middleware
\item \textbf{Error handler} - Központi hibakezelés (4 param)
\end{enumerate}
\end{block}
\end{frame}
\begin{frame}{Összefoglalás - Middleware}
\begin{itemize}
\item \textbf{Middleware} = Függvény a kérés-válasz között
\item \textbf{Paraméterek:} (req, res, next) vagy (err, req, res, next)
\item \textbf{Típusok:} Application, Router, Error-handling, Built-in, Third-party
\item \textbf{next():} Következő middleware-re adja a vezérlést
\item \textbf{Használat:}
\begin{itemize}
\item Logging (morgan, winston)
\item Authentication (JWT validáció)
\item Authorization (szerepkör ellenőrzés)
\item Validation (input ellenőrzés)
\item Rate limiting (express-rate-limit)
\item Security (helmet, cors)
\item Error handling (központi hibakezelés)
\end{itemize}
\item \textbf{Sorrend fontos:} Security → Parsing → Auth → Routes → Error
\end{itemize}
\end{frame}
-443
View File
@@ -1,443 +0,0 @@
\section{OAuth 2.0}
\begin{frame}{Mi az OAuth 2.0?}
\begin{block}{Definíció}
OAuth 2.0 egy nyílt szabványú \textbf{autorizációs protokoll}, amely korlátozott hozzáférést ad jelszó megosztása nélkül.
\end{block}
\begin{itemize}
\item RFC 6749 (2012)
\item Autorizációra fókuszál
\item Biztonságos delegált hozzáférés
\item Google, Facebook, GitHub
\end{itemize}
\begin{exampleblock}{Használat}
Alkalmazás hozzáfér Google Drive-hoz anélkül, hogy ismebné a jelszót.
\end{exampleblock}
\end{frame}
\begin{frame}{OAuth 2.0 szerepkörök}
\begin{enumerate}
\item \textbf{Resource Owner (Erőforrás tulajdonos)}
\begin{itemize}
\item A felhasználó, aki birtokolja az erőforrást
\item Engedélyezheti a hozzáférést
\end{itemize}
\vspace{0.3cm}
\item \textbf{Client (Kliens)}
\begin{itemize}
\item Az alkalmazás, amely hozzáférést kér az erőforráshoz
\item Például: mobil app, webalkalmazás
\end{itemize}
\vspace{0.3cm}
\item \textbf{Authorization Server (Autorizációs szerver)}
\begin{itemize}
\item Kiállítja az access token-t sikeres authentikáció után
\item Kezeli a felhasználói hozzájárulást
\end{itemize}
\vspace{0.3cm}
\item \textbf{Resource Server (Erőforrás szerver)}
\begin{itemize}
\item Tárolja a védett erőforrásokat
\item Elfogadja és validálja az access token-eket
\end{itemize}
\end{enumerate}
\end{frame}
\begin{frame}{OAuth 2.0 Flow - Áttekintés}
\begin{center}
\begin{tikzpicture}[node distance=2cm, auto]
\node (ro) [rectangle, draw, text width=2cm, text centered] {Resource Owner};
\node (client) [rectangle, draw, below of=ro, text width=2cm, text centered] {Client};
\node (authserver) [rectangle, draw, right of=client, xshift=3cm, text width=2.5cm, text centered] {Authorization Server};
\node (resserver) [rectangle, draw, below of=authserver, text width=2.5cm, text centered] {Resource Server};
\draw[->, thick] (client) -- node[left] {1. Kérés} (ro);
\draw[->, thick] (ro) -- node[above] {2. Hozzájárulás} (authserver);
\draw[->, thick] (authserver) -- node[right] {3. Token} (client);
\draw[->, thick] (client) -- node[above] {4. API kérés} (resserver);
\draw[->, thick] (resserver) -- node[below] {5. Védett erőforrás} (client);
\end{tikzpicture}
\end{center}
\begin{itemize}
\item Az Authorization Server és Resource Server lehet ugyanaz a rendszer
\item A kommunikáció HTTPS-en keresztül történik
\end{itemize}
\end{frame}
\begin{frame}[shrink=10]{OAuth 2.0 Grant Types}
\begin{block}{Grant Type}
Módszer, ahogyan kliens access token-t szerez.
\end{block}
\begin{enumerate}
\item \textbf{Authorization Code}
\begin{itemize}
\item Legbiztonságosabb, szerveroldali app-hoz
\end{itemize}
\item \textbf{Implicit}
\begin{itemize}
\item \textcolor{red}{Elavult!}
\end{itemize}
\item \textbf{Resource Owner Password}
\begin{itemize}
\item Közvetlen jelszó, megbízható app-okhoz
\end{itemize}
\item \textbf{Client Credentials}
\begin{itemize}
\item Machine-to-machine
\end{itemize}
\item \textbf{PKCE}
\begin{itemize}
\item Modern mobil és SPA-hoz
\end{itemize}
\end{enumerate}
\end{frame}
\begin{frame}[shrink=10]{Authorization Code Flow}
\begin{block}{Leggyakrabban használt flow}
Biztonságos szerveroldali alkalmazásokhoz, ahol a client secret biztonságosan tárolható.
\end{block}
\begin{enumerate}
\small
\item \textbf{Kliens átirányítja a felhasználót} az Authorization Server-re
\begin{itemize}
\item URL: \texttt{/authorize?client\_id=\&redirect\_uri=\&scope=\&state=}
\end{itemize}
\item \textbf{Felhasználó bejelentkezik és hozzájárul}
\item \textbf{Authorization Server visszairányít} authorization code-dal
\begin{itemize}
\item URL: \texttt{redirect\_uri?code=AUTH\_CODE\&state=}
\end{itemize}
\item \textbf{Kliens kicseréli a code-ot} access token-re
\begin{itemize}
\item POST: \texttt{/token} (client\_id, client\_secret, code, redirect\_uri)
\end{itemize}
\item \textbf{Authorization Server visszaad} access token-t (és refresh token-t)
\item \textbf{Kliens használja az access token-t} API kérésekhez
\end{enumerate}
\end{frame}
\begin{frame}[fragile,shrink=10]{Authorization Code Flow - Példa}
\begin{block}{1. Felhasználó átirányítása az authorization endpoint-ra}
\small
\begin{verbatim}
GET /oauth/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=https://yourapp.com/callback&
scope=read write&
state=xyz123
\end{verbatim}
\end{block}
\begin{block}{2. Callback authorization code-dal}
\small
\begin{verbatim}
GET https://yourapp.com/callback?
code=AUTH_CODE_HERE&
state=xyz123
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile,shrink=10]{Authorization Code Flow - Példa (folyt.)}
\begin{block}{3. Token kérés}
\small
\begin{verbatim}
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTH_CODE_HERE&
redirect_uri=https://yourapp.com/callback&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET
\end{verbatim}
\end{block}
\begin{block}{4. Token válasz}
\small
\begin{verbatim}
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
"scope": "read write"
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}{PKCE (Proof Key for Code Exchange)}
\begin{block}{Miért szükséges?}
A natív és SPA (Single Page Application) alkalmazások nem tudják biztonságosan tárolni a client secret-et. A PKCE authorization code interception elleni védelmet nyújt.
\end{block}
\vspace{0.5cm}
\begin{enumerate}
\item Kliens generál egy random \textbf{code\_verifier} értéket
\item Létrehoz egy \textbf{code\_challenge}-t a verifier-ből
\begin{itemize}
\item \texttt{code\_challenge = BASE64URL(SHA256(code\_verifier))}
\end{itemize}
\item Authorization kéréshez csatolja a \texttt{code\_challenge}-t
\item Token kéréshez csatolja a \texttt{code\_verifier}-t
\item Szerver ellenőrzi, hogy a verifier megfelel-e a challenge-nek
\end{enumerate}
\vspace{0.5cm}
\begin{alertblock}{Fontos!}
A PKCE ma már \textbf{minden} OAuth 2.0 flow-hoz ajánlott!
\end{alertblock}
\end{frame}
\begin{frame}{Client Credentials Grant}
\begin{block}{Machine-to-Machine hitelesítés}
Amikor nincs felhasználói interakció, csak két rendszer kommunikál egymással.
\end{block}
\vspace{0.5cm}
\begin{itemize}
\item \textbf{Használati esetek:}
\begin{itemize}
\item Backend szolgáltatások közötti kommunikáció
\item Cron job-ok, scheduled task-ok
\item CLI eszközök
\end{itemize}
\vspace{0.3cm}
\item \textbf{Folyamat:}
\begin{enumerate}
\item Kliens küld egy POST kérést a token endpoint-ra
\item Csatolja a client\_id-t és client\_secret-et
\item Szerver visszaadja az access token-t
\item Kliens használja a token-t API kérésekhez
\end{enumerate}
\end{itemize}
\vspace{0.3cm}
\begin{alertblock}{Figyelem!}
Nincs refresh token, mert nincs felhasználói kontextus!
\end{alertblock}
\end{frame}
\begin{frame}[fragile]{Client Credentials Grant - Példa}
\begin{block}{Token kérés}
\small
\begin{verbatim}
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic BASE64(client_id:client_secret)
grant_type=client_credentials&
scope=api.read
\end{verbatim}
\end{block}
\vspace{0.3cm}
\begin{block}{Token válasz}
\small
\begin{verbatim}
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "api.read"
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Access Token és Refresh Token}
\begin{columns}
\begin{column}{0.48\textwidth}
\begin{block}{Access Token}
\begin{itemize}
\item Rövid élettartam (pl. 1 óra)
\item API kérésekhez használt
\item Bearer token formátum
\item Gyakran JWT
\end{itemize}
\end{block}
\vspace{0.3cm}
\textbf{Használat:}
\small
\begin{verbatim}
Authorization: Bearer
ACCESS_TOKEN
\end{verbatim}
\end{column}
\begin{column}{0.48\textwidth}
\begin{block}{Refresh Token}
\begin{itemize}
\item Hosszú élettartam (napok/hetek)
\item Új access token kéréséhez
\item Biztonságosan tárolva
\item Opaque token
\end{itemize}
\end{block}
\vspace{0.3cm}
\textbf{Token megújítás:}
\small
\begin{verbatim}
POST /oauth/token
grant_type=refresh_token&
refresh_token=REFRESH_TOKEN
\end{verbatim}
\end{column}
\end{columns}
\vspace{0.5cm}
\begin{alertblock}{Best Practice}
Az access token lejárta esetén a refresh token-nel új tokent kérünk, nem kell újra bejelentkeztetni a felhasználót!
\end{alertblock}
\end{frame}
\begin{frame}{Scopes (Hatókörök)}
\begin{block}{Mi az a scope?}
A \textbf{scope} meghatározza, hogy a kliens milyen erőforrásokhoz és milyen műveletekhez férhet hozzá.
\end{block}
\vspace{0.5cm}
\begin{itemize}
\item \textbf{Granulált jogosultságok:}
\begin{itemize}
\item \texttt{read} - olvasási jogosultság
\item \texttt{write} - írási jogosultság
\item \texttt{delete} - törlési jogosultság
\end{itemize}
\vspace{0.3cm}
\item \textbf{Erőforrás specifikus:}
\begin{itemize}
\item \texttt{user:email} - email cím olvasása
\item \texttt{repo:write} - repository írása
\item \texttt{profile:read} - profil olvasása
\end{itemize}
\vspace{0.3cm}
\item \textbf{Best Practices:}
\begin{itemize}
\item Least privilege principle: csak a szükséges scope-ok
\item Egyértelmű elnevezés
\item Dokumentáció minden scope-hoz
\end{itemize}
\end{itemize}
\end{frame}
\begin{frame}{OpenID Connect (OIDC)}
\begin{block}{Authentikációs réteg OAuth 2.0 felett}
Az \textbf{OpenID Connect} kiegészíti az OAuth 2.0-t authentikációs funkcionalitással.
\end{block}
\vspace{0.5cm}
\begin{itemize}
\item \textbf{OAuth 2.0} = Autorizáció
\item \textbf{OpenID Connect} = Autorizáció + Authentikáció
\vspace{0.3cm}
\item \textbf{ID Token:}
\begin{itemize}
\item JWT formátumú token
\item Tartalmazza a felhasználó azonosító adatait
\item Claims: \texttt{sub}, \texttt{name}, \texttt{email}, \texttt{picture}, stb.
\end{itemize}
\vspace{0.3cm}
\item \textbf{UserInfo Endpoint:}
\begin{itemize}
\item További felhasználói információk lekérdezése
\item Access token-nel hívható
\end{itemize}
\vspace{0.3cm}
\item \textbf{Használat:} "Sign in with Google", "Login with Facebook"
\end{itemize}
\end{frame}
\begin{frame}{OAuth 2.0 Biztonsági best practices}
\begin{alertblock}{Kritikus biztonsági szempontok}
\begin{itemize}
\item \textbf{HTTPS kötelező} minden kommunikációhoz!
\item \textbf{State paraméter} használata CSRF védelem miatt
\item \textbf{Client secret} biztonságos tárolása (szerver oldalon)
\item \textbf{Redirect URI validáció} - csak engedélyezett URI-k
\item \textbf{Token lejárati idő} beállítása
\item \textbf{Scope minimalizálása} - least privilege
\end{itemize}
\end{alertblock}
\vspace{0.3cm}
\begin{block}{További ajánlások}
\begin{itemize}
\item PKCE használata minden flow-ban
\item Token revocation támogatása
\item Rate limiting az endpoint-okon
\item Token tárolás biztonságos helyen (HttpOnly cookie)
\item Regular security audit
\end{itemize}
\end{block}
\end{frame}
\begin{frame}{Összefoglalás}
\begin{itemize}
\item \textbf{OAuth 2.0} = Autorizációs protokoll (nem authentikációs!)
\item \textbf{Szerepkörök:} Resource Owner, Client, Authorization Server, Resource Server
\item \textbf{Grant Types:} Authorization Code (PKCE), Client Credentials
\item \textbf{Tokenek:} Access Token (rövid), Refresh Token (hosszú)
\item \textbf{Scopes:} Granulált jogosultság kezelés
\item \textbf{OpenID Connect:} OAuth 2.0 + Authentikáció
\item \textbf{Biztonság:} HTTPS, state, PKCE, token biztonság
\end{itemize}
\vspace{0.5cm}
\begin{exampleblock}{Amikor OAuth 2.0-t használunk}
\begin{itemize}
\item Social login implementációhoz
\item Third-party API hozzáféréshez
\item Microservice authentikációhoz
\item Mobile és SPA alkalmazásokhoz
\end{itemize}
\end{exampleblock}
\end{frame}
-671
View File
@@ -1,671 +0,0 @@
\section{Service Layer}
\begin{frame}[shrink=5]{Mi az a Service Layer?}
\begin{block}{Definíció}
A \textbf{Service Layer} (szolgáltatási réteg) egy tervezési minta, amely elválasztja az üzleti logikát a controller-ektől és a data access layer-től.
\end{block}
\begin{itemize}
\item \textbf{Separation of Concerns:} Felelősségek szétválasztása
\item \textbf{Reusability:} Újrafelhasználható üzleti logika
\item \textbf{Testability:} Könnyebb tesztelhetőség
\item \textbf{Maintainability:} Karbantarthatóság
\end{itemize}
\begin{exampleblock}{MVC architektúrában}
Controller → \textbf{Service Layer} → Repository/Model → Database
\end{exampleblock}
\end{frame}
\begin{frame}[shrink=5]{Háromrétegű architektúra}
\begin{columns}
\begin{column}{0.3\textwidth}
\begin{block}{Presentation Layer}
\begin{itemize}
\item Controllers
\item Routes
\item HTTP kérés/válasz
\item Validáció
\end{itemize}
\end{block}
\end{column}
\begin{column}{0.35\textwidth}
\begin{block}{Business Logic Layer}
\begin{itemize}
\item \textbf{Services}
\item Üzleti szabályok
\item Adatmanipuláció
\item Orchestration
\end{itemize}
\end{block}
\end{column}
\begin{column}{0.3\textwidth}
\begin{block}{Data Access Layer}
\begin{itemize}
\item Repository
\item Models
\item ORM
\item Database
\end{itemize}
\end{block}
\end{column}
\end{columns}
\begin{alertblock}{Fontos!}
A controller \textbf{NEM} tartalmaz üzleti logikát, csak delegál a service-eknek!
\end{alertblock}
\end{frame}
\begin{frame}[shrink=10]{Service Layer előnyei}
\begin{enumerate}
\item \textbf{Separation of Concerns}
\begin{itemize}
\item Tiszta felelősségi körök
\item Controller: HTTP kezelés
\item Service: Üzleti logika
\item Repository: Adatelérés
\end{itemize}
\item \textbf{Reusability}
\begin{itemize}
\item Több controller használhatja ugyanazt a service-t
\item Különböző kontextusokban (API, CLI, Background job)
\end{itemize}
\item \textbf{Testability}
\begin{itemize}
\item Service-ek könnyebben unit tesztelhetők
\item Mock-olható függőségek
\end{itemize}
\item \textbf{Maintainability}
\begin{itemize}
\item Változások izoláltak
\item Könnyebb hibakeresés
\end{itemize}
\end{enumerate}
\end{frame}
\begin{frame}[fragile,shrink=5]{AuthService - Authentikációs szolgáltatás}
\begin{block}{AuthService felelősségei}
\begin{itemize}
\item Felhasználó regisztráció
\item Bejelentkezés (login)
\item Token generálás és validálás
\item Jelszó hash-elés
\item Kijelentkezés (logout)
\end{itemize}
\end{block}
\begin{exampleblock}{AuthService példa}
\small
\begin{verbatim}
class AuthService {
async register(userData) { ... }
async login(email, password) { ... }
async logout(userId) { ... }
async refreshToken(refreshToken) { ... }
async verifyToken(token) { ... }
async resetPassword(email) { ... }
}
\end{verbatim}
\end{exampleblock}
\end{frame}
\begin{frame}[fragile,shrink=10]{AuthService implementáció - Register}
\begin{block}{Regisztráció üzleti logikája}
\small
\begin{verbatim}
class AuthService {
async register(userData) {
const existing = await this.userRepository
.findByEmail(userData.email);
if (existing) throw new Error('User exists');
const hashed = await bcrypt.hash(userData.password, 10);
const user = await this.userRepository.create({
...userData,
password: hashed
});
return { id: user.id, email: user.email };
}
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{AuthService implementáció - Login}
\begin{block}{Bejelentkezés üzleti logikája}
\small
\begin{verbatim}
async login(email, password) {
const user = await this.userRepository.findByEmail(email);
if (!user) throw new Error('Invalid credentials');
const valid = await bcrypt.compare(password, user.password);
if (!valid) throw new Error('Invalid credentials');
const accessToken = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET, { expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.REFRESH_SECRET, { expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Controller használja az AuthService-t}
\begin{block}{Thin Controller - Fat Service}
\small
\begin{verbatim}
app.post('/api/auth/register', async (req, res) => {
try {
const user = await authService.register(req.body);
res.status(201).json({ user });
} catch (error) {
res.status(400).json({ error: error.message });
}
});
app.post('/api/auth/login', async (req, res) => {
try {
const result = await authService.login(
req.body.email, req.body.password
);
res.cookie('refreshToken', result.refreshToken,
{ httpOnly: true, secure: true });
res.json({ accessToken: result.accessToken });
} catch (error) {
res.status(401).json({ error: error.message });
}
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{UserService - Felhasználó kezelés}
\begin{block}{UserService felelősségei}
\begin{itemize}
\item Felhasználói profil lekérdezés
\item Profil módosítás
\item Jelszó változtatás
\item Felhasználó törlés
\item Felhasználó lista (admin)
\end{itemize}
\end{block}
\vspace{0.3cm}
\begin{exampleblock}{UserService példa}
\small
\begin{verbatim}
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async getUserProfile(userId) { ... }
async updateProfile(userId, data) { ... }
async changePassword(userId, oldPassword, newPassword) { ... }
async deleteUser(userId) { ... }
async getAllUsers(filters) { ... }
}
\end{verbatim}
\end{exampleblock}
\end{frame}
\begin{frame}[fragile,shrink=5]{UserService implementáció}
\begin{block}{Jelszó változtatás üzleti logikával}
\small
\begin{verbatim}
class UserService {
async changePassword(userId, oldPass, newPass) {
const user = await this.userRepository.findById(userId);
if (!user) throw new Error('User not found');
const valid = await bcrypt.compare(oldPass, user.password);
if (!valid) throw new Error('Incorrect password');
const hashed = await bcrypt.hash(newPass, 10);
await this.userRepository.update(userId,
{ password: hashed });
await this.tokenService.revokeAllTokens(userId);
return { success: true };
}
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{TokenService - Token kezelés}
\begin{block}{TokenService felelősségei}
\begin{itemize}
\item Access token generálás
\item Refresh token generálás
\item Token validálás
\item Token megújítás (refresh)
\item Token visszavonás (revoke)
\end{itemize}
\end{block}
\vspace{0.3cm}
\begin{exampleblock}{TokenService példa}
\small
\begin{verbatim}
class TokenService {
constructor(tokenRepository) {
this.tokenRepository = tokenRepository;
}
generateAccessToken(payload) { ... }
generateRefreshToken(userId) { ... }
async verifyAccessToken(token) { ... }
async refreshAccessToken(refreshToken) { ... }
async revokeToken(token) { ... }
async revokeAllTokens(userId) { ... }
}
\end{verbatim}
\end{exampleblock}
\end{frame}
\begin{frame}[fragile,shrink=5]{TokenService implementáció}
\begin{block}{Token refresh implementáció}
\small
\begin{verbatim}
class TokenService {
async refreshAccessToken(refreshToken) {
const decoded = jwt.verify(
refreshToken, process.env.REFRESH_SECRET);
const stored = await this.tokenRepository
.findByToken(refreshToken);
if (!stored || stored.revoked)
throw new Error('Token revoked');
const user = await this.userRepository
.findById(decoded.userId);
return {
accessToken: this.generateAccessToken(user)
};
}
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{AuthorizationService - Autorizáció}
\begin{block}{AuthorizationService felelősségei}
\begin{itemize}
\item Jogosultság ellenőrzés
\item Role-based access control (RBAC)
\item Permission-based access control (PBAC)
\item Resource ownership ellenőrzés
\end{itemize}
\end{block}
\vspace{0.3cm}
\begin{exampleblock}{AuthorizationService példa}
\small
\begin{verbatim}
class AuthorizationService {
async hasRole(userId, role) { ... }
async hasPermission(userId, permission) { ... }
async canAccessResource(userId, resourceId, action) { ... }
async isOwner(userId, resourceId) { ... }
}
\end{verbatim}
\end{exampleblock}
\end{frame}
\begin{frame}[fragile]{AuthorizationService implementáció}
\begin{block}{Jogosultság ellenőrzés}
\small
\begin{verbatim}
class AuthorizationService {
constructor(userRepository, permissionRepository) {
this.userRepository = userRepository;
this.permissionRepository = permissionRepository;
}
async canAccessResource(userId, resourceId, action) {
const user = await this.userRepository.findById(userId);
// 1. Admin mindent csinálhat
if (user.role === 'admin') {
return true;
}
// 2. Ownership ellenőrzés
const resource = await this.resourceRepository.findById(resourceId);
if (resource.ownerId === userId && action === 'read') {
return true;
}
// 3. Permission alapú ellenőrzés
const permissions = await this.permissionRepository.getByUserId(userId);
return permissions.some(p => p.action === action && p.resource === resourceId);
}
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Dependency Injection}
\begin{block}{Service-ek kapcsolata}
A service-ek más service-eket használnak. Dependency Injection segít a függőségek kezelésében.
\end{block}
\vspace{0.3cm}
\begin{columns}
\begin{column}{0.48\textwidth}
\textbf{Rossz példa:}
\small
\begin{verbatim}
class AuthService {
constructor() {
this.userRepo =
new UserRepository();
}
}
\end{verbatim}
\end{column}
\begin{column}{0.48\textwidth}
\textbf{Jó példa (DI):}
\small
\begin{verbatim}
class AuthService {
constructor(userRepo) {
this.userRepo = userRepo;
}
}
const authService =
new AuthService(userRepo);
\end{verbatim}
\end{column}
\end{columns}
\end{frame}
\begin{frame}[fragile]{Service Container / DI Container}
\begin{block}{Automatikus Dependency Injection}
DI container-ek automatikusan kezelik a szolgáltatások létrehozását.
\end{block}
\vspace{0.3cm}
\begin{exampleblock}{Awilix library használata}
\small
\begin{verbatim}
const { createContainer, asClass } = require('awilix');
const container = createContainer();
container.register({
userRepository: asClass(UserRepository).singleton(),
tokenRepository: asClass(TokenRepository).singleton(),
authService: asClass(AuthService).singleton(),
userService: asClass(UserService).singleton(),
tokenService: asClass(TokenService).singleton()
});
const authService = container.resolve('authService');
\end{verbatim}
\end{exampleblock}
\end{frame}
\begin{frame}[fragile]{Error Handling a Service Layer-ben}
\begin{block}{Custom Error osztályok}
\small
\begin{verbatim}
class AuthenticationError extends Error {
constructor(message) {
super(message);
this.statusCode = 401;
}
}
class AuthorizationError extends Error {
constructor(message) {
super(message);
this.statusCode = 403;
}
}
class NotFoundError extends Error {
constructor(resource) {
super(`${resource} not found`);
this.statusCode = 404;
}
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Error Handling - Service használat}
\begin{block}{Service dobja a custom error-t}
\small
\begin{verbatim}
class AuthService {
async login(email, password) {
const user = await this.userRepository
.findByEmail(email);
if (!user) {
throw new AuthenticationError('Invalid credentials');
}
const valid = await bcrypt.compare(
password, user.password
);
if (!valid) {
throw new AuthenticationError('Invalid credentials');
}
// ...
}
}
\end{verbatim}
\end{block}
\vspace{0.2cm}
\begin{block}{Controller kezeli}
\small
\begin{verbatim}
app.post('/api/auth/login', async (req, res, next) => {
try {
const result = await authService.login(
req.body.email, req.body.password);
res.json(result);
} catch (error) {
next(error);
}
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Global Error Handler Middleware}
\begin{block}{Központi hibaüzenet kezelés}
\small
\begin{verbatim}
app.use((error, req, res, next) => {
console.error(error);
if (error.statusCode) {
return res.status(error.statusCode)
.json({ error: error.message });
}
res.status(500).json({
error: 'Internal Server Error'
});
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}{Service Layer Testing}
\begin{block}{Unit Testing}
A service-ek izoláltan tesztelhetők mock repository-kkal és service-ekkel.
\end{block}
\vspace{0.3cm}
\begin{itemize}
\item \textbf{Előnyök:}
\begin{itemize}
\item Gyors tesztek (nincs adatbázis)
\item Üzleti logika fókusz
\item Mock-olható függőségek
\end{itemize}
\vspace{0.3cm}
\item \textbf{Test framework-ök:}
\begin{itemize}
\item Jest
\item Mocha + Chai
\item Vitest
\end{itemize}
\vspace{0.3cm}
\item \textbf{Mocking library-k:}
\begin{itemize}
\item Sinon.js
\item Jest built-in mocks
\end{itemize}
\end{itemize}
\end{frame}
\begin{frame}[fragile,shrink=5]{Service Unit Test példa}
\begin{block}{AuthService.login() teszt}
\small
\begin{verbatim}
describe('AuthService', () => {
let authService, mockUserRepo;
beforeEach(() => {
mockUserRepo = { findByEmail: jest.fn() };
authService = new AuthService(mockUserRepo);
});
test('login fails for invalid user', async () => {
mockUserRepo.findByEmail.mockResolvedValue(null);
await expect(
authService.login('test@test.com', 'pass')
).rejects.toThrow('Invalid credentials');
});
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}{Service Layer Best Practices}
\begin{enumerate}
\item \textbf{Single Responsibility:} Egy service egy felelősségi kör
\item \textbf{Dependency Injection:} Konstruktorban injektált függőségek
\item \textbf{Thin Controllers:} Controller csak HTTP kezel
\item \textbf{Error Handling:} Custom error osztályok használata
\item \textbf{Async/Await:} Tiszta aszinkron kód
\item \textbf{Validation:} Input validáció a service-ben is
\item \textbf{Transaction Management:} Adatbázis tranzakciók
\item \textbf{Logging:} Strukturált log-olás
\end{enumerate}
\end{frame}
\begin{frame}{Projekt struktúra Service Layer-rel}
\begin{columns}
\begin{column}{0.48\textwidth}
\textbf{Fájl struktúra:}
\small
\begin{itemize}
\item \texttt{src/}
\begin{itemize}
\item \texttt{controllers/}
\begin{itemize}
\item \texttt{auth.controller.js}
\item \texttt{user.controller.js}
\end{itemize}
\item \texttt{services/}
\begin{itemize}
\item \texttt{auth.service.js}
\item \texttt{user.service.js}
\item \texttt{token.service.js}
\end{itemize}
\item \texttt{repositories/}
\begin{itemize}
\item \texttt{user.repository.js}
\item \texttt{token.repository.js}
\end{itemize}
\item \texttt{models/}
\item \texttt{middlewares/}
\item \texttt{utils/}
\end{itemize}
\end{itemize}
\end{column}
\begin{column}{0.48\textwidth}
\textbf{Rétegek felelősségei:}
\begin{itemize}
\item \textbf{Controller:}
\begin{itemize}
\item HTTP kérés/válasz
\item Validáció (input)
\item Service hívás
\end{itemize}
\vspace{0.3cm}
\item \textbf{Service:}
\begin{itemize}
\item Üzleti logika
\item Adatmanipuláció
\item Orchestration
\end{itemize}
\vspace{0.3cm}
\item \textbf{Repository:}
\begin{itemize}
\item Adatbázis műveletek
\item Query-k
\item ORM interaction
\end{itemize}
\end{itemize}
\end{column}
\end{columns}
\end{frame}
\begin{frame}{Összefoglalás}
\begin{itemize}
\item \textbf{Service Layer} = Üzleti logika réteg
\item \textbf{Separation of Concerns:} Controller, Service, Repository
\item \textbf{AuthService:} Regisztráció, login, token kezelés
\item \textbf{UserService:} Felhasználó kezelés, profil, jelszó
\item \textbf{TokenService:} Token generálás, validálás, refresh
\item \textbf{AuthorizationService:} Jogosultság ellenőrzés
\item \textbf{Dependency Injection:} Konstruktor alapú DI
\item \textbf{Error Handling:} Custom error osztályok
\item \textbf{Testing:} Unit teszt mock-okkal
\item \textbf{Best Practices:} Thin controller, fat service
\end{itemize}
\vspace{0.3cm}
\begin{exampleblock}{Miért használjuk?}
Tiszta kód, újrafelhasználhatóság, tesztelhetőség, karbantarthatóság
\end{exampleblock}
\end{frame}