Що таке JWT і як він працює?
JWT (JSON Web Token) - це відкритий стандарт (RFC 7519) для безпечної передачі інформації між сторонами у вигляді JSON об'єкта. Зазвичай використовується для автентифікації та обміну інформацією.
Структура JWT
JWT складається з трьох частин, розділених крапками (.):
``` xxxxx.yyyyy.zzzzz ```
1. Header (Заголовок)
Містить тип токена та алгоритм підпису:
```json { "alg": "HS256", "typ": "JWT" } ```
2. Payload (Корисне навантаження)
Містить claims (дані користувача):
```json { "sub": "1234567890", "name": "Іван Петренко", "email": "ivan@example.com", "iat": 1516239022, "exp": 1516242622 } ```
3. Signature (Підпис)
Забезпечує що токен не було підроблено:
```javascript HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret ) ```
Повний приклад
``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ```
Як працює автентифікація
1. Логін користувача
```javascript // Backend (Node.js/Express) app.post('/api/login', async (req, res) => { const { email, password } = req.body;
// Перевірка облікових даних const user = await User.findByEmail(email); if (!user || !await user.checkPassword(password)) { return res.status(401).json({ error: 'Невірні облікові дані' }); }
// Генерація JWT const jwt = require('jsonwebtoken'); const token = jwt.sign( { userId: user.id, email: user.email, role: user.role }, process.env.JWT_SECRET, { expiresIn: '24h' } );
res.json({ token }); }); ```
2. Клієнт зберігає токен
```javascript // Frontend const login = async (email, password) => { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) });
const { token } = await response.json();
// Зберігання в localStorage localStorage.setItem('token', token); // Або в пам'яті для кращої безпеки // sessionStorage.setItem('token', token); }; ```
3. Клієнт відправляє токен
```javascript // Включення токена в запити const getData = async () => { const token = localStorage.getItem('token');
const response = await fetch('/api/protected', { headers: { 'Authorization': `Bearer ${token}` } });
return response.json(); }; ```
4. Сервер перевіряє токен
```javascript // Middleware для перевірки JWT const jwt = require('jsonwebtoken');
const verifyToken = (req, res, next) => { const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Токен не надано' }); }
const token = authHeader.substring(7);
try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; next(); } catch (error) { return res.status(401).json({ error: 'Невірний токен' }); } };
// Захищений роут app.get('/api/protected', verifyToken, (req, res) => { res.json({ data: 'Секретні дані', user: req.user }); }); ```
JWT Claims
Стандартні Claims (зареєстровані)
- `iss` (issuer): Видавець токена
- `sub` (subject): ID користувача
- `aud` (audience): Призначений отримувач
- `exp` (expiration): Час закінчення дії
- `iat` (issued at): Час створення токена
- `nbf` (not before): Токен не дійсний до цього часу
```javascript const token = jwt.sign( { iss: 'myapp.com', sub: '123', aud: 'myapp-api', exp: Math.floor(Date.now() / 1000) + (60 * 60), // 1 година iat: Math.floor(Date.now() / 1000) }, secret ); ```
Найкращі практики
1. Короткий час дії
```javascript // Access token: короткоживучий const accessToken = jwt.sign(payload, secret, { expiresIn: '15m' });
// Refresh token: довгоживучий, більш безпечний const refreshToken = jwt.sign(payload, refreshSecret, { expiresIn: '7d' }); ```
2. Безпечне зберігання
```javascript // ❌ Погано: localStorage вразливий до XSS localStorage.setItem('token', token);
// ✅ Краще: httpOnly cookie res.cookie('token', token, { httpOnly: true, secure: true, sameSite: 'strict' });
// ✅ Найкраще: Короткоживучі токени + механізм оновлення ```
3. Патерн оновлення токена
```javascript // Frontend let accessToken = ''; let refreshToken = '';
const refreshAccessToken = async () => { const response = await fetch('/api/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken }) });
const { accessToken: newToken } = await response.json(); accessToken = newToken; };
// Axios interceptor для автоматичного оновлення axios.interceptors.response.use( response => response, async error => { if (error.response?.status === 401) { await refreshAccessToken(); // Повторити оригінальний запит return axios(error.config); } return Promise.reject(error); } ); ```
Типові питання на співбесіді
-
П: JWT vs автентифікація на основі сесій? В: JWT без стану (немає серверного зберігання), краще масштабується. Сесії зберігають стан на сервері, легше відкликати. JWT краще для мікросервісів/API.
-
П: Чи можна розшифрувати JWT? В: JWT ПІДПИСАНИЙ, а не зашифрований. Будь-хто може декодувати та прочитати payload. Для чутливих даних використовуйте JWE (JSON Web Encryption).
-
П: Як анулювати JWT? В: JWT не можна анулювати (без стану). Рішення: короткий час дії, чорний список (втрачає перевагу без стану), або використання refresh токенів.
-
П: Де зберігати JWT? В: HttpOnly cookies (найкраще), пам'ять (скидається при оновленні), localStorage (вразливий до XSS). Ніколи не зберігайте в localStorage для чутливих додатків.
Міркування безпеки
✅ РОБІТЬ:
- Використовуйте сильні секрети (256+ біт)
- Встановлюйте короткий час дії
- Використовуйте лише HTTPS
- Валідуйте токени на кожному запиті
- Впроваджуйте ротацію refresh токенів
❌ НЕ РОБІТЬ:
- Зберігайте чутливі дані в payload
- Використовуйте слабкі секрети
- Пропускайте валідацію expiration
- Довіряйте клієнтському декодуванню
- Зберігайте в localStorage для чутливих даних
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.