Expo 集成

Expo 是一个用于使用 React Native 构建跨平台应用的流行框架。Better Auth 同时支持 Expo 原生和网页应用。

安装

配置 Better Auth 后端

在将 Better Auth 与 Expo 一起使用之前,请确保你已经设置了 Better Auth 后端。你可以使用单独的服务器,或者利用 Expo 的新 API 路由功能来托管你的 Better Auth 实例。

要开始使用,请查看我们的安装指南来在你的服务器上设置 Better Auth。如果你想查看完整示例,可以在这里找到。

要在 Expo 中使用新的 API 路由功能托管 Better Auth 实例,你可以在 Expo 应用中创建一个新的 API 路由并挂载 Better Auth 处理程序。

app/api/auth/[...auth]+api.ts
import { auth } from "@/lib/auth"; // 导入 Better Auth 处理程序
 
const handler = auth.handler;
export { handler as GET, handler as POST }; // 导出处理程序用于 GET 和 POST 请求

安装服务器依赖

在你的服务器应用中安装 Better Auth 包和 Expo 插件。

npm install @better-auth/expo better-auth

安装客户端依赖

你还需要在 Expo 应用中安装 Better Auth 包和 Expo 插件。

npm install better-auth @better-auth/expo 

如果你计划使用我们的社交集成(如 Google、Apple 等),则在 Expo 应用中还需要一些额外的依赖。在默认的 Expo 模板中,这些依赖已经安装,所以如果你已经有这些依赖,可以跳过此步骤。

npm install expo-linking expo-web-browser expo-constants

在服务器上添加 Expo 插件

在你的 Better Auth 服务器上添加 Expo 插件。

server.ts
import { betterAuth } from "better-auth";
import { expo } from "@better-auth/expo";
 
export const auth = betterAuth({
    plugins: [expo()]
});

初始化 Better Auth 客户端

要在 Expo 应用中初始化 Better Auth,你需要使用 Better Auth 后端的基础 URL 调用 createAuthClient。确保从 /react 导入客户端。

你还需要从 @better-auth/expo/client 导入客户端插件,并在初始化认证客户端时将其传递给 plugins 数组。

这很重要,因为:

  • 社交认证支持: 通过在 Expo 网页浏览器中处理授权 URL 和回调来启用社交认证流程。
  • 安全 Cookie 管理: 安全地存储 cookie,并自动将它们添加到你的认证请求的头部。
src/auth-client.ts
import { createAuthClient } from "better-auth/react";
import { expoClient } from "@better-auth/expo/client";
import * as SecureStore from "expo-secure-store";
 
export const authClient = createAuthClient({
    baseURL: "http://localhost:8081", /* Better Auth 后端的基础 URL。 */
    plugins: [
        expoClient({
            scheme: "myapp",
            storagePrefix: "myapp",
            storage: SecureStore,
        })
    ]
});

如果你已更改默认路径 /api/auth,请确保包含完整的 URL,包括路径。

方案和可信来源

Better Auth 使用深层链接将用户在认证后重定向回你的应用。要启用此功能,你需要将你的应用方案添加到 Better Auth 配置中的 trustedOrigins 列表。

首先,确保你在 app.json 文件中定义了方案。

app.json
{
    "expo": {
        "scheme": "myapp"
    }
}

然后,更新你的 Better Auth 配置,将方案包含在 trustedOrigins 列表中。

auth.ts
export const auth = betterAuth({
    trustedOrigins: ["myapp://"]
})

配置 Metro 打包器

要解析 Better Auth 导出,你需要在 Metro 配置中启用 unstable_enablePackageExports

metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
 
const config = getDefaultConfig(__dirname)
 
config.resolver.unstable_enablePackageExports = true; 
 
module.exports = config;

如果你无法启用 unstable_enablePackageExports 选项,可以使用 babel-plugin-module-resolver 手动解析路径。

babel.config.js
module.exports = function (api) {
    api.cache(true);
    return {
        presets: ["babel-preset-expo"],
        plugins: [
            [
                "module-resolver",
                {
                    alias: {
                        "better-auth/react": "./node_modules/better-auth/dist/client/react/index.cjs",
                        "better-auth/client/plugins": "./node_modules/better-auth/dist/client/plugins/index.cjs",
                        "@better-auth/expo/client": "./node_modules/@better-auth/expo/dist/client.cjs",
                    },
                },
            ],
        ],
    }
}

修改后别忘了清除缓存。

npx expo start --clear

使用方法

用户认证

初始化 Better Auth 后,你现在可以在 Expo 应用中使用 authClient 来认证用户。

app/sign-in.tsx
import { useState } from "react"; 
import { View, TextInput, Button } from "react-native";
import { authClient } from "./auth-client";
 
export default function App() {
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
 
    const handleLogin = async () => {
        await authClient.signIn.email({
            email,
            password,
        })
    };
 
    return (
        <View>
            <TextInput
                placeholder="Email"
                value={email}
                onChangeText={setEmail}
            />
            <TextInput
                placeholder="Password"
                value={password}
                onChangeText={setPassword}
            />
            <Button title="Login" onPress={handleLogin} />
        </View>
    );
}

社交登录

对于社交登录,你可以使用 authClient.signIn.social 方法,并提供提供商名称和回调 URL。

app/social-sign-in.tsx
import { Button } from "react-native";
 
export default function App() {
    const handleLogin = async () => {
        await authClient.signIn.social({
            provider: "google",
            callbackURL: "/dashboard" // 在原生环境中将转换为深层链接(例如 `myapp://dashboard`)
        })
    };
    return <Button title="Login with Google" onPress={handleLogin} />;
}

IdToken 登录

如果你想在移动设备上发起提供商请求,然后在服务器上验证 ID 令牌,可以使用带有 idToken 选项的 authClient.signIn.social 方法。

app/social-sign-in.tsx
import { Button } from "react-native";
 
export default function App() {
    const handleLogin = async () => {
        await authClient.signIn.social({
            provider: "google", // 仅支持 google、apple 和 facebook 进行 idToken 登录
            idToken: {
                token: "...", // 来自提供商的 ID 令牌
                nonce: "...", // 来自提供商的随机数(可选)
            }
            callbackURL: "/dashboard" // 在原生环境中将转换为深层链接(例如 `myapp://dashboard`)
        })
    };
    return <Button title="Login with Google" onPress={handleLogin} />;
}

会话

Better Auth 提供了 useSession 钩子来在你的应用中访问当前用户的会话。

src/App.tsx
 
import { authClient } from "@/lib/auth-client";
 
export default function App() {
    const { data: session } = authClient.useSession();
 
    return <Text>Welcome, {session.user.name}</Text>;
}

在原生环境中,会话数据将缓存在 SecureStore 中。这将允许你在应用重新加载时无需显示加载动画。你可以通过向客户端传递 disableCache 选项来禁用此行为。

向你的服务器发送经过认证的请求

要向需要用户会话的服务器发送经过认证的请求,你必须从 SecureStore 检索会话 cookie,并手动将其添加到请求头部。

import { authClient } from "@/lib/auth-client";
 
const makeAuthenticatedRequest = async () => {
  const cookies = authClient.getCookie(); 
  const headers = {
    "Cookie": cookies, 
  };
  const response = await fetch("http://localhost:8081/api/secure-endpoint", { headers });
  const data = await response.json();
  return data;
};

示例:与 TRPC 一起使用

lib/trpc-provider.tsx
//...其他导入
import { authClient } from "@/lib/auth-client"; 
 
export const api = createTRPCReact<AppRouter>();
 
export function TRPCProvider(props: { children: React.ReactNode }) {
  const [queryClient] = useState(() => new QueryClient());
  const [trpcClient] = useState(() =>
    api.createClient({
      links: [
        httpBatchLink({
          //...你的其他选项
          headers() {
            const headers = new Map<string, string>(); 
            const cookies = authClient.getCookie(); 
            if (cookies) { 
              headers.set("Cookie", cookies); 
            } 
            return Object.fromEntries(headers); 
          },
        }),
      ],
    }),
  );
 
  return (
    <api.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>
        {props.children}
      </QueryClientProvider>
    </api.Provider>
  );
}

选项

Expo 客户端

storage:用于缓存会话数据和 cookie 的存储机制。

src/auth-client.ts
import { createAuthClient } from "better-auth/react";
import SecureStorage from "expo-secure-store";
 
const authClient = createAuthClient({
    baseURL: "http://localhost:8081",
    storage: SecureStorage
});

scheme:方案用于在用户使用 OAuth 提供商认证后深层链接回你的应用。默认情况下,Better Auth 尝试从 app.json 文件读取方案。如果你需要覆盖此设置,可以向客户端传递方案选项。

src/auth-client.ts
import { createAuthClient } from "better-auth/react";
 
const authClient = createAuthClient({
    baseURL: "http://localhost:8081",
    scheme: "myapp"
});

disableCache:默认情况下,客户端将在 SecureStore 中缓存会话数据。你可以通过向客户端传递 disableCache 选项来禁用此行为。

src/auth-client.ts
import { createAuthClient } from "better-auth/react";
 
const authClient = createAuthClient({
    baseURL: "http://localhost:8081",
    disableCache: true
});

Expo 服务器

服务器插件选项:

overrideOrigin:覆盖 Expo API 路由的源(默认值:false)。如果你遇到 Expo API 路由的跨域源问题,请启用此选项。