Shopify OAuth获取永久访问令牌提示授权码不存在或已使用问题
报错信息:
"error_description": "The authorization code was not found or was already used"
该错误属于OAuth2.0流程的常见报错,授权码是一次性、短有效期凭证,可按以下顺序排查:
确认授权码时效性
Shopify发放的授权码有效期仅为10分钟,拿到授权码后必须立即调用换取令牌接口。可在NodeJS应用的授权回调逻辑中增加日志,输出授权码获取时间和接口调用时间的差值,确认是否超出有效期。排查授权码重复使用问题
授权码仅可使用一次,哪怕第一次调用接口失败,第二次使用同一授权码也会触发该报错。优先检查两个场景:
- 应用的回调接口是否被重复触发:比如用户安装应用时刷新页面、浏览器自动重试请求,导致同一个授权码被多次提交
- 你的接口调用逻辑是否添加了失败自动重试规则:首次调用已消耗授权码,重试时使用同一编码直接报错
可在回调入口添加请求日志,统计同一个授权码的调用次数即可定位。
核对请求参数一致性
检查请求Body中的client_id、client_secret是否和Shopify后台创建应用时获取的参数完全一致,注意不要混淆测试环境和生产环境的应用凭证,参数不匹配也会触发相同报错。检查授权码来源合法性
确认你使用的授权码是当前店铺本次授权流程回调返回的最新编码,不是本地测试写死的旧编码、其他店铺的授权编码或者缓存的历史编码。校验重定向地址配置
确认Shopify应用后台填写的重定向URL,和你发起授权请求时携带的redirect_uri参数完全一致,协议(http/https)、域名、路径、端口全部匹配,否则即使拿到授权码,调用换取令牌接口也会报错。
临时修复方案
如果排查后无法立即定位问题,直接引导用户重新发起应用安装授权,获取新的授权码后重新调用接口即可解决单次报错。
代码优化建议
可参考以下逻辑改造你的NodeJS应用回调代码,避免重复提交问题:
const express = require('express'); const axios = require('axios'); const app = express(); // 授权回调路由 app.get('/shopify/auth/callback', async (req, res) => { const { code, shop, state } = req.query; // 先校验state参数防CSRF,此处省略校验逻辑 // 校验授权码是否已被使用,可使用Redis或本地缓存存储已消耗的编码 const isCodeUsed = await cache.get(`shopify_code:${code}`); if (isCodeUsed) { return res.redirect(`/shopify/auth/start?shop=${shop}`); } // 标记授权码已使用,过期时间设置为10分钟和授权码有效期一致 await cache.setEx(`shopify_code:${code}`, 60 * 10, 'used'); try { const response = await axios.post(`https://${shop}/admin/oauth/access_token`, { client_id: process.env.SHOPIFY_CLIENT_ID, client_secret: process.env.SHOPIFY_CLIENT_SECRET, code: code }); // 存储永久令牌到你的业务数据库 await db.shopToken.upsert({ shop: shop, access_token: response.data.access_token, scope: response.data.scope }); // 跳转到应用首页 return res.redirect(`/app?shop=${shop}`); } catch (error) { console.error('换取令牌失败', error.response?.data || error.message); // 报错后直接引导用户重新授权,不要重试 return res.redirect(`/shopify/auth/start?shop=${shop}`); } });
内容的提问来源于stack exchange,提问作者Cycl0n3

