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

问题描述 投票:0回答:1

问题

我有两个快速节点应用程序——一个纯粹充当 REST 服务器,另一个是前端使用 ejs 并使用 axios 向后端发送请求。我正试图在我的开发环境中运行它,这两个应用程序都在本地主机上运行。

我正在尝试在

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 错误消息。

其他 stackoverflow 问题 似乎证实了这一理论,但我仍然无法成功将 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"]
}));
  • 我知道我需要发送带有凭据的请求,我已经在每个请求中和默认情况下都尝试过以防万一这是一个 axios 问题.
  • 我知道浏览器对本地主机有特殊处理,在某些情况下它被视为安全环境,但我不清楚设置安全 cookie 是否属于这种情况 - 我已将
    secure: true/false
    sameSite: "Strict","None"
    的值切换为他们所有的组合,但没有变化。
  • 我尝试将所有内容(服务器和客户端)切换为使用
    127.0.0.1
    而不是
    localhost
    ,但没有任何变化。
  • 我尝试使用
    mkcert
    安装本地 SSL 证书以确保我可以正确设置
    secure: true
    sameSite: "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");
});
express cookies axios cors ejs
1个回答
0
投票

我已经解决了这个问题。我用 CORS 找错了树——我找到的所有其他答案都把它变成了一个红鲱鱼。

我的问题是类似于这个即使我的“客户端”应用程序是通过浏览器访问的,因为我使用的是ejs,它本质上意味着它是两个服务器相互通信:

Browser -> EJS server -> REST endpoint server.

这意味着浏览器不知道 set-cookie 标头发回的 cookie,并且只有当接收 set-cookie 标头的客户端是浏览器时,cookie 才会默认保存 - axios 不会保存它们在快速服务器内运行时的默认设置,因此我不得不添加手动处理保存和发送 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 设置,尽管它与实际答案无关这里是我确定的最终配置,它适用于本地主机上的上述配置(为了完整性):

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: "/",
                        });
© www.soinside.com 2019 - 2024. All rights reserved.