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

Chrome扩展中Salesforce认证遇400错误,Express环境正常

Chrome扩展Salesforce OAuth认证400错误排查(Express环境正常)

在Chrome扩展中实现Salesforce OAuth认证时遇到400错误,但相同逻辑在Express服务器环境下可正常运行。以下是相关代码及报错信息:


相关代码文件

background.js

console.log("Extension loaded");

let user_signed_in = false;
const clientId =
  "xyz";
const callbackUrl = "https://dfghlbaebomdoihlfmegbpcmfkfgfno.chromiumapp.org";

const getSalesforceLoginLink = async () => {
  const res = await fetch(
    `https://login.salesforce.com/services/oauth2/authorize?response_type=code&client_id=${clientId}&redirect_uri=${callbackUrl}`
  );

  if (res.status === 200) {
    // append login link
    return res.url;
  } else {
    console.error("Not able to fetch login link");
  }
};

const authenticateSalesforce = (salesforceLink) => {
  chrome.identity.launchWebAuthFlow(
    {
      url: salesforceLink,
      interactive: true,
    },
    function (redirect_url) {
      const codeParam = searchParams.get('code');
      let authCode = codeParam;
      const loginUrl = `https://login.salesforce.com/services/oauth2/token?grant_type=authorization_code&redirect_uri=${
    process.env.SALESFORCE_CALLBACK_URL
  }&client_id=${process.env.SALESFORCE_CLIENT_ID}&client_secret=${
    process.env.SALESFORCE_CLIENT_SECRET
  }&code=${encodeURIComponent(authCode)}`;
  const response = await fetch(loginUrl)
    .then(function (response) {
      return response?.json();
    })
    .then(function (data) {
      return data;
    });
      if (chrome.runtime.lastError) {
        sendResponse({ message: "fail" });
      } else {
        console.log({ redirect_url });
      }
    }
  );
};

chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
  if (request.message === "login") {
    if (user_signed_in) {
      console.log("User is already signed in.");
    } else {
      const salesforceLink = await getSalesforceLoginLink();
      authenticateSalesforce(salesforceLink);
    }

    return true;
  } else if (request.message === "logout") {
    user_signed_in = false;
    chrome.browserAction.setPopup({ popup: "./popup.html" }, () => {
      sendResponse({ message: "success" });
    });

    return true;
  }
});

manifest.json

{
  "manifest_version": 3,
  "name": "SF POC",
  "description": "A quick way to browse top posts from DEV Community.",
  "version": "0.0.1",
  "action": {
    "default_title": "SF POC"
  },
  "key": "xyz",
  "background": {
    "service_worker": "background.js"
  },
  "permissions": ["identity", "activeTab", "tabs", "windows"],
  "host_permissions": ["https://login.salesforce.com/*"]
}

报错截图

报错截图1
报错截图2
报错截图3


问题分析与修复方案

核心问题点

  1. 错误的授权URL获取方式:getSalesforceLoginLink用fetch请求Salesforce授权接口是完全错误的——该接口是用于跳转登录页面的,无需通过fetch获取,直接构造URL即可。
  2. 未定义的变量与语法错误:
    • searchParams未初始化,无法从redirect_url中提取授权码
    • 普通回调函数内使用await但未标记async,会触发语法错误
  3. 环境变量不可用:Chrome扩展的Service Worker不支持process.env,导致请求Token时的redirect_uri、client_id等参数为undefined,直接引发400错误
  4. Manifest V3 API兼容问题:chrome.browserAction在Manifest V3中已被废弃,需替换为chrome.action

修正后的background.js代码

console.log("Extension loaded");

let user_signed_in = false;
const clientId = "xyz"; // 替换为你的实际Client ID
const clientSecret = "你的Client Secret"; // 注意:扩展内存储Client Secret有安全风险,建议用服务器代理
const callbackUrl = "https://dfghlbaebomdoihlfmegbpcmfkfgfno.chromiumapp.org";

// 直接构造授权URL,无需fetch请求
const getSalesforceLoginLink = () => {
  return `https://login.salesforce.com/services/oauth2/authorize?response_type=code&client_id=${clientId}&redirect_uri=${encodeURIComponent(callbackUrl)}`;
};

const authenticateSalesforce = async (salesforceLink, sendResponse) => {
  try {
    // 用Promise封装launchWebAuthFlow以支持async/await
    const redirect_url = await new Promise((resolve, reject) => {
      chrome.identity.launchWebAuthFlow(
        { url: salesforceLink, interactive: true },
        (redirectUrl, error) => error ? reject(error) : resolve(redirectUrl)
      );
    });

    // 从回调URL中提取授权码
    const urlParams = new URLSearchParams(new URL(redirect_url).search);
    const authCode = urlParams.get('code');
    if (!authCode) throw new Error("未获取到授权码");

    // 构造Token请求(推荐用URLSearchParams避免拼接错误)
    const tokenUrl = new URL("https://login.salesforce.com/services/oauth2/token");
    tokenUrl.searchParams.append('grant_type', 'authorization_code');
    tokenUrl.searchParams.append('redirect_uri', callbackUrl);
    tokenUrl.searchParams.append('client_id', clientId);
    tokenUrl.searchParams.append('client_secret', clientSecret);
    tokenUrl.searchParams.append('code', authCode);

    const response = await fetch(tokenUrl.toString(), {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(`获取Token失败: ${errorData.error_description || response.statusText}`);
    }

    const data = await response.json();
    console.log("认证成功", data);
    user_signed_in = true;
    sendResponse({ message: "success", data });
  } catch (error) {
    console.error("认证失败", error);
    sendResponse({ message: "fail", error: error.message });
  }
};

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.message === "login") {
    if (user_signed_in) {
      console.log("用户已登录");
      sendResponse({ message: "already signed in" });
    } else {
      const salesforceLink = getSalesforceLoginLink();
      authenticateSalesforce(salesforceLink, sendResponse);
    }
    return true; // 保持消息通道开放以支持异步响应
  } else if (request.message === "logout") {
    user_signed_in = false;
    // Manifest V3 使用chrome.action代替browserAction
    chrome.action.setPopup({ popup: "./popup.html" }, () => {
      sendResponse({ message: "success" });
    });
    return true;
  }
});

额外注意事项

  • Client Secret安全:扩展内直接存储Client Secret存在被反编译泄露的风险,建议通过自有服务器代理Token请求,将Client Secret存放在服务器端。
  • 回调URL配置:确保Salesforce Connected App中配置的回调URL与扩展内的callbackUrl完全一致(包括协议、域名)。
  • 权限验证:确认manifest中identity权限及host_permissions已正确配置Salesforce登录域名。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 05:55:02