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

Supabase OAuth登录回调URL含#而非?,服务端无法获取参数

解决Express + Supabase Auth谷歌登录回调参数在哈希中的问题

问题原因

你遇到的哈希(#)参数问题,是因为Supabase的signInWithOAuth默认是为前端单页应用(SPA)设计的流程:谷歌授权后会先回调到Supabase的OAuth端点,Supabase再将token通过URL哈希(而非查询参数)重定向到你指定的redirectTo地址——哈希参数只会在浏览器前端解析,服务端无法直接获取。

解决方案

方案一:前端中转哈希参数到服务端

在回调路由返回一个极简的HTML页面,通过前端JS提取哈希参数,再重定向到服务端的处理路由(将参数转为查询参数):

// 回调路由:返回前端中转页面
app.get('/login/google/callback', (req, res) => {
  res.send(`
    <!DOCTYPE html>
    <html>
      <body>
        <script>
          // 提取哈希中的参数
          const hashParams = new URLSearchParams(window.location.hash.slice(1));
          const queryString = hashParams.toString();
          // 重定向到服务端处理路由
          window.location.href = '/login/google/callback/handle?' + queryString;
        </script>
      </body>
    </html>
  `);
});

// 处理参数的服务端路由
app.get('/login/google/callback/handle', async (req, res) => {
  const { access_token, refresh_token } = req.query;

  // 验证token并获取用户信息
  const { data: { user }, error } = await supabase.auth.getUser(access_token);
  if (error) {
    return res.status(401).send('无效的登录凭证');
  }

  // 这里添加你的会话设置逻辑(比如用express-session)
  req.session.user = user;
  req.session.accessToken = access_token;
  req.session.refreshToken = refresh_token;

  // 跳转至用户主页
  res.redirect('/dashboard');
});

方案二:直接使用谷歌授权码流(服务端原生处理)

绕过Supabase的前端导向流程,手动构建谷歌OAuth授权URL,让谷歌直接回调到你的Express路由,再通过授权码交换token并同步到Supabase:

步骤1:配置谷歌OAuth客户端

在谷歌云平台的OAuth 2.0客户端设置中,将授权重定向URI设为http://localhost:3000/login/google/callback。

步骤2:构建谷歌授权URL

const querystring = require('querystring');

app.get('/login/google', (req, res) => {
  // 生成随机state值防止CSRF攻击
  const state = Math.random().toString(36).substring(2, 15);
  req.session.oauthState = state;

  const authUrl = 'https://accounts.google.com/o/oauth2/v2/auth?' + querystring.stringify({
    client_id: '你的谷歌客户端ID',
    redirect_uri: 'http://localhost:3000/login/google/callback',
    response_type: 'code',
    scope: 'email profile',
    access_type: 'offline', // 必须添加才能获取refresh_token
    state: state
  });

  res.redirect(authUrl);
});

步骤3:处理回调并交换token

const axios = require('axios');

app.get('/login/google/callback', async (req, res) => {
  const { code, state } = req.query;

  // 验证state防止CSRF
  if (state !== req.session.oauthState) {
    return res.status(403).send('非法请求');
  }

  // 用授权码交换谷歌token
  try {
    const tokenResponse = await axios.post(
      'https://oauth2.googleapis.com/token',
      querystring.stringify({
        code: code,
        client_id: '你的谷歌客户端ID',
        client_secret: '你的谷歌客户端密钥',
        redirect_uri: 'http://localhost:3000/login/google/callback',
        grant_type: 'authorization_code'
      }),
      { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
    );

    const { id_token, access_token, refresh_token } = tokenResponse.data;

    // 用谷歌id_token登录Supabase
    const { data: { session }, error } = await supabase.auth.signInWithIdToken({
      provider: 'google',
      token: id_token
    });

    if (error) throw error;

    // 设置会话
    req.session.supabaseSession = session;
    res.redirect('/dashboard');
  } catch (err) {
    res.status(500).send('登录失败:' + err.message);
  }
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 20:38:32