# Electron 集成 (/docs/integrations/electron)

将 Better Auth 集成到 Electron 中。



Electron 是一个流行的框架，使用 Web 技术构建跨平台的桌面应用程序。

Better Auth 可以集成到 Electron 应用中，提供安全的认证流程，利用系统浏览器。

## 安装 [#安装]

<Steps>
  <Step>
    ### 配置 Better Auth 前端和后端 [#配置-better-auth-前端和后端]

    在与 Electron 集成之前，请确保您已经设置好了 Better Auth 服务器和客户端。

    要开始，请查看我们的 [安装指南](/docs/installation) 来设置 Better Auth。
  </Step>

  <Step>
    ### 安装所需包 [#安装所需包]

    在您的服务器、Electron 应用和 Web 客户端项目中安装 Better Auth 服务器包和 Electron 集成包。

    **在每个项目中运行：**

    <CodeBlockTabs defaultValue="npm" groupId="persist-install">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="npm">
          npm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="pnpm">
          pnpm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="yarn">
          yarn
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="bun">
          bun
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="npm">
        ```bash
        npm install better-auth @better-auth/electron
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pnpm">
        ```bash
        pnpm add better-auth @better-auth/electron
        ```
      </CodeBlockTab>

      <CodeBlockTab value="yarn">
        ```bash
        yarn add better-auth @better-auth/electron
        ```
      </CodeBlockTab>

      <CodeBlockTab value="bun">
        ```bash
        bun add better-auth @better-auth/electron
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    <Callout type="info">
      我们支持比最新稳定版本的 Electron 主版本晚两个主版本。
      这样能让您使用获得安全更新的版本，且符合 [Electron 的版本支持策略](https://www.electronjs.org/docs/latest/tutorial/electron-timelines#breaking-api-changes)。
    </Callout>
  </Step>

  <Step>
    ### 向 Better Auth 服务器添加 Electron 插件 [#向-better-auth-服务器添加-electron-插件]

    将 Electron 插件添加到您的 Better Auth 服务器。

    ```ts title="web/lib/auth.ts"
    import { betterAuth } from "better-auth";
    import { electron } from "@better-auth/electron";

    export const auth = betterAuth({
      plugins: [electron()],
      emailAndPassword: {
        enabled: true,
      },
      social: {
        google: {
          clientId: process.env.GOOGLE_CLIENT_ID!,
          clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
        },
      },
    });
    ```
  </Step>

  <Step>
    ### 在 Web 客户端添加代理插件 [#在-web-客户端添加代理插件]

    在前端添加代理插件，以处理重定向回 Electron 应用。

    ```ts title="web/lib/auth-client.ts"
    import { createAuthClient } from "better-auth/client";
    import { electronProxyClient } from "@better-auth/electron/proxy";

    export const authClient = createAuthClient({
      baseURL: "http://localhost:8081",
      plugins: [
        electronProxyClient({
          protocol: {
            scheme: "com.example.app"
          },
        }),
      ],
    });
    ```
  </Step>

  <Step>
    ### 初始化 Electron 客户端 [#初始化-electron-客户端]

    ```ts title="electron/lib/auth-client.ts"
    import { createAuthClient } from "better-auth/client";
    import { electronClient } from "@better-auth/electron/client";

    export const authClient = createAuthClient({
      baseURL: "http://localhost:8081", // 前端 Better Auth 的基础 URL
      plugins: [
        electronClient({
          signInURL: "https://app.example.com/sign-in", // 认证重定向的 URL
          protocol: {
            scheme: "com.example.app" // Electron 应用注册的自定义协议
          },
          storage: {
            getItem: async (key) => {
              // 从存储中检索条目
            },
            setItem: async (key, value) => {
              // 在存储中设置条目
            },
          },
        }),
      ],
    });
    ```

    如果您更愿意使用默认的存储方案，可以这样做：

    1. 确保已安装 `conf` 包：

    <CodeBlockTabs defaultValue="npm" groupId="persist-install">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="npm">
          npm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="pnpm">
          pnpm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="yarn">
          yarn
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="bun">
          bun
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="npm">
        ```bash
        npm install conf
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pnpm">
        ```bash
        pnpm add conf
        ```
      </CodeBlockTab>

      <CodeBlockTab value="yarn">
        ```bash
        yarn add conf
        ```
      </CodeBlockTab>

      <CodeBlockTab value="bun">
        ```bash
        bun add conf
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    2. 然后可以按如下方式使用：

    ```ts title="electron/lib/auth-client.ts"
    import { storage } from "@better-auth/electron/storage";

    electronClient({
      storage: storage(),
    });
    ```

    <Callout type="warning">
      您不应直接将 authClient 暴露给渲染进程。相反，[请创建 IPC 桥](#creating-ipc-bridges)以在主进程和渲染进程之间进行安全通信。

      同时，请确保不要向渲染进程暴露任何敏感数据（如令牌或 Cookie），以缓解注入攻击。
    </Callout>
  </Step>

  <Step>
    ### 协议和可信来源 [#协议和可信来源]

    Electron 插件使用深度链接在认证后将用户重定向回您的应用。
    为此，您需要在 **Better Auth 服务器** 的 `trustedOrigins` 中添加应用的协议方案。

    首先，请确保在构建配置中注册了自定义协议方案。

    <Tabs items="[&#x22;electron-forge&#x22;, &#x22;electron-builder&#x22;]">
      <Tab value="electron-forge">
        ```ts title="forge.config.js"
        module.exports = {
          packagerConfig: {
            protocols: [{
              name: "MyApp Protocol",
              schemes: ["com.example.app"],
            }],
          },
          // ...其他配置选项
        };
        ```
      </Tab>

      <Tab value="electron-builder">
        ```ts title="electron-builder.json5"
        {
          "protocols": [
            {
              "name": "MyApp Protocol",
              "schemes": ["com.example.app"]
            }
          ],
          // ...其他配置选项
        }
        ```
      </Tab>
    </Tabs>

    然后，更新 Better Auth 配置以在 `trustedOrigins` 中包含该方案：

    ```ts title="web/lib/auth.ts"
    export const auth = betterAuth({
      trustedOrigins: ["com.example.app:/"],
    });
    ```
  </Step>

  <Step>
    ### 配置 BrowserWindow [#配置-browserwindow]

    确保您的 `BrowserWindow` 配置中 `nodeIntegration` 设置为 `false`，`contextIsolation` 设置为 `true`，以确保 NodeJS API 不会暴露给在浏览器中运行的任何 JavaScript 进程。

    ```ts title="electron/main.ts"
    import { BrowserWindow } from "electron";
    import { join } from "node:path";

    const win = new BrowserWindow({
      webPreferences: {
        preload: join(__dirname, "preload.mjs"),
        nodeIntegration: false,
        contextIsolation: true,
      },
    });
    ```

    <Callout type="warning">
      如果要在 `sandbox` 模式下运行进程，请确保将 `@better-auth/electron` 打包到预加载脚本中。
      仅在预加载脚本中导入 `@better-auth/electron/preload`，并启用 tree-shaking，以避免打包不必要的依赖项。

      以下示例使用 electron-vite，但该概念适用于任何打包工具：

      ```ts title="electron.vite.config.ts"
      import { defineConfig } from "electron-vite";

      export default defineConfig({
        preload: {
          build: {
            externalizeDeps: { // [!code highlight]
              exclude: ["@better-auth/electron"], // [!code highlight]
            }, // [!code highlight]
          },
        },
      });
      ```

      如果配置使用显式外部依赖列表，请**不要**将 `@better-auth/electron` 添加到该列表中，以便将其打包进去。
    </Callout>
  </Step>

  <Step>
    ### 设置主进程 [#设置主进程]

    在 Electron 主进程中，使用 Electron 认证客户端的 `setupMain()` 方法来处理必要的配置。

    这将：

    * 注册深度链接的协议处理器。
    * 注册 [用户图像代理](#user-image-proxy) 协议。
    * 设置内容安全策略。
    * 设置主进程与渲染进程之间的 IPC 桥。

    ```ts title="electron/main.ts"
    import { authClient } from "./lib/auth-client";

    authClient.setupMain();
    ```

    请注意，这必须在应用准备就绪之前调用。
  </Step>

  <Step>
    ### 设置渲染进程 [#设置渲染进程]

    在预加载脚本中，使用 Electron 认证客户端的 `setupRenderer()` 方法向渲染进程公开安全的 IPC 桥。

    ```ts title="electron/preload.ts"
    import { setupRenderer } from "@better-auth/electron/preload";

    setupRenderer();
    ```

    请注意，这也必须在应用准备就绪之前调用。

    <Callout type="info">
      要推断公开桥的类型，可以使用 `authClient.$Infer.Bridges` 来扩展 `Window` 接口。

      ```ts title="electron/preload.d.ts"
      import type { authClient } from "./lib/auth-client";

      declare global {
        type Bridges = typeof authClient.$Infer.Bridges;
        interface Window extends Bridges {}
      }
      ```
    </Callout>
  </Step>
</Steps>

## 用法 [#用法]

### 浏览器中的授权处理 [#浏览器中的授权处理]

为了将用户重定向回您的 Electron 应用，您需要在 Web 的登录回调页面调用 `ensureElectronRedirect()` 方法。同时，请确保在发起登录请求时通过 `fetchOptions.query` 保留 PKCE 和 state 参数。

以下示例使用 React，但逻辑适用于任意框架：

```tsx title="web/pages/sign-in.tsx"
import { useEffect, use } from "react";
import { authClient } from "../auth-client";

function SignIn({
  searchParams,
}: {
  searchParams: Promise<{
    client_id?: string | undefined;
    state?: string | undefined;
    code_challenge?: string | undefined;
  }>;
}) {
  const query = use(searchParams);

  useEffect(() => {
    const id = authClient.ensureElectronRedirect();
    return () => {
      clearTimeout(id);
    };
  }, []);

  return (
    <button
      onClick={() =>
        authClient.signIn.social({
          provider: "google",
          fetchOptions: { query }, // preserve PKCE/state
        })
      }
    >
      使用 Google 登录
    </button>
  );
}
```

要处理已登录用户，可以使用 `electron.transferUser` 方法在客户端和 `electronTransferUser` 在服务器端将用户转移到 Electron 应用中。[参见示例](https://github.com/better-auth/better-auth/blob/main/demo/nextjs/app/\(auth\)/sign-in/_components/electron.tsx)

### Electron 中的身份验证处理 [#electron-中的身份验证处理]

在 Electron 的渲染进程中，您可以使用预加载脚本暴露的 IPC 桥：

```tsx title="electron/App.tsx"
function Auth() {
  useEffect(() => {
    const unsubscribeAuthenticated =
      window.onAuthenticated((user) => {
        console.log("认证用户:", user);
      });
    const unsubscribeAuthError =
      window.onAuthError((ctx) => {
        toast.error(`认证错误: ${ctx.message}`);
      });

    return () => {
      unsubscribeAuthenticated();
      unsubscribeAuthError();
    };
  }, []);

  return (
    <>
      <button onClick={() => window.requestAuth()}>
        使用浏览器登录
      </button>
      <button
        onClick={() =>
          window.requestAuth({
            provider: "google", // sign in with social provider, redirecting directly to the provider
          })
        }
      >
        使用 Google 登录
      </button>
    </>
  );
}
```

在主进程中，您可以直接调用 auth client：

```ts title="electron/main.ts"
import { authClient } from "./lib/auth-client";

authClient.requestAuth();
```

### 登出 [#登出]

在渲染进程中，可以使用 `signOut` 桥退出登录：

```tsx title="electron/App.tsx"
<button onClick={() => window.signOut()}>退出登录</button>
```

### 订阅用户更新 [#订阅用户更新]

可以通过渲染进程的 `onUserUpdated` 桥监听用户信息变更：

```tsx title="electron/App.tsx"
useEffect(() => {
  const unsubscribe = window.onUserUpdated((user) => {
    console.log("用户更新:", user);
  });
  return () => unsubscribe();
}, []);
```

### 错误处理 [#错误处理]

通过渲染进程的 `onAuthError` 桥监听认证错误。该桥会接收由 fetch 钩子转发的错误上下文。

```tsx title="electron/error-listener.tsx"
useEffect(() => {
  const unsubscribe = window.onAuthError((ctx) => {
    console.error("认证错误:", ctx);
  });
  return () => unsubscribe();
}, []);
```

### 手动令牌交换 [#手动令牌交换]

在某些环境中，深度链接跳转可能不稳定（例如某些 Linux 桌面环境、沙箱浏览器，或者 Electron 应用未注册为该协议的默认处理程序）。
作为备选方案，用户可以从 Web 界面手动复制授权码并粘贴到 Electron 应用中。

**前端：**

认证完成后，可以通过代理客户端的 `electron.getAuthorizationCode()` 方法获取授权码。您可以将该码展示给用户以便复制：

```tsx title="web/components/providers.tsx"
import { useEffect } from "react";
import { authClient } from "../auth-client";

function Providers({ children }) {
  useEffect(() => {
    const authorizationCode = authClient.electron.getAuthorizationCode();
    if (authorizationCode) {
      // 向用户展示授权码
      console.log("授权码:", authorizationCode);
    }
  }, []);

  return <>{children}</>;
}
```

授权码也会在 Electron 应用中的登录发起后返回，或在使用 `transferUser` 方法时返回。

```tsx title="web/pages/sign-in.tsx"
const { data } = await authClient.electron.transferUser({
  fetchOptions: {
    query: params,
  },
});

if (data?.electron_authorization_code) {
  // 向用户展示授权码
}
```

**Electron：**

在 Electron 渲染进程中，提供输入框供用户粘贴授权码，然后调用 `window.authenticate()` 交换会话：

```tsx title="electron/App.tsx"
function ManualCodeEntry() {
  return (
    <input
      type="text"
      placeholder="请粘贴授权码"
      maxLength={32}
      onChange={(e) => {
        if (e.target.value.length === 32) {
          // 重要：必须先调用过 requestAuth()
          window.authenticate({ token: e.target.value }); // [!code highlight]
        }
      }}
    />
  );
}
```

`authenticate` 桥会将授权码发送到主进程，主进程使用存储的 PKCE 验证码和 state 进行交换。成功时，会触发 `onAuthenticated` 桥。

<Callout type="info">
  授权码是一个短时有效的 32 字符字符串。必须先调用 `requestAuth()`，因为其生成的验证码和 state 是 `authenticate()` 所依赖的。
</Callout>

### 用户头像代理 [#用户头像代理]

为避免 CSP 问题，Electron 插件通过自定义协议 `user-image://` 安全代理用户头像图片。

您可以直接在渲染进程中使用图片 URL：

```tsx title="electron/App.tsx"
<img src={user.image} alt="头像" />
{/* 或 <img src="user-image://<user-id>" /> */}
```

要配置或禁用代理，请参见 [`userImageProxy`](#user-image-proxy) 选项。

<Callout type="warning">
  若要访问除当前用户外其他用户的头像，请启用 [管理员插件](/docs/plugins/admin)。
</Callout>

### 创建 IPC 桥 [#创建-ipc-桥]

您应该创建 IPC 桥以扩展渲染进程暴露的功能，确保 API 表面尽可能小且安全。

首先，在主进程中创建 IPC 处理器，使用 `authClient` 执行相应操作：

```ts title="electron/main.ts"
import { authClient } from "./lib/auth-client";
import { ipcMain } from "electron";

ipcMain.handle("myBridge", async (_event, data) => {
  const cookie = authClient.getCookie();
  return await authClient.someEndpoint({
    data,
    fetchOptions: {
      headers: { cookie },
    },
  });
});
```

接着，在预加载脚本中使用 `contextBridge` 暴露桥：

```ts title="electron/preload.ts"
import { contextBridge, ipcRenderer } from "electron";

contextBridge.exposeInMainWorld("myBridge", (data: Record<string, any>) => {
  return ipcRenderer.invoke("myBridge", data);
});
```

<Callout type="info">
  要推断您桥的类型，可以扩展 `Window` 接口：

  ```ts title="electron/preload.d.ts"
  import type { authClient } from "./lib/auth-client";

  declare global {
    type Bridges = typeof authClient.$Infer.Bridges;
    interface Window extends Bridges {
      myBridge: (data: Record<string, any>) => Promise<any>;
    }
  }
  ```
</Callout>

现在您可以在渲染进程的任何位置调用自定义桥：

```tsx title="electron/App.tsx"
useEffect(() => {
  window
    .myBridge({
      foo: "bar",
    })
    .then((res) => {
      console.log("桥响应:", res);
    });
}, []);
```

更多详细信息请参考 Electron 的[进程间通信（IPC）](https://www.electronjs.org/docs/latest/tutorial/ipc)教程。

## 选项 [#选项]

### 服务器插件 [#服务器插件]

#### `codeExpiresIn?` [#codeexpiresin]

授权码有效时长（秒）。默认 `300`

注意，授权码在活跃的端点使用中会刷新。

```ts title="web/auth.ts"
electron({
  codeExpiresIn: 300, // 5 分钟
});
```

#### `redirectCookieExpiresIn?` [#redirectcookieexpiresin]

重定向 cookie 有效时长（秒）。默认 `120`

```ts title="web/auth.ts"
electron({
  redirectCookieExpiresIn: 120, // 2 分钟
});
```

重定向 cookie 名称由 `clientID` 派生。

#### `cookiePrefix?` [#cookieprefix]

插件设置的 cookie 前缀。默认 `better-auth`

```ts title="web/auth.ts"
electron({
  cookiePrefix: "better-auth",
});
```

#### `clientID?` [#clientid]

用于识别 Electron 客户端的 client id。默认 `electron`

确保与代理客户端和 electron 客户端插件使用的 clientID 一致。

```ts title="web/auth.ts"
electron({
  clientID: "electron",
});
```

### 代理客户端 [#代理客户端]

#### `protocol` [#protocol]

Electron 深度链接使用的协议方案。

应遵循 [反向域名命名规范](https://datatracker.ietf.org/doc/html/rfc8252#section-7.1) 保证唯一性。

确保与 Electron 客户端和 `trustedOrigins` 使用的协议一致。

```ts title="web/auth-client.ts"
electronProxyClient({
  protocol: "com.example.app",
});
```

```ts title="web/auth-client.ts"
electronProxyClient({
  protocol: {
    scheme: "com.example.app",
  },
});
```

#### `callbackPath?` [#callbackpath]

认证重定向使用的回调路径。默认 `/auth/callback`

确保与 electron 客户端插件路径一致。

```ts title="web/auth-client.ts"
electronProxyClient({
  callbackPath: "/auth/callback",
});
```

#### `clientID?` [#clientid-1]

用于识别 Electron 客户端的 client id。默认 `electron`

确保与服务器和 electron 客户端插件的 clientID 一致。

```ts title="web/auth-client.ts"
electronProxyClient({
  clientID: "electron",
});
```

#### `cookiePrefix?` [#cookieprefix-1]

插件设置的 cookie 前缀。默认 `better-auth`

```ts title="web/auth-client.ts"
electronProxyClient({
  cookiePrefix: "better-auth",
});
```

### 客户端插件 [#客户端插件]

#### `signInURL` [#signinurl]

用于认证跳转的 URL。

```ts title="electron/auth-client.ts"
electronClient({
  signInURL: "http://localhost:3000/sign-in",
});
```

#### `protocol` [#protocol-1]

Electron 深度链接使用的协议方案。

应遵循 [反向域名命名规范](https://datatracker.ietf.org/doc/html/rfc8252#section-7.1) 保证唯一性。

确保与代理客户端和 `trustedOrigins` 的协议一致。

```ts title="web/auth-client.ts"
electronProxyClient({
  protocol: "com.example.app",
});
```

```ts title="web/auth-client.ts"
electronProxyClient({
  protocol: {
    scheme: "com.example.app",
  },
});
```

#### `callbackPath?` [#callbackpath-1]

认证重定向使用的回调路径。默认 `/auth/callback`

确保与代理客户端插件路径一致。

```ts title="electron/auth-client.ts"
electronClient({
  callbackPath: "/auth/callback",
});
```

#### `storage` [#storage]

用于存储会话和 cookie 数据的存储方案。

默认是生成一个放在 `userData` 目录的存储文件，文件名由项目名派生。

```ts title="electron/auth-client.ts"
electronClient({
  storage: {
    getItem: (key) => {
      // 从存储读取条目
    },
    setItem: (key, value) => {
      // 在存储写入条目
    },
  },
});
```

#### `storagePrefix?` [#storageprefix]

本地存储键的前缀。默认 `better-auth`

```ts title="electron/auth-client.ts"
electronClient({
  storagePrefix: "better-auth",
});
```

#### `cookiePrefix?` [#cookieprefix-2]

服务器 cookie 名称的前缀（数组）。默认 `better-auth`

用于识别哪类 cookie 属于 better-auth，防止第三方 cookie 引起无限重新请求。

```ts title="electron/auth-client.ts"
electronClient({
  cookiePrefix: "better-auth",
});
```

```ts title="electron/auth-client.ts"
electronClient({
  cookiePrefix: ["better-auth", "my-app"],
});
```

#### `channelPrefix?` [#channelprefix]

IPC 桥通信通道的前缀。默认 `better-auth`

```ts title="electron/auth-client.ts"
electronClient({
  channelPrefix: "myapp",
});
```

#### `clientID?` [#clientid-2]

用于识别 Electron 客户端的 client id。默认 `electron`

确保和服务器及代理客户端插件中的 clientID 一致。

```ts title="electron/auth-client.ts"
electronClient({
  clientID: "electron",
});
```

#### `sanitizeUser?` [#sanitizeuser]

发送给渲染进程前对用户对象做 sanitize 的函数。用于剥离敏感字段。

```ts title="electron/auth-client.ts"
electronClient({
  sanitizeUser: (user) => {
    const { sensitiveField, ...rest } = user;
    return rest;
  },
});
```

#### `userImageProxy?` [#userimageproxy]

用户头像代理的配置。详见 [用户头像代理](#user-image-proxy)。

```ts title="electron/auth-client.ts"
electronClient({
  userImageProxy: {
    enabled: true, // 默认开启
    maxSize: 1024 * 1024 * 5, // 默认 5MB
  },
});
```

若要完全禁用代理：

```ts title="electron/auth-client.ts"
electronClient({
  userImageProxy: {
    enabled: false,
  },
});
```

#### `disableCache?` [#disablecache]

是否禁用会话数据的本地缓存。默认 `false`

```ts title="electron/auth-client.ts"
electronClient({
  disableCache: true,
});
```

