Shopify Node.js应用从后台打开时报错argument str must be a string 求助
问题修复:Shopify应用后台打开时触发
argument str must be a string错误 错误原因
从Shopify后台点击已安装应用时,请求不会携带安装流程中设置的state Cookie,甚至req.headers.cookie可能为undefined。直接调用cookie.parse(req.headers.cookie)时,因为传入的是undefined而非字符串,就会抛出argument str must be a string错误。
另外,后台打开应用属于已授权后的正常访问,不需要走安装回调的state验证、token获取流程,当前代码把「安装回调请求」和「后台正常访问请求」混在一起处理了。
修复步骤
- 先校验
req.headers.cookie是否存在,避免直接解析undefined - 区分请求类型:只有当请求携带
code参数时,才是安装回调请求,需执行state验证和token获取;无code参数时是后台正常访问,直接使用已存储的access_token拉取数据
修改后的代码示例
app.get('/shopify', (req, res) => { const shop = req.query.shop; if (!shop) { return res.status(400).send('no shop')} const state = nonce(); const installShopUrl = buildInstallUrl(shop, state, buildRedirectUri()) res.cookie('state', state, { httpOnly: true, secure: process.env.NODE_ENV === 'production', // 生产环境启用HTTPS sameSite: 'lax' }) // 生产环境必须加密Cookie res.redirect(installShopUrl); }); app.get('/shopify/callback', async (req, res) => { const { shop, code, state } = req.query; // 1. 先判断Cookie是否存在,避免解析undefined报错 let stateCookie = null; if (req.headers.cookie) { stateCookie = cookie.parse(req.headers.cookie).state; } // 2. 区分安装回调和后台访问请求 if (code) { // 安装回调流程:执行state验证和token获取 if (!state || !stateCookie || state !== stateCookie) { return res.status(403).send('Cannot be verified') } const { hmac, ...params } = req.query const queryParams = querystring.stringify(params) const hash = generateEncryptedHash(queryParams) if (hash !== hmac) { return res.status(400).send('HMAC validation failed') } try { const data = { client_id: shopifyApiPublicKey, client_secret: shopifyApiSecretKey, code }; const tokenResponse = await fetchAccessToken(shop, data) const { access_token } = tokenResponse.data // 关键:把access_token和店铺域名关联存储到数据库 await saveShopToken(shop, access_token); const shopData = await fetchShopData(shop, access_token) res.send(shopData.data.shop) } catch(err) { console.log(err) res.status(500).send('something went wrong') } } else { // 后台正常访问流程:从数据库读取已存储的access_token if (!shop) { return res.status(400).send('no shop') } try { // 从数据库获取该店铺的access_token const access_token = await getShopToken(shop); if (!access_token) { // 无token时重定向到安装页面 return res.redirect(`/shopify?shop=${shop}`); } const shopData = await fetchShopData(shop, access_token) res.send(shopData.data.shop) } catch(err) { console.log(err) res.status(500).send('something went wrong') } } }); ///////////// Start the Server ///////////// app.listen(PORT, () => console.log(`listening on port ${PORT}`));
额外注意事项
- 生产环境必须加密
stateCookie,同时设置httpOnly、secure、sameSite属性,防范XSS和CSRF攻击 - 需自行实现
saveShopToken和getShopToken方法,将access_token与店铺域名关联存储,后台访问时才能快速获取 - 后台访问请求建议补充HMAC验证,拦截非法请求
内容的提问来源于stack exchange,提问作者stackcall01
相关产品推荐
相关产品推荐

