成功购买后如何阻止 Stripe 覆盖客户的会话 cookie
How to stop Stripe from over-writing client's session cookie after a successful purchase
环境: Express、express-session、Stripe
在下面的简化示例中,当用户请求主页时 express-session
会为用户分配一个会话 cookie。刷新页面会保留与访问成功或失败路由相同的会话 ID。单击升级按钮会将客户端带到 Stripe 购物车屏幕,该屏幕也保持相同的会话 ID。然而,一旦用户在 Stripe 购物车上,如果用户成功购买,他将被转发到成功路线,会话 ID 将被 Stripe 覆盖。在完整版中,这是一个问题,因为用户会登录,这会导致用户在成功购买后自动注销。我不确定为什么会发生这种情况或如何阻止它。
app.js
const bodyParser = require('body-parser');
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
app.set('view engine', 'ejs');
app.use(express.static('views'));
app.use(
session({
maxAge: 24 * 60 * 60 * 1000,
name: 'randomName',
resave: false,
saveUninitialized: true,
secret: 'randomSecret',
cookie: {
sameSite: true,
secure: false
}
})
);
app.get('/', function(req, res) {
req.session.userValues = true;
console.log(req.session);
res.render('index', { stripePublicKey: process.env.STRIPE_PUBLIC_KEY });
});
app.get('/success', function(req, res) {
console.log(req.session);
res.render('success');
});
app.get('/fail', function(req, res) {
console.log(req.session);
res.render('fail');
});
app.post('/create-checkout-session', bodyParser.raw({ type: 'application/json' }), async function(req, res) {
console.log(req.session);
const session = await stripe.checkout.sessions.create({
submit_type: 'auto',
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: 'usd',
product_data: {
name: 'name of product',
description: 'description of product'
},
unit_amount: 100
},
quantity: 1,
}
],
locale: 'en',
mode: 'payment',
success_url: 'http://localhost:8080/success',
cancel_url: 'http://localhost:8080/fail'
});
res.json({ id: session.id });
});
app.listen(8080, function() {
console.log('listening on port 8080');
});
index.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upgrade</title>
<script>var stripePublicKey = '<%- stripePublicKey %>';</script>
<script defer src="https://js.stripe.com/v3/"></script>
<script defer src="checkout.js"></script>
</head>
<body>
<button id="checkout-button">upgrade premium</button>
</body>
</html>
checkout.js
var stripe = Stripe(stripePublicKey);
var checkoutButton = document.getElementById('checkout-button');
checkoutButton.addEventListener('click', checkoutSequence);
function checkoutSequence() {
fetch('/create-checkout-session', {
method: 'POST',
})
.then(function(response) {
return response.json();
})
.then(function(session) {
console.log(session);
return stripe.redirectToCheckout({ sessionId: session.id });
})
.then(function(result) {
if (result.error) {
alert(result.error.message);
}
})
.catch(function(error) {
console.error('Error:', error);
});
}
经过6个小时的测试,我发现了问题。 cookie.sameSite
必须设置为 lax
而不是 true
。显然,当 Stripe 命中成功路由时 express-session
确定这是来自外部站点并重置 cookie。
app.use(
session({
maxAge: 24 * 60 * 60 * 1000,
name: 'randomName',
resave: false,
saveUninitialized: true,
secret: 'randomSecret',
cookie: {
sameSite: 'lax',
secure: false
}
})
);
我同意关闭 SameSite strict 可以轻松解决问题。但我很想知道是否有办法保留安全设置。
我发现当直接从 Stripe 加载 /success 页面时,SameSite cookies 不会被发送,如果你从你网站上的“非会话补间”页面反弹它,我发现你可以解决这个问题,即一个页面没有会话处理,因此不会检测到丢失的会话 cookie,因此不会替换它。但是我发现你必须使用客户端重定向才能工作。
这似乎是有道理的,因为元刷新正在执行从您的站点到您的站点的客户端导航,所以它就像用户单击 link。然而,在服务器重定向中,浏览器似乎“知道”事件链并没有从 Stripe 站点断开到您的站点。如果您在服务器端重定向中设置断点,您可以看到浏览器仍然位于 Stripe 中,所以我猜它在响应 301 重定向响应时仍然认为它在那个上下文中,并且不发送 cookie。有趣的边界案例。
流量:
- 您的站点页面:将 return url 配置为
/success-redirect
- 条纹支付...
/success-redirect
(来自没有会话 cookie 的 Stripe)
- 浏览器中的<元刷新>
/success
(有 cookie!)
App.js / 路由器
const url = require('url');
// Add this route before adding your session to the app/router
// so this route will not set a new session cookie, and won't overwrite the session.
// The previous session cookie stays in the browser...
app.get('/success-redirect', (req, res, next)=>{
// Server side redirect doesn't work, this still wont send the cookie,
// even though it's the browser responding to the 302/301
// res.redirect(url.format({
// pathname:'/success',
// query:req.query,
// }));
// If you load an actual page on the site domain, then it does a meta-redirect,
// the original session cookie is sent and this success page is logged in normally
res.render('success-redirect.hbs', {
url: url.format({pathname:'/success',query:req.query,}),
});
});
// then add your session middleware as usual
app.use(session(...));
成功-redirect.hbs
<meta http-equiv="refresh" content="1;URL='{{url}}'" />
这种事情可能解释了为什么一些 complex/aggregate 站点有这么多临时重定向页面。
环境: Express、express-session、Stripe
在下面的简化示例中,当用户请求主页时 express-session
会为用户分配一个会话 cookie。刷新页面会保留与访问成功或失败路由相同的会话 ID。单击升级按钮会将客户端带到 Stripe 购物车屏幕,该屏幕也保持相同的会话 ID。然而,一旦用户在 Stripe 购物车上,如果用户成功购买,他将被转发到成功路线,会话 ID 将被 Stripe 覆盖。在完整版中,这是一个问题,因为用户会登录,这会导致用户在成功购买后自动注销。我不确定为什么会发生这种情况或如何阻止它。
app.js
const bodyParser = require('body-parser');
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
app.set('view engine', 'ejs');
app.use(express.static('views'));
app.use(
session({
maxAge: 24 * 60 * 60 * 1000,
name: 'randomName',
resave: false,
saveUninitialized: true,
secret: 'randomSecret',
cookie: {
sameSite: true,
secure: false
}
})
);
app.get('/', function(req, res) {
req.session.userValues = true;
console.log(req.session);
res.render('index', { stripePublicKey: process.env.STRIPE_PUBLIC_KEY });
});
app.get('/success', function(req, res) {
console.log(req.session);
res.render('success');
});
app.get('/fail', function(req, res) {
console.log(req.session);
res.render('fail');
});
app.post('/create-checkout-session', bodyParser.raw({ type: 'application/json' }), async function(req, res) {
console.log(req.session);
const session = await stripe.checkout.sessions.create({
submit_type: 'auto',
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: 'usd',
product_data: {
name: 'name of product',
description: 'description of product'
},
unit_amount: 100
},
quantity: 1,
}
],
locale: 'en',
mode: 'payment',
success_url: 'http://localhost:8080/success',
cancel_url: 'http://localhost:8080/fail'
});
res.json({ id: session.id });
});
app.listen(8080, function() {
console.log('listening on port 8080');
});
index.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upgrade</title>
<script>var stripePublicKey = '<%- stripePublicKey %>';</script>
<script defer src="https://js.stripe.com/v3/"></script>
<script defer src="checkout.js"></script>
</head>
<body>
<button id="checkout-button">upgrade premium</button>
</body>
</html>
checkout.js
var stripe = Stripe(stripePublicKey);
var checkoutButton = document.getElementById('checkout-button');
checkoutButton.addEventListener('click', checkoutSequence);
function checkoutSequence() {
fetch('/create-checkout-session', {
method: 'POST',
})
.then(function(response) {
return response.json();
})
.then(function(session) {
console.log(session);
return stripe.redirectToCheckout({ sessionId: session.id });
})
.then(function(result) {
if (result.error) {
alert(result.error.message);
}
})
.catch(function(error) {
console.error('Error:', error);
});
}
经过6个小时的测试,我发现了问题。 cookie.sameSite
必须设置为 lax
而不是 true
。显然,当 Stripe 命中成功路由时 express-session
确定这是来自外部站点并重置 cookie。
app.use(
session({
maxAge: 24 * 60 * 60 * 1000,
name: 'randomName',
resave: false,
saveUninitialized: true,
secret: 'randomSecret',
cookie: {
sameSite: 'lax',
secure: false
}
})
);
我同意关闭 SameSite strict 可以轻松解决问题。但我很想知道是否有办法保留安全设置。
我发现当直接从 Stripe 加载 /success 页面时,SameSite cookies 不会被发送,如果你从你网站上的“非会话补间”页面反弹它,我发现你可以解决这个问题,即一个页面没有会话处理,因此不会检测到丢失的会话 cookie,因此不会替换它。但是我发现你必须使用客户端重定向才能工作。
这似乎是有道理的,因为元刷新正在执行从您的站点到您的站点的客户端导航,所以它就像用户单击 link。然而,在服务器重定向中,浏览器似乎“知道”事件链并没有从 Stripe 站点断开到您的站点。如果您在服务器端重定向中设置断点,您可以看到浏览器仍然位于 Stripe 中,所以我猜它在响应 301 重定向响应时仍然认为它在那个上下文中,并且不发送 cookie。有趣的边界案例。
流量:
- 您的站点页面:将 return url 配置为
/success-redirect
- 条纹支付...
/success-redirect
(来自没有会话 cookie 的 Stripe)- 浏览器中的<元刷新>
/success
(有 cookie!)
App.js / 路由器
const url = require('url');
// Add this route before adding your session to the app/router
// so this route will not set a new session cookie, and won't overwrite the session.
// The previous session cookie stays in the browser...
app.get('/success-redirect', (req, res, next)=>{
// Server side redirect doesn't work, this still wont send the cookie,
// even though it's the browser responding to the 302/301
// res.redirect(url.format({
// pathname:'/success',
// query:req.query,
// }));
// If you load an actual page on the site domain, then it does a meta-redirect,
// the original session cookie is sent and this success page is logged in normally
res.render('success-redirect.hbs', {
url: url.format({pathname:'/success',query:req.query,}),
});
});
// then add your session middleware as usual
app.use(session(...));
成功-redirect.hbs
<meta http-equiv="refresh" content="1;URL='{{url}}'" />
这种事情可能解释了为什么一些 complex/aggregate 站点有这么多临时重定向页面。