You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Shopify应用结合Firebase的请求权限验证问题

解决Shopify应用API的店铺数据访问权限验证问题

你的核心问题在于依赖客户端可篡改的shopOrigin cookie来获取店铺名称,而没有利用Shopify认证后服务器端保留的真实Session信息。verifyRequest中间件确实会验证请求是否来自Shopify合法调用,但它不会自动帮你校验cookie和真实店铺的一致性——你需要主动从服务器端Session中提取可靠的店铺标识。

核心解决方案:从服务器Session获取真实店铺信息

Shopify的createShopifyAuth在认证成功后,会将shop和accessToken等关键信息存储在服务器端Session中。这个Session是客户端无法直接修改的,用它来获取店铺名称就能彻底避免越权访问问题。


具体实现方式

方式1:将API路由挂载到Koa服务器(推荐,和认证逻辑统一)

如果你的API和认证路由同属一个Koa服务器实例,直接给API路由加上verifyRequest中间件,并从ctx.session中读取真实店铺:

// 在你的Koa server配置中添加API路由
server.get('/api/firebase', verifyRequest(), async (ctx) => {
  // 从服务器端Session获取不可篡改的shop值
  const shop = ctx.session.shop;
  
  const doc = await db.collection('shops').doc(shop).get();
  ctx.body = { success: true, data: doc.data() };
});

方式2:适配Next.js自带API路由

如果你想保留/pages/api的结构,需要在API端点中集成Session验证逻辑。首先确保你已经配置了Firestore作为Session存储(让服务器能跨路由读取Session):

// 在认证初始化代码中配置Session存储
import { FirestoreStore } from '@shopify/koa-shopify-auth/dist/session-stores/firestore';

server.use(
  createShopifyAuth({
    apiKey: SHOPIFY_API_KEY,
    secret: SHOPIFY_API_SECRET_KEY,
    scopes: ['read_products', 'write_products'],
    accessMode: "offline",
    // 将Session存储到Firestore,确保跨路由可访问
    store: new FirestoreStore({ database: db }),
    async afterAuth(ctx) {
      const { shop, accessToken } = ctx.session;
      ctx.cookies.set('shopOrigin', shop, {
        httpOnly: false,
        secure: true,
        sameSite: 'none'
      });
      await getSubscriptionUrl(ctx, accessToken, shop);
      await db.collection('shops').doc(shop).set({
        name: shop,
        accessToken
      }, { merge: true });
    },
  }),
);

然后在Next.js API路由中通过Session中间件获取真实店铺:

// /pages/api/firebase.js
import { verifyRequest } from '@shopify/koa-shopify-auth';
import session from 'koa-session';
import { FirestoreStore } from '@shopify/koa-shopify-auth/dist/session-stores/firestore';
import db from '../../path/to/your/firestore';
import { SHOPIFY_API_SECRET_KEY } from '../../path/to/your/config';

// 初始化Session中间件(和服务器端配置一致)
const sessionConfig = {
  store: new FirestoreStore({ database: db }),
  secret: SHOPIFY_API_SECRET_KEY,
  sameSite: 'none',
  secure: true,
};

export default async (req, res) => {
  try {
    // 将Next.js请求转换为Koa上下文并验证
    const ctx = { req, res };
    await session(sessionConfig)(ctx, async () => {
      await verifyRequest()(ctx, async () => {
        const shop = ctx.session.shop;
        const doc = await db.collection('shops').doc(shop).get();
        res.status(200).json({ success: true, data: doc.data() });
      });
    });
  } catch (err) {
    res.status(401).json({ success: false, message: 'Unauthorized request' });
  }
};

方式3:使用Next.js专属Shopify工具包(更简洁)

如果你用的是Next.js,建议直接用Shopify官方的@shopify/next-shopify-auth包,它专门适配Next.js的路由和Session管理:

// /pages/api/firebase.js
import { getSession } from '@shopify/next-shopify-auth';
import db from '../../path/to/your/firestore';

export default async (req, res) => {
  try {
    // 获取经过验证的服务器端Session
    const session = await getSession(req, res);
    if (!session) {
      return res.status(401).json({ success: false, message: 'Unauthorized' });
    }
    
    const shop = session.shop;
    const doc = await db.collection('shops').doc(shop).get();
    res.status(200).json({ success: true, data: doc.data() });
  } catch (err) {
    res.status(500).json({ success: false, message: err.message });
  }
};

额外安全建议

  • 务必加密Access Token:上线前一定要对accessToken进行加密后再存入Firestore,避免敏感数据泄露。
  • 限制Session存储权限:确保Firestore的Session集合只有你的服务器能读写,防止外部篡改。
  • 最小数据返回原则:API只返回当前店铺需要的必要数据,不要泄露其他店铺的信息。

内容的提问来源于stack exchange,提问作者K20GH

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 07:32:57