Получение «401 Unauthorized» при доступе к защищенному маршруту в Express.js с помощью экспресс-сессии и паспорта

Я создаю веб-приложение BLOB-объектов с использованием Express.js, где пользователи могут входить в систему и получать доступ к защищенным маршрутам. Я настроил управление сеансами с помощью экспресс-сессии и аутентификацию с помощью паспорта-локального. Однако, когда я пытаюсь получить доступ к маршруту /home после входа в систему, я постоянно получаю ошибку «401 Несанкционировано». Он может ненадолго открыться примерно на 1-2 секунды, а затем направить меня в /login.

import express from 'express';
import bodyParser from 'body-parser';
import cors from 'cors';
import pg from 'pg';
import { fileURLToPath } from 'url';
import path from 'path';
import bcrypt from 'bcrypt';
import passport from 'passport';
import { Strategy } from 'passport-local';
import session from 'express-session';
import dotenv from 'dotenv';

dotenv.config(); 

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

let { PGHOST, PGDATABASE, PGUSER, PGPASSWORD, ENDPOINT_ID } = process.env;

PGPASSWORD = decodeURIComponent(PGPASSWORD);

const pool = new pg.Pool({
  user: PGUSER,
  host: PGHOST,
  database: PGDATABASE,
  password: PGPASSWORD,
  port: 5432,
  ssl: {
    rejectUnauthorized: false,
  },
  connectionTimeoutMillis: 3000, 
});

const app = express();
const PORT = process.env.PORT || 5000;
const saltRounds = 10;

app.use(cors({
    origin: 'https://blog-t7q7.onrender.com',
    credentials: true
}));

app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
app.use(session({
    secret: 'TOPSECRETWORD',
    resave: false,
    saveUninitialized: false,
    cookie: {
        secure: true, 
        sameSite: 'none', 
        httpOnly: true,
    }
}));

app.use(passport.initialize());
app.use(passport.session());


const isAuthenticated = (req, res, next) => {
  if (res.isAuthenticated()) {
    return next();
  } else {
    res.status(401).json({ error: 'Unauthorized' });
  }
};


app.get("/home", isAuthenticated, async (req, res) => {
    try {
        const result = await pool.query('SELECT post_title, post_content FROM userposts');
        res.status(200).json(result.rows);
    } catch (error) {
        console.error('Error fetching posts:', error);
        res.status(500).json({ error: 'Internal Server Error' });
    }
});


app.post("/register", async (req, res) => {
    const { username, userpassword } = req.body;
    try {
        const userCheckResult = await pool.query('SELECT * FROM blog WHERE username = $1', [username]);
        if (userCheckResult.rows.length > 0) {
            return res.status(400).send({ error: "Username already taken" });
        }

        const hashedPassword = await bcrypt.hash(userpassword, saltRounds);
        const result = await pool.query('INSERT INTO blog (username, userpassword) VALUES ($1, $2) RETURNING *', [username, hashedPassword]);
        const user = result.rows[0];
        req.login(user, (err) => {
            if (err) {
                console.info(err);
                res.status(500).send({ error: "Registration failed" });
            } else {
                res.status(200).send({ message: "User registered successfully" });
            }
        });

    } catch (error) {
        console.error("Error inserting user:", error);
        res.status(500).send({ error: "Registration failed" });
    }
});


app.post("/login", (req, res, next) => {
    passport.authenticate("local", (err, user, info) => {
        if (err) {
            console.error('Error during authentication:', err);
            return res.status(500).send({ error: "Internal Server Error" });
        }
        if (!user) {
            console.info('Authentication failed:', info);
            return res.status(401).send({ error: "Invalid username or password" });
        }
        req.logIn(user, (err) => {
            if (err) {
                console.error('Error logging in user:', err);
                return res.status(500).send({ error: "Internal Server Error" });
            }
            return res.status(200).send({ message: "User logged in successfully" });
        });
    })(req, res, next);
});


app.post("/postblog", isAuthenticated, async (req, res) => {
    const { postTitle, postContent } = req.body;
    try {
        await pool.query('INSERT INTO userposts (post_title, post_content) VALUES ($1, $2)', [postTitle, postContent]);
        res.status(200).send({ message: "Post inserted successfully" });
    } catch (error) {
        console.error("Error inserting post:", error);
        res.status(500).send({ error: "Error inserting post" });
    }
});


app.post("/post", isAuthenticated, async (req, res) => {
    const { postTitle } = req.body;
    console.info('Received request to fetch post:', postTitle);
    try {
        const result = await pool.query('SELECT * FROM userposts WHERE post_title = $1', [postTitle]);
        console.info('Query result:', result.rows);
        if (result.rows.length > 0) {
            const post = result.rows[0];
            res.status(200).send({ post });
        } else {
            res.status(404).send({ error: "Post not found" });
        }
    } catch (error) {
        console.error("Error fetching post:", error);
        res.status(500).send({ error: "Error fetching post" });
    }
});

passport.use(new Strategy(
    {
        usernameField: 'username',
        passwordField: 'userpassword'
    },
    async (username, password, cb) => {
        try {
            const result = await pool.query('SELECT * FROM blog WHERE username = $1', [username]);
            if (result.rows.length > 0) {
                const user = result.rows[0];
                const storedHashedPassword = user.userpassword;
                bcrypt.compare(password, storedHashedPassword, (err, isMatch) => {
                    if (err) {
                        return cb(err);
                    }
                    if (isMatch) {
                        return cb(null, user);
                    } else {
                        return cb(null, false, { message: 'Incorrect username or password.' });
                    }
                });
            } else {
                return cb(null, false, { message: 'Incorrect username or password.' });
            }
        } catch (error) {
            console.error("Error logging in user:", error);
            return cb(error);
        }
    }
));

passport.serializeUser((user, cb) => {
    cb(null, user.id);
});

passport.deserializeUser(async (id, cb) => {
    try {
        const result = await pool.query('SELECT * FROM blog WHERE id = $1', [id]);
        if (result.rows.length > 0) {
            cb(null, result.rows[0]);
        } else {
            cb(new Error('User not found'));
        }
    } catch (error) {
        cb(error);
    }
});

app.listen(PORT, () => {
    console.info(`Server is running on port ${PORT}`);
});

это мой index.js

import React, { useState } from "react";
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
import './Login.css'; // Import the CSS file

function Login() {
    const [name, setName] = useState("");
    const [password, setPassword] = useState("");
    const navigate = useNavigate();

    const handleNameChange = (event) => {
        setName(event.target.value);
    }

    const handlePasswordChange = (event) => {
        setPassword(event.target.value);
    }

    const loginUser = async (event) => {
        event.preventDefault();
        try {
            const response = await axios.post('https://blog-backend-khj7.onrender.com/login', 
            { username: name, userpassword: password },
            { withCredentials: true }); 

            if (response.status === 200) {
                navigate('/home');
            }
        } catch (error) {
            if (error.response) {
                if (error.response.status === 400 || error.response.status === 401) {
                    alert("Invalid username or password");
                } else {
                    console.error('Error logging in user:', error);
                    alert("There was an error logging in the user");
                }
            } else {
                console.error('Error logging in user:', error);
                alert("There was an error logging in the user");
            }
        }
    }
    
    return (
        <div className = "login-background">
            <div className = "login-container">
                <h1>Login</h1>
                <form onSubmit = {loginUser}>
                    <input type = "text" onChange = {handleNameChange} name = "username" value = {name} placeholder = "Username" required />
                    <input type = "password" onChange = {handlePasswordChange} name = "userpassword" value = {password} placeholder = "Password" required />
                    <button type = "submit">Submit</button>
                </form>
            </div>
        </div>
    );
}

export default Login;

логин.jsx

import React, { useState } from "react";
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
import './Register.css'; // Import the CSS file

function Register() {
    const [name, setName] = useState("");
    const [password, setPassword] = useState("");
    const navigate = useNavigate();

    function handleNameChange(event) {
        setName(event.target.value);
    }

    function handlePasswordChange(event) {
        setPassword(event.target.value);
    }

    async function registerUser(event) {
        event.preventDefault();
        try {
            const response = await axios.post('https://blog-backend-khj7.onrender.com/register', 
                { username: name, userpassword: password },
                { withCredentials: true } 
            );
            if (response.status === 200) {
                navigate('/home');
            }
        } catch (error) {
            if (error.response && error.response.status === 400) {
                alert("Username already exists");
            } else {
                console.error('There was an error registering the user:', error);
            }
        }
    }

    function goToLogin() {
        navigate('/login');
    }

    return (
        <div className = "register-background">
            <h1 className = "register-header">Welcome to blog</h1>
            <div className = "register-container">
                <h1>Register</h1>
                <form onSubmit = {registerUser}>
                    <input type = "text" onChange = {handleNameChange} value = {name} placeholder = "Username" />
                    <input type = "password" onChange = {handlePasswordChange} value = {password} placeholder = "Password" />
                    <button type = "submit">Submit</button>
                </form>
            </div>
            <h3>Already registered?</h3>
            <h4 onClick = {goToLogin} className = "blog-go-login">Login</h4>
        </div>
    );
}

export default Register;

регистр.jsx

Сайт работает! Вы также можете проверить https://blog-t7q7.onrender.com/

github https://github.com/AMEYSATI/Блог

Дело в инструментах разработчика, когда я правильно вхожу в систему, сеанс cookie передается, но я думаю, он не получен /home или что-то в этом роде.

Пожалуйста, помогите мне, так как это тоже для меня ново.

Вы запускаете это приложение через http или https? Secure: true будет отправлять файлы cookie только при наличии соединения https. См. цитату «Файл cookie с атрибутом Secure отправляется на сервер только с зашифрованным запросом по протоколу HTTPS. Он никогда не отправляется по незащищенному HTTP (кроме локального хоста)...» цитата Developer.mozilla.org /en-US/docs/Web/HTTP/Cookies

WeDoTheBest4You 26.06.2024 09:34

Также попробуйте заменить это if (response.status === 200) { Navigation('/home'); } с константным ответом = await axios.get('blog-backend-khj7.onrender.com/home', { withCredentials: true // Включаем учетные данные в запрос });. Это делается для того, чтобы проверить и выяснить, связана ли проблема с вызовом axios.get... через маршрутизатор реагирования.

WeDoTheBest4You 26.06.2024 09:40

@WeDoTheBest4You мое приложение обслуживается через HTTPS, а это означает, что для атрибута безопасности для файлов cookie должно быть установлено значение true. Так что я думаю, что проблема может быть не в этом. Возможно, я ошибаюсь. blog-t7q7.onrender.com и теперь я попробую второе решение. Спасибо за ответ :)

elclasico 26.06.2024 21:44

Ничего не работает. Пожалуйста, помогите. Я не знаю, что делать.

elclasico 27.06.2024 12:58

Привет, удалось попробовать второй вариант?

WeDoTheBest4You 27.06.2024 13:00

да, но это все равно не работает

elclasico 27.06.2024 13:02

Вход работает нормально, но не авторизуется. На рендере на внутреннем хосте это выглядит следующим образом: Вход успешен. Сеанс: сеанс {cookie: {путь: '/', _expires: null, originalMaxAge: null, httpOnly: true, secure: true, SameSite: 'none', домен: '.onrender.com' }, паспорт: {пользователь: 31 } } итак сеанс создается

elclasico 27.06.2024 13:04

Посмотрим на это.

WeDoTheBest4You 27.06.2024 13:20

Привет, это приложение хорошо работает в разработке?

WeDoTheBest4You 01.07.2024 11:26

да, до того, как он был размещен, он работал хорошо. Я только что изменил конфигурацию промежуточного программного обеспечения экспресс-сессии, где я установил secure:true

elclasico 01.07.2024 14:19

Спасибо за обновление. Я изучаю это. Буду держать вас в курсе.

WeDoTheBest4You 02.07.2024 02:58

Можете ли вы прочитать этот пост и попробовать решение: stackoverflow.com/questions/78581724/…. Он доверяет прокси app.set('trust proxy', 1) // доверяем первому прокси

WeDoTheBest4You 02.07.2024 10:35

Мне сейчас очень хочется плакать... Огромное спасибо, брат, я отказался от этого и решил создать другой проект, но увидел твое уведомление и сделал последнюю попытку. Еще раз спасибо :) Если есть способ подписаться на тебя тогда, пожалуйста, скажи мне

elclasico 02.07.2024 22:23

Вы уже проделали всю тяжелую работу по его разработке. Это последнее обновление представляет собой своего рода работу DevOps, о которой большинство разработчиков узнают только по мере необходимости. Поэтому не о чем беспокоиться, продолжайте идти своим путем, выполняйте все больше и больше развивающих работ, оттачивайте свои навыки. Одно небольшое напоминание, применимое ко всем разработчикам, включая меня: всегда задавайте вопрос «Почему так?» и постарайтесь найти ответы, адекватные контексту. Таким образом, вы станете самодостаточными во всех своих начинаниях в области развития. Это не совет, а ключ в работе развития...

WeDoTheBest4You 03.07.2024 04:27

...Мы еще будем общаться на этом форуме. Большое спасибо за ваше время и общение. Кстати, может быть полезно, если вы запишете официальное завершение этого вопроса, поэтому попросите подготовить краткий ответ «проблема, основная причина и решение», опубликовать его самостоятельно рядом с этим вопросом и отметить этот вопрос. как закрыто. Таким образом, данное начинание станет полезным и для общества.

WeDoTheBest4You 03.07.2024 04:36
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
15
75
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Итак, проблема заключалась в том, что до того, как я разместил свой проект на рендеринге, он работал, то есть сеанс успешно создавался, и файлы cookie передавались с каждым запросом, но после того, как он был размещен, файлы cookie передавались после регистрации и входа в систему, но не проходили аутентификацию.

Решение состоит в том, что вам просто нужно написать app.set('trust proxy', 1)

Установив app.set('trust proxy', 1), вы включили Express, чтобы доверять первому прокси в цепочке заголовков X-Forwarded-*, что часто необходимо, когда ваше приложение находится за прокси-сервером, например, используемым некоторыми хостинг-провайдерами. . https://expressjs.com/en/resources/middleware/session.html

Проще говоря, пользователь пытается войти в систему или запросить определенную страницу, которую вы аутентифицировали. Сначала запрос поступает на прокси-сервер (например, сервер, используемый Render, Heroku, Netlify). Прокси-сервер перенаправляет запрос на ваше приложение Express. Express не доверяет информации от прокси-сервера. Он считает, что запрос исходит от прокси-сервера, а не от фактического пользователя. Безопасные файлы cookie и IP-адреса клиентов обрабатываются неправильно, поскольку Express не доверяет прокси-серверу.

Таким образом, установив настройку app.set('trust proxy', 1), Express доверяет первому прокси в цепочке. Он видит исходную информацию пользователя (например, IP-адрес), предоставленную прокси. Безопасные файлы cookie обрабатываются правильно, поскольку Express знает исходный соединение безопасно.

Другие вопросы по теме