Как защитить маршрут с помощью JWT в nodeJs?

Вопрос или проблема

Я хочу сделать маршрут (/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. Убедитесь, что токены правильно создаются, передаются и проверяются на серверной стороне. Если все реализовано корректно, у вас не возникнет проблем с доступом на защищенные страницы.

Оцените материал
Добавить комментарий

Капча загружается...