Cookie从本地主机上的节点服务器发送到ejs客户端,但不会在浏览器中的axios的后续请求中发送回来,

vhmi4jdf  于 2023-04-11  发布在  iOS
关注(0)|答案(1)|浏览(112)

问题

我有两个express node应用程序-一个纯粹充当REST服务器,另一个是使用ejs的前端,并使用axios向后端发送请求。我试图在我的开发环境中让这两个应用程序都运行在localhost上。
我正在尝试登录http://localhost:3000/login,它将用户名/密码作为POST请求发送到http://localhost:4000/login,并且为令牌正确设置了Set-Cookie头:

'set-cookie': 'token=[redacted]; Max-Age=28800; Domain=localhost; Path=/; Expires=Wed, 05 Apr 2023 20:42:47 GMT; HttpOnly; SameSite=Strict',

但是,当成功重定向到http://localhost:3000/transactions页面时,cookie没有被发送,因此身份验证失败。

我所尝试的

这段代码使用Postman处理cookie,这让我认为这是Chrome的安全更改和/或CORS问题的问题,尽管我在Chrome控制台中没有收到任何CORS错误消息。
Other stackoverflow questions似乎证实了这一理论,但我仍然无法成功地将cookie与后续请求一起发送到服务器(即下面的/transactions调用)
我的服务器端CORS配置现在是这样的,我想我已经涵盖了上述问题的所有基础,但我仍然怀疑这里是我的问题所在:

app.use(cors({
    origin: ['http://127.0.0.1:3000','http://localhost:3000'],
    methods: ['POST', 'PUT', 'GET', 'OPTIONS', 'HEAD'],
    credentials: true,
    allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'],
    exposedHeaders: ['*', 'Authorization', "X-Set-Cookie"]
}));
  • 我知道我需要发送带有凭据的请求,我已经在每个请求中都尝试过了,并且作为默认的Just in case it's an axios issue
  • 我知道浏览器对localhost有特殊的处理,在某些情况下它被视为安全环境,但我不清楚这是否是设置安全cookie的情况-我已经将secure: true/falsesameSite: "Strict","None"的值切换到所有组合,但没有更改。
  • 我试着把所有东西(服务器和客户端)都切换到使用127.0.0.1而不是localhost,但没有任何变化。
  • 我尝试使用mkcert安装本地SSL证书,以确保我可以正确设置secure: truesameSite: "None",但没有更改。注意,我在下面发布之前删除了此代码,因为似乎其他人已经在不需要本地主机上的HTTPS的情况下完成了此工作,所以我放弃了这一点,并没有将其包含在下面的代码中。

任何关于下一步尝试什么的想法将不胜感激。

扩展代码
服务端-http://localhost:4000/index.js
require('dotenv').config({path: './.env'});
const express = require('express');
const logger = require('morgan');
const expressSanitizer = require('express-sanitizer');
const cors = require('cors');
const cookieParser = require('cookie-parser');

let app = express();

app.use(cors({
    origin: ['http://127.0.0.1:3000','http://localhost:3000'],
    methods: ['POST', 'PUT', 'GET', 'OPTIONS', 'HEAD'],
    credentials: true,
    allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'],
    exposedHeaders: ['*', 'Authorization', "X-Set-Cookie"]
}));

app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(expressSanitizer());
app.use(cookieParser());
app.use(logger('dev'));
app.use(require('./routes/general/authentication.js'));
app.use(require('./handlers/authHandler.js'));
app.use(require('./routes/general/generalRoute.js'));

// Start the server
if(!module.parent){
    let port = process.env.PORT != null ? process.env.PORT : 4000;
    var server = app.listen(port, 'localhost', function() {
        console.log(`Server started on port ${port}...`);
    });
}

module.exports = app;
authentication.js

process.env.SECURE_COOKIE = false

const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const db = require('../../database/database');
const { check, validationResult } = require('express-validator');

require('dotenv').config({path: './.env'});

const secret = process.env['SECRET'];
const dbURL = process.env['DB_URL'];
const saltRounds = 10;

router.post('/login', [ 
  check('username').exists().escape().isEmail(),
  check('password').exists().escape()
  ], async(req, res, next) => {
    try {

        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            res.statusCode = 400;
            return next('Authentication failed! Please check the request');
        }

        res.setHeader('content-type', 'application/json');

        if (req.body.username && req.body.password) {
            let dbhash = await db.getHashedPassword(req.body.username);
            bcrypt.compare(req.body.password, dbhash, async function(err, result) {
                if (err) {
                    res.statusCode = 400;
                    res.error = err;
                    return next('Authentication failed! Please check the request');
                }
                if (result) {
                    let userData = await db.getUserAuthData(req.body.username);
                    if (userData.app_access) {
                        let token = jwt.sign(
                            { user_id: userData.id },
                            secret,
                            { expiresIn: '24h' }
                        );
                        res.cookie("token", JSON.stringify(token), {
                            secure: process.env.SECURE_COOKIE === "true",
                            httpOnly: true,
                            withCredentials: true,
                            maxAge: 8 * 60 * 60 * 1000,  // 8 hours
                            sameSite: "Strict",
                            domain: "localhost"
                        });
                        res.statusCode = 200;
                        res.json({
                            success: true,
                            response: 'Authentication successful!'
                        });
                    } else {
                        res.statusCode = 401;
                        return next('User is not authorised');
                    }
                } else {
                    res.statusCode = 401;
                    return next('Incorrect username or password');
                }
            });
        } else {
            res.statusCode = 400;
            return next('Authentication failed! Please check the request');
        }
    } catch (err) {
        return next(err);
    }
});
  
module.exports = router;
authHandler.js
var jwt = require('jsonwebtoken');
require('dotenv').config({path: './.env'});
const secret = process.env['SECRET'];

var checkToken = function(req, res, next) {
    let token = req.cookies.token;
    console.log(req.headers);
    if (token) {
        // Remove Bearer from string
        if (token.startsWith('Bearer ')) {
            token = token.slice(7, token.length);
        } 
        // Remove quotes around token
        else if (token.startsWith('"')) {
            token = token.substring(1, token.length-1);
        }
        jwt.verify(token, secret, (err, decoded) => {
            if (err) {
                res.statusCode = 401;
                return next('Authentication failed! Credentials are invalid');
            } else {
                req.decoded = decoded;
                next();
            }
        });
    } else {
        res.statusCode = 400;
        return next('Authentication failed! Please check the request');
    }
};

module.exports = checkToken;
客户端-http://localhost:3000/
const express = require('express');
const app = express();
const axios = require('axios');
axios.defaults.baseURL = "http://localhost:4000";
axios.defaults.withCredentials = true;

app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(express.static('resources'));

app.set('view engine', 'ejs');
app.set('views', 'views');

app.get('/transactions', async (req, res) => {
      axios.get('/budget/transactions', {
        withCredentials: true,
        headers: {
            'Content-Type': 'application/json',
        }
    })
        .then(response => {
            console.log(response.request);
            res.render('transactions', { name : 'transactions', title : 'transactions', ...response.data});
        })
        .catch(err => {
            console.log(err);
            res.render('transactions', { name : 'transactions', title : 'transactions', ...status });
        });
});

app.post('/login', async (req, res) => {
    axios.post('/login', 
    {
      username: req.body.email,
      password: req.body.password
    },
    {
      withCredentials: true,
      headers: {
          'content-type': 'application/json',
      }
    })
  .then(response => {
    console.log(response.headers);
    if (response.data.success === true) {
      res.redirect('/transactions');
    } else {
      res.redirect('/login');
    }
  });
});

app.listen(3000, () => {
    console.log("LISTENING ON PORT 3000");
});
jmo0nnb3

jmo0nnb31#

我已经解决了这个问题。我在CORS上找错了对象-我找到的所有其他答案都把它变成了一个红鲱鱼。
我的问题是similar to this one,即使我的“客户端”应用程序是通过浏览器访问的,因为我使用的是ejs,这本质上意味着它是两个服务器相互通信:
Browser -> EJS server -> REST endpoint server.
这意味着浏览器不知道set-cookie头发送回的cookie,并且只有当接收set-cookie头的客户端是浏览器时,cookie才会默认保存- axios在express服务器中运行时默认不会保存它们,因此我不得不添加手动处理保存和发送cookie的操作。请参阅下面的完整客户端代码。

客户端-http://localhost:3000/
const express = require('express');
const app = express();
const config = require('./config.json');
const axios = require('axios');
const cookieParser = require('cookie');
const params404 = { name : '404', title : "404 Not Found" };
axios.defaults.baseURL = "http://localhost:4000";
axios.defaults.withCredentials = true;
let cookie = undefined;

app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(express.static('resources'));

app.set('view engine', 'ejs');
app.set('views', 'views');

app.get('/transactions', async (req, res) => {
    let headers = {
        'Content-Type': 'application/json',
    };
    if (cookie) {
        headers.Cookie = "token="+cookie
    }
    axios.get('/budget/transactions', {
        withCredentials: true,
        headers: headers
    })
    .then(response => {
        res.render('transactions', { name : 'transactions', title : 'transactions', ...response.data});
    })
    .catch(err => {
        if (err?.response?.data != null) {
            console.error(err.response.data);
        } else {
            console.error(err);
        }
        let status = { success: err.status!=null?err.status:false }
        res.render('transactions', { name : 'transactions', title : 'transactions', ...status });
    });
});

app.post('/login', async (req, res) => {
    axios.post('/login', 
        {
        username: req.body.email,
        password: req.body.password
        },
        {
            withCredentials: true,
            headers: {
            'content-type': 'application/json',
        }
    })
    .then(response => {
    if (
        response.data.success === true && 
        response?.headers["set-cookie"] != null && 
        response.headers["set-cookie"][0] != null
    ) {
        cookie = cookieParser.parse(response?.headers["set-cookie"][0]).token;
        res.redirect('/transactions');
    } else {
        res.redirect('/login');
    }
    });
});

app.listen(3000, () => {
    console.log("LISTENING ON PORT 3000");
});

我保留了CORS设置,尽管它与实际答案无关,这是我在localhost上与上述内容一起使用的最终配置(为了完整起见):

authentication.js
res.cookie("token", JSON.stringify(token), {
                            secure: process.env.SECURE_COOKIE === "true",
                            httpOnly: true,
                            withCredentials: true,
                            maxAge: 8 * 60 * 60 * 1000,  // 8 hours
                            sameSite: "None",
                            path: "/",
                        });

相关问题