\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 } \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}