React Native 使用 Appwrite Google OAuth 登录失败如何解决?
应将登录入口改为 account.createOAuth2Token(),从回调中读取 userId 和 secret,再调用 account.createSession()。现有代码把 createOAuth2Session() 和用 Token 换取 Session 的处理方式混在了一起。日志中的实际回调仍然使用 exp://,还需要检查自定义 scheme 是否已在运行的应用中生效。
为什么浏览器显示成功,却没有登录成功?
代码中调用的是:
const response = await account.createOAuth2Session(
OAuthProvider.Google,
redirectUri
);
日志也确认生成的地址指向 /account/sessions/oauth2/google。但后续代码要求回调带上 userId 和 secret,再执行 createSession(userId, secret),这是 OAuth2 Token 流程的处理方式。Appwrite 的 Expo 登录说明使用 createOAuth2Token() 发起授权,浏览器授权完成后读取回调凭据,再调用 createSession()。Appwrite OAuth2 登录文档
原始回调是:
exp://192.168.0.91:8081/--/#
这个地址没有查询参数,# 后面也没有内容,因此无法从中提取 secret 和 userId。browserResult.type === 'success' 只能说明浏览器认证会话收到了回调,不能证明 Appwrite SDK 已建立可用的登录会话。
参数不存在时,URLSearchParams.get() 返回 null,后面的 ?.toString() 又让结果变成了 undefined。这解释了日志中的值,但修改可选链无法解决凭据没有返回的问题。
修改登录代码
下面沿用题中 SDK 的位置参数写法,并复用已有的 account 和 config。示例适用于已注册自定义 scheme 的原生开发构建。回调使用 localhost 作为主机名,不是在设备上启动本地 HTTP 服务。
import { OAuthProvider } from 'react-native-appwrite';
import { openAuthSessionAsync } from 'expo-web-browser';
export async function login(): Promise<boolean> {
try {
if (!config.projectId) {
throw new Error('Missing Appwrite project ID');
}
const callbackScheme = `appwrite-callback-${config.projectId}`;
const redirectUri = `${callbackScheme}://localhost`;
const loginUrl = await account.createOAuth2Token(
OAuthProvider.Google,
redirectUri,
redirectUri
);
const browserResult = await openAuthSessionAsync(
loginUrl.toString(),
redirectUri
);
if (browserResult.type !== 'success') {
return false;
}
const callbackUrl = new URL(browserResult.url);
const oauthError = callbackUrl.searchParams.get('error');
if (oauthError) {
throw new Error(`OAuth callback error: ${oauthError}`);
}
const userId = callbackUrl.searchParams.get('userId');
const secret = callbackUrl.searchParams.get('secret');
if (!userId || !secret) {
throw new Error('OAuth callback is missing userId or secret');
}
await account.createSession(userId, secret);
await account.get();
return true;
} catch (error) {
console.error('Appwrite Google login failed', error);
return false;
}
}
createSession() 用于建立登录会话,原代码中它上方的 // create account 注释容易引起误解。这里不需要额外调用注册接口。
题目没有提供 react-native-appwrite 的版本。如果已安装 SDK 的类型声明要求对象参数,相应调用应写为:
const loginUrl = await account.createOAuth2Token({
provider: OAuthProvider.Google,
success: redirectUri,
failure: redirectUri,
});
await account.createSession({ userId, secret });
请以项目实际安装版本的类型声明为准。当前 React Native SDK 源码同时列出了对象参数接口和已标记为 deprecated 的位置参数重载,不能据此反推题目使用的版本。React Native SDK Account 源码
让 Expo 的回调 scheme 真正生效
配置键名是 scheme,不是 schema。题目给出的代码片段中键名已经正确:
{
"expo": {
"scheme": "appwrite-callback-69b2c78211160d0a51b5"
}
}
将这项配置合并到现有 app.json,并确认其中的项目 ID 与 EXPO_PUBLIC_APPWRITE_PROJECT_ID 一致。
修改原生 scheme 后,需要重新生成、安装并运行 development build。只刷新 JavaScript 不会把新的协议注册到设备。Expo 文档要求添加自定义 scheme 后创建新的开发构建。Expo 深度链接配置
日志中的 exp://.../--/ 与 Expo Go 的开发链接格式一致,说明本次回调没有使用所配置的 appwrite-callback-...。按 Expo 当前认证指南,涉及自定义 scheme 的 OAuth 测试应使用 development build。Expo 认证指南
上面的示例显式构造了回调 URI。如果继续使用 Linking.createURL(),也应检查它在实际运行环境中生成的地址,确保传给 Appwrite 和 openAuthSessionAsync() 的回调配置一致。
如何处理 guests 缺少 account scopes 的错误?
这条日志表示发出相应请求时,Appwrite 将调用方识别为未登录访客:
User (role: guests) missing scopes (["account"])
一种常见情况是应用启动时调用了需要登录身份的接口,例如 account.get(),但此时还没有有效 Session。题目没有展示触发该日志的请求,因此还不能确定它发生在哪个调用中。
可以按发生时机排查:
- 如果出现在登录前的用户状态检查中,将未登录作为正常分支处理,避免与 OAuth 回调错误混为一谈。
- 如果出现在
createSession()之后,检查后续请求是否复用了同一个account/client,以及 endpoint、project ID 是否一致。 - 如果 Session 创建本身失败,先处理该请求的错误,再读取当前用户。
示例将 account.get() 放在 createSession() 之后,用来确认应用侧能否读取当前用户。不要把这里的 account scopes 错误直接当成 Google OAuth scopes 配置错误。
如果修改后仍然失败
需要区分两段重定向:Google 授权完成后先返回 Appwrite,随后 Appwrite 再返回移动应用。
Google Cloud 中的 Authorized redirect URI 应填写 Appwrite Console 的 Google Provider 配置页面提供的回调地址。移动应用的 appwrite-callback-...://localhost 则传给 Appwrite 的 success、failure 参数。Google 集成文档要求创建 Web application 类型的 OAuth 客户端,并将 Appwrite 提供的 URI 配置到 Google Cloud。Appwrite Google 集成说明
排查时检查回调是否包含参数即可,不要记录完整 secret。这里的 secret 是用于建立 Appwrite Session 的临时凭据,与保存在 Appwrite Provider 配置中的 Google Client secret 是不同的值。
备注:内容仅供参考。
