← Назад к вопросам
Реализовывал ли авторизацию на Node.js
2.0 Middle🔥 231 комментариев
#Node.js и JavaScript#Безопасность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Реализация авторизации и аутентификации на Node.js
Да, я реализовывал различные схемы аутентификации и авторизации в Node.js. Это один из самых критичных компонентов любого приложения, потому что ошибки здесь приводят к взлому учётных записей пользователей.
JWT (JSON Web Tokens) — самый распространённый подход
Мой стандартный паттерн для REST API:
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
// 1. Регистрация пользователя
router.post('/auth/register', async (req, res, next) => {
try {
const { email, password } = req.body;
// Валидация
if (!email || !password) {
return res.status(400).json({ error: 'Missing fields' });
}
// Проверка, не существует ли уже
const exists = await User.findOne({ email });
if (exists) {
return res.status(409).json({ error: 'User already exists' });
}
// Хеширование пароля (НИКОГДА не храним в открытом виде)
const hashedPassword = await bcrypt.hash(password, 12);
// Сохраняем в БД
const user = await User.create({
email,
password: hashedPassword,
createdAt: new Date()
});
res.status(201).json({ userId: user.id });
} catch (error) {
next(error);
}
});
// 2. Логин (получение JWT токена)
router.post('/auth/login', async (req, res, next) => {
try {
const { email, password } = req.body;
// Находим пользователя
const user = await User.findOne({ email });
if (!user) {
// Не говорим, что пользователь не найден (security best practice)
return res.status(401).json({ error: 'Invalid credentials' });
}
// Проверяем пароль
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Генерируем JWT токен
const accessToken = jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{ expiresIn: '1h' } // Короткая жизнь для безопасности
);
// Refresh token (долгоживущий)
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '7d' }
);
// Сохраняем refresh token в БД (для revocation)
await RefreshToken.create({
userId: user.id,
token: refreshToken,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
});
res.json({
accessToken,
refreshToken,
expiresIn: 3600
});
} catch (error) {
next(error);
}
});
Middleware для проверки JWT токена
// Middleware, который проверяет токен во всех защищённых маршрутах
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // "Bearer TOKEN"
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
// Проверяем, истёк ли токен
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expired' });
}
return res.status(403).json({ error: 'Invalid token' });
}
req.user = user; // Добавляем данные пользователя в request
next();
});
};
// Использование
router.get('/profile', authenticateToken, async (req, res) => {
const user = await User.findById(req.user.userId);
res.json(user);
});
// Endpoint для обновления access token
router.post('/auth/refresh', async (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({ error: 'No refresh token' });
}
try {
const verified = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
// Проверяем, не был ли токен отозван
const tokenRecord = await RefreshToken.findOne({ token: refreshToken });
if (!tokenRecord) {
return res.status(401).json({ error: 'Token revoked' });
}
// Генерируем новый access token
const user = await User.findById(verified.userId);
const newAccessToken = jwt.sign(
{ userId: user.id, email: user.email, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
res.json({ accessToken: newAccessToken });
} catch (error) {
res.status(401).json({ error: 'Invalid refresh token' });
}
});
OAuth 2.0 (Вход через Google, GitHub, etc.)
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
// Настройка Passport
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/auth/google/callback'
},
async (accessToken, refreshToken, profile, done) => {
try {
// Проверяем, есть ли уже пользователь с этим Google ID
let user = await User.findOne({ googleId: profile.id });
if (!user) {
// Создаём нового пользователя
user = await User.create({
googleId: profile.id,
email: profile.emails[0].value,
name: profile.displayName,
avatar: profile.photos[0]?.value
});
}
return done(null, user);
} catch (error) {
return done(error);
}
}
)
);
// Маршруты
router.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] })
);
router.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
(req, res) => {
const token = jwt.sign(
{ userId: req.user.id, email: req.user.email },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
// Редирект на фронтенд с токеном
res.redirect(`/auth/callback?token=${token}`);
}
);
Роли и разрешения (RBAC)
// Middleware для проверки ролей
const authorize = (allowedRoles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
// Использование
router.delete('/users/:id',
authenticateToken,
authorize(['admin']),
async (req, res) => {
await User.deleteById(req.params.id);
res.json({ ok: true });
}
);
// Более сложный пример с permission-based access
const checkPermission = (requiredPermission) => {
return async (req, res, next) => {
const user = await User.findById(req.user.userId);
const role = await Role.findById(user.roleId);
if (!role.permissions.includes(requiredPermission)) {
return res.status(403).json({ error: 'Permission denied' });
}
next();
};
};
router.post('/posts',
authenticateToken,
checkPermission('create_posts'),
createPostHandler
);
Защита от атак
// 1. Rate limiting на auth endpoint (защита от brute force)
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 минут
max: 5, // 5 попыток
message: 'Too many login attempts, please try again later'
});
router.post('/auth/login', loginLimiter, loginHandler);
// 2. CSRF protection
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: false });
router.post('/auth/logout', csrfProtection, (req, res) => {
// Отозываем refresh token
res.json({ ok: true });
});
// 3. Secure cookies для refresh token (вместо localStorage)
res.cookie('refreshToken', refreshToken, {
httpOnly: true, // JS не может получить
secure: true, // Только HTTPS
sameSite: 'strict' // CSRF protection
});
// 4. Password reset с одноразовым токеном
router.post('/auth/forgot-password', async (req, res) => {
const { email } = req.body;
const user = await User.findOne({ email });
if (!user) {
// Не говорим, существует ли пользователь
return res.json({ message: 'Check your email' });
}
// Генерируем одноразовый токен с коротким TTL
const resetToken = jwt.sign(
{ userId: user.id, type: 'password-reset' },
process.env.JWT_SECRET,
{ expiresIn: '15m' } // Только 15 минут!
);
// Сохраняем хеш токена (не сам токен)
await PasswordReset.create({
userId: user.id,
tokenHash: await bcrypt.hash(resetToken, 10)
});
// Отправляем ссылку на email
await sendEmail(user.email, `Reset password: /reset/${resetToken}`);
res.json({ message: 'Password reset link sent' });
});
Лучшие практики, которые я применяю
- Никогда не храню пароли в открытом виде — только bcrypt hash
- JWT с коротким TTL (1 час) + refresh tokens (7 дней)
- Refresh tokens в httpOnly cookies, не в localStorage
- Rate limiting на auth endpoints
- HTTPS always — токены только по защищённому каналу
- Логирование попыток входа — detect suspicious activity
- 2FA для критичных операций (изменение email, выход из всех устройств)
- Регулярная смена JWT_SECRET — старые токены становятся невалидными
- CORS + CSRF protection — защита от атак
Это основные паттерны, которые я использую в production приложениях.