- Вопрос или проблема
- Предыдущий ответ
- Ответ или решение
- Шаг 1: Убедитесь, что JWT корректно создается и отправляется
- Шаг 2: Настройте middleware для проверки токена
- Шаг 3: Обновите сервер для использования middleware
- Шаг 4: Проверьте, как отправляется токен со стороны клиента
- Шаг 5: Обработка ошибок аутентификации
- Шаг 6: Проверка логики работы с токенами
- Заключение
Вопрос или проблема
Я хочу сделать маршрут (/homepage), доступный только для авторизованных пользователей, но я не могу это реализовать.
В общем, я создал модуль аутентификации, используя JWT и MongoDB, и сейчас я застрял на том, чтобы сделать страницу доступной только для авторизованных пользователей.
На самом деле все работает, токен хранится в localstorage, и authMiddleware возвращает успешный статус. Но проблема в том, что я по-прежнему не могу зайти на /homepage, когда я авторизован (на странице написано: требуется аутентификация).
Я думаю, что мне стоит изменить всю систему проверки авторизации.
Я искренне запутался, поэтому я приведу все файлы своего проекта. Однако самые важные, вероятно, это authMiddleware.js; server.js; signin.js.
authController.js :
require('dotenv').config();
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const User = require('../models/User');
exports.signup = async (req, res) => {
const { name, password } = req.body;
try {
const hashedPassword = await bcrypt.hash(password, 10);
const user = await User.create({ name, password: hashedPassword });
const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: '1d' });
res.status(201).json({ token });
} catch (error) {
res.status(500).json({ error: 'Не удалось создать пользователя' });
}
};
exports.checkAlreadyExists = async (req, res) => {
const { name } = req.body;
try {
const user = await User.findOne({ name });
if (user) {
res.status(200).json({ exists: true });
} else {
res.status(200).json({ exists: false });
}
} catch (error) {
res.status(500).json({ error: 'Не удалось проверить существование пользователя' });
}
};
exports.login = async (req, res) => {
const { name, password } = req.body;
try {
const user = await User.findOne({ name });
if (!user) {
return res.status(401).json({ error: 'Некорректные учетные данные' });
}
const isMatch = await user.comparePassword(password);
if (!isMatch) {
return res.status(401).json({ error: 'Некорректные учетные данные' });
}
const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: '1d' });
res.status(200).json({ token });
} catch (error) {
res.status(500).json({ error: 'Не удалось войти в систему' });
}
};
authMiddleware.js :
const jwt = require('jsonwebtoken');
const isAuthenticated = (req, res, next) => {
console.log('Authorization Header:', req.headers['authorization']);
const token = req.headers['authorization']?.split(' ')[1];
if (!token) {
console.log('Токен не найден');
return res.status(401).json({ error: 'Токен не предоставлен' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) {
console.log('Проверка токена не удалась:', err);
return res.status(401).json({ error: 'Не удалось аутентифицировать токен' });
}
req.userId = decoded.id;
next();
});
};
module.exports = isAuthenticated;
authRoutes.js :
// authRoute.js
const express = require('express');
const path = require('path');
const { signup, login, checkAlreadyExists } = require('../controllers/authController');
const router = express.Router();
router.post('/signup', signup);
router.post('/checkAlreadyExists', checkAlreadyExists);
router.post('/login', login);
module.exports = router;
server.js :
require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const authRoutes = require('./routes/authRoute');
const isAuthenticated = require('./middleware/authMiddleware');
const app = express();
app.use(express.json());
app.use(express.static('public'));
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
app.use('/api/auth', authRoutes);
app.get("https://stackoverflow.com/", (req, res) => {
res.sendFile(__dirname + '/public/signup.html');
});
app.get('/verifyToken', isAuthenticated, (req, res) => {
res.status(200).json({ message: 'Токен действителен' });
});
app.get('/homepage', isAuthenticated,(req, res) => {
res.sendFile(__dirname + '/public/homepage.html');
})
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Сервер запущен на порту ${PORT}`);
});
signin.js :
document.getElementById('signin-form').addEventListener('submit', async (event) => {
event.preventDefault();
const name = document.getElementById('name').value;
const password = document.getElementById('password').value;
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, password }),
});
const data = await response.json();
if (response.ok) {
localStorage.setItem('token', data.token);
const isSignedIn = await fetch('/homepage', {
method: 'GET',
headers: {
'Authorization': `Bearer ${data.token}`
}
});
} else {
alert(data.error);
}
} catch (error) {
console.error('Ошибка:', error);
alert('Произошла ошибка. Пожалуйста, попробуйте снова.');
}
});
Я знаю, что это не простая задача, поэтому действительно, спасибо вам.
PS: Некоторые другие посты говорят об этом, но я не думаю, что они действительно подходят под мой код.
Хорошо, я лучше понимаю, в чем ваша проблема;
Вы используете JsonWebToken, и обычно эти токены не определяются как сессионные токены.
Это означает, что они не отправляются с вашими запросами, они обычно отправляются через заголовок Authorization
; и извлекаются из заголовка WWW-Authenticate
в ответе.
Вам стоит взглянуть на этот ответ
Итак, чтобы отправлять токен с каждым запросом и иметь возможность получить доступ к /homepage
с помощью простого GET, вы можете использовать куки вместо этого (см. этот ответ) (на стороне сервера, в authController.js и/или authMiddleware.js);
// server.js
app.use(express.cookieParser());
// И в ваших функциях signup(), login() и/или isAuthenticated()
res.cookie('token', tokenValue, { maxAge: 10000, httpOnly: true });
Предыдущий ответ
Это происходит потому, что вы не проходите через middleware isAuthenticated()
;
Вам может понадобиться что-то вроде этого;
app.use('/homepage', isAuthenticated);
app.get('/homepage', (req, res) => {
res.sendFile(__dirname + '/public/homepage.html');
});
И добавьте в начало authMiddleware.js;
const jwt = require('jsonwebtoken');
require('dotenv').config();
[...]
Точно так же, как вы сделали для authController.js, чтобы получить доступ к вашим переменным окружения.
Ответ или решение
Чтобы обеспечить безопасность маршрута /homepage
в Node.js при помощи JWT (JSON Web Token), необходимо реализовать несколько ключевых шагов. Ниже представлена пошаговая инструкция по настройке и корректной реализации проверки аутентификации пользователя.
Шаг 1: Убедитесь, что JWT корректно создается и отправляется
Ваш код для аутентификации в authController.js
выглядит хорошо. При успешном входе в систему вы создаете токен и отправляете его пользователю. Убедитесь, что этот токен правильно сохраняется в localStorage
на стороне клиента, как показано в signin.js
.
localStorage.setItem('token', data.token);
Шаг 2: Настройте middleware для проверки токена
В вашем authMiddleware.js
, вы правильно проверяете наличие токена и его валидность. Однако, убедитесь, что заголовок Authorization
в запросах работает корректно. Вот как выглядит ваше middleware:
const jwt = require('jsonwebtoken');
const isAuthenticated = (req, res, next) => {
const token = req.headers['authorization']?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(401).json({ error: 'Failed to authenticate token' });
}
req.userId = decoded.id;
next();
});
};
module.exports = isAuthenticated;
Шаг 3: Обновите сервер для использования middleware
В файле server.js
вы должны убедиться, что middleware isAuthenticated
применяется к маршруту /homepage
. В вашем случае, вы вызываете его при определении маршрута:
app.get('/homepage', isAuthenticated, (req, res) => {
res.sendFile(__dirname + '/public/homepage.html');
});
Кроме того, проверьте, что middleware применяется перед обработчиком маршрута. Вы можете сделать это на уровне маршрутизации:
app.use('/homepage', isAuthenticated);
app.get('/homepage', (req, res) => {
res.sendFile(__dirname + '/public/homepage.html');
});
Шаг 4: Проверьте, как отправляется токен со стороны клиента
В вашем коде для отправки GET запроса на /homepage
в signin.js
, убедитесь, что токен отправляется в заголовках:
const isSignedIn = await fetch('/homepage', {
method: 'GET',
headers: {
'Authorization': `Bearer ${data.token}`
}
});
Ваша логика выглядит правильно, но необходимо удостовериться, что такой запрос выполняется после успешного входа в систему.
Шаг 5: Обработка ошибок аутентификации
Обратите внимание на обработку ошибок. Если пользователь не авторизован, важно предоставить адекватную информацию о том, что аутентификация не удалась. Возможно, стоит добавить более детальное сообщение в случае ошибки:
if (response.status === 401) {
alert('Аутентификация не выполнена. Пожалуйста, войдите снова.');
}
Шаг 6: Проверка логики работы с токенами
Включите журналы для отладки. Выводите на консоль информацию о токенах и проверяйте, приходит ли токен и действительно ли он действителен. Убедитесь, что на сервер получаете правильный токен:
console.log('Authorization Header:', req.headers['authorization']);
Заключение
Проверка аутентификации с использованием JWT — это эффективный способ защитить ваши маршруты в Node.js. Убедитесь, что токены правильно создаются, передаются и проверяются на серверной стороне. Если все реализовано корректно, у вас не возникнет проблем с доступом на защищенные страницы.