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

如何在React项目中使用带权限范围的新版Google API?

解决React中Google Fit API授权与数据获取问题

核心问题排查

你遇到的加scope登录失败、无法获取Fit权限的问题,大多和Google Cloud控制台配置、授权库的正确使用方式有关,以下是分步解决方案:


第一步:先确认Google Cloud控制台的关键配置

这些是所有授权流程的基础,没配置对再怎么写代码都没用:

  • 启用Google Fit API:在Cloud控制台的API库中搜索并启用「Google Fit API」
  • 配置OAuth同意屏幕:
    • 切换到「测试」或「生产」模式,添加测试用户(你的Google邮箱)
    • 在「已授权的范围」中添加所需的Fit权限,比如:
      • https://www.googleapis.com/auth/fitness.activity.read(读取活动数据)
      • profile、email(获取基础用户信息)
  • 验证重定向URI:在OAuth客户端ID的设置中,确保http://localhost:3000已添加到「已授权的重定向URI」列表

方案一:用@react-oauth/google正确获取授权(推荐)

你之前的代码加scope报错,大概率是控制台没配置对应scope,或者ux_mode的处理问题。以下是修正后的完整代码:

方式1:Popup模式(无需页面跳转)

import { useGoogleLogin } from '@react-oauth/google';
import { useState } from 'react';

const GoogleFitLogin = () => {
  const [fitData, setFitData] = useState(null);

  const signIn = useGoogleLogin({
    clientId: process.env.REACT_APP_GOOGLE_CLIENT_ID,
    ux_mode: 'popup',
    scope: 'profile email https://www.googleapis.com/auth/fitness.activity.read',
    access_type: 'offline', // 可选,需要刷新token时添加
    onSuccess: async (response) => {
      console.log('获取到的AccessToken:', response.access_token);
      // 调用Google Fit API获取活动数据
      try {
        const res = await fetch('https://www.googleapis.com/fitness/v1/users/me/dataset:aggregate', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${response.access_token}`,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            aggregateBy: [{ dataTypeName: 'com.google.activity.segment' }],
            bucketByTime: { durationMillis: 86400000 }, // 按天聚合
            startTimeMillis: Date.now() - 7 * 86400000, // 最近7天
            endTimeMillis: Date.now(),
          }),
        });
        const data = await res.json();
        setFitData(data);
      } catch (err) {
        console.error('调用Fit API失败:', err);
      }
    },
    onError: (err) => console.error('登录授权失败:', err),
  });

  return (
    <div>
      <button onClick={signIn}>登录并授权Google Fit</button>
      {fitData && <pre>{JSON.stringify(fitData, null, 2)}</pre>}
    </div>
  );
};

export default GoogleFitLogin;

方式2:Redirect模式(需处理回调)

如果必须用redirect模式,需要在重定向后的页面处理授权码:

// App.js 主页面
import { useGoogleOAuth } from '@react-oauth/google';
import { useEffect, useState } from 'react';

function App() {
  const { code } = useGoogleOAuth();
  const [tokens, setTokens] = useState(null);

  useEffect(() => {
    if (code) {
      // 推荐通过后端交换token,避免暴露Client Secret
      fetch('http://localhost:5000/api/google/token', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ code }),
      })
      .then(res => res.json())
      .then(data => setTokens(data.tokens))
      .catch(err => console.error('交换token失败:', err));
    }
  }, [code]);

  return (
    <div>
      {!tokens ? (
        <a href={`https://accounts.google.com/o/oauth2/v2/auth?client_id=${process.env.REACT_APP_GOOGLE_CLIENT_ID}&redirect_uri=http://localhost:3000&response_type=code&scope=profile%20email%20https://www.googleapis.com/auth/fitness.activity.read`}>
          登录并授权Google Fit
        </a>
      ) : (
        <div>
          <p>已获取授权,Token: {tokens.access_token.slice(0, 20)}...</p>
          {/* 这里可以调用Fit API */}
        </div>
      )}
    </div>
  );
}

export default App;

方案二:用Google官方OAuth2库直接实现

你之前用的google.accounts.id是身份认证(获取ID Token),要获取授权scope需用google.accounts.oauth2:

import { useEffect, useState } from 'react';

const GoogleFitAuth = () => {
  const [fitData, setFitData] = useState(null);

  useEffect(() => {
    // 加载Google OAuth2脚本
    const script = document.createElement('script');
    script.src = 'https://accounts.google.com/gsi/client';
    script.onload = () => {
      /* global google */
      const tokenClient = google.accounts.oauth2.initTokenClient({
        client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID,
        scope: 'profile email https://www.googleapis.com/auth/fitness.activity.read',
        callback: async (response) => {
          if (response.error) {
            console.error('授权失败:', response.error);
            return;
          }
          // 调用Fit API
          try {
            const res = await fetch('https://www.googleapis.com/fitness/v1/users/me/dataset:aggregate', {
              method: 'POST',
              headers: {
                'Authorization': `Bearer ${response.access_token}`,
                'Content-Type': 'application/json',
              },
              body: JSON.stringify({
                aggregateBy: [{ dataTypeName: 'com.google.activity.segment' }],
                bucketByTime: { durationMillis: 86400000 },
                startTimeMillis: Date.now() - 7 * 86400000,
                endTimeMillis: Date.now(),
              }),
            });
            const data = await res.json();
            setFitData(data);
          } catch (err) {
            console.error('调用Fit API失败:', err);
          }
        },
      });

      // 绑定按钮点击事件
      document.querySelector('#fit-auth-btn').addEventListener('click', () => {
        tokenClient.requestAccessToken({ prompt: 'consent' }); // 强制弹出授权窗口
      });
    };
    document.body.appendChild(script);
    return () => document.body.removeChild(script);
  }, []);

  return (
    <div>
      <button id="fit-auth-btn">授权Google Fit获取数据</button>
      {fitData && <pre>{JSON.stringify(fitData, null, 2)}</pre>}
    </div>
  );
};

export default GoogleFitAuth;

方案三:后端处理Token交换(生产环境必备)

生产环境不能在前端暴露Client Secret,用Node.js+Express+googleapis实现后端:

// server.js
const express = require('express');
const { google } = require('googleapis');
const cors = require('cors');
require('dotenv').config();

const app = express();
app.use(cors());
app.use(express.json());

const oauth2Client = new google.auth.OAuth2(
  process.env.GOOGLE_CLIENT_ID,
  process.env.GOOGLE_CLIENT_SECRET,
  'http://localhost:3000'
);

// 交换授权码获取Token
app.post('/api/google/token', async (req, res) => {
  const { code } = req.body;
  try {
    const { tokens } = await oauth2Client.getToken(code);
    oauth2Client.setCredentials(tokens);
    
    // 调用Google Fit API
    const fitness = google.fitness({ version: 'v1', auth: oauth2Client });
    const fitResponse = await fitness.users.dataset.aggregate({
      userId: 'me',
      requestBody: {
        aggregateBy: [{ dataTypeName: 'com.google.activity.segment' }],
        bucketByTime: { durationMillis: 86400000 },
        startTimeMillis: Date.now() - 7 * 86400000,
        endTimeMillis: Date.now(),
      },
    });

    res.json({ tokens, fitData: fitResponse.data });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(5000, () => console.log('后端服务运行在 http://localhost:5000'));

关键注意事项

  • 测试阶段必须在OAuth同意屏幕添加自己的邮箱作为测试用户,否则会提示「应用未验证」
  • Google Fit API的部分接口需要特定的请求格式(比如dataset:aggregate必须用POST),要严格参考官方文档的参数要求
  • 若遇到「invalid_scope」错误,检查控制台的OAuth同意屏幕是否已添加对应scope,且scope拼写完全正确

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 11:44:58