问题
我有两个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/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");
});
1条答案
按热度按时间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/
我保留了CORS设置,尽管它与实际答案无关,这是我在localhost上与上述内容一起使用的最终配置(为了完整起见):
authentication.js