# Apple (/docs/authentication/apple)

Apple 提供者的设置与使用。



<Steps>
  <Step>
    ### 获取 OAuth 凭证 [#获取-oauth-凭证]

    使用 Apple 登录时，您需要一个客户端 ID、团队 ID、密钥 ID 和私钥。您可以从 [Apple 开发者门户](https://developer.apple.com/account/resources/authkeys/list) 获取它们，然后用它们生成客户端密钥 JWT。

    您需要一个有效的 **Apple 开发者账户** 才能访问开发者门户并生成这些凭证。

    按照以下步骤设置您的 App ID、服务 ID 并生成客户端密钥所需的密钥：

    1. **导航到证书、标识符与配置文件：**
       在 Apple 开发者门户中，转到“证书、标识符与配置文件”部分。

    2. **创建 App ID：**
       * 进入“标识符”标签页。
       * 点击标识符旁边的 `+` 图标。
       * 选择 `App IDs`，然后点击 `继续`。
       * 选择 `App` 作为类型，然后点击 `继续`。
       * **描述：** 输入您的应用名称（例如，“我的出色应用”）。此名称在用户登录时可能会显示给用户。
       * **捆绑标识符：** 设置一个捆绑标识符。推荐的格式是反向域名（例如，`com.yourcompany.yourapp`）。使用 `.ai`（用于应用标识符）等后缀有助于组织，但不是必需的（例如，`com.yourcompany.yourapp.ai`）。
       * 向下滚动到 **功能**。选择 `使用 Apple 登录` 的复选框。
       * 点击 `继续`，然后点击 `注册`。

    3. **创建服务 ID：**
       * 返回“标识符”标签页。
       * 点击 `+` 图标。
       * 选择 `服务 ID`，然后点击 `继续`。
       * **描述：** 输入此服务的描述（例如，再次输入您的应用名称）。
       * **标识符：** 为服务设置一个唯一标识符。将其设置为与您的 App ID 不同的反向域名格式（例如，`com.yourcompany.yourapp.si`，其中 `.si` 表示服务标识符——这仅用于您的组织，并非必需）。&#x2A;*此服务 ID 将作为您的 `clientId`。**
       * 点击 `继续`，然后点击 `注册`。

    4. **配置服务 ID：**
       * 在 `标识符` 列表中找到您刚刚创建的服务 ID 并点击它。
       * 勾选 `使用 Apple 登录` 的功能，然后点击 `配置`。
       * 在 **主 App ID** 下，选择您之前创建的 App ID（例如，`com.yourcompany.yourapp.ai`）。
       * 在 **域和子域** 下，列出您将用于 Apple 登录的所有根域（例如，`example.com`、`anotherdomain.com`）。
       * 在 **返回 URL** 中，输入回调 URL：`https://yourdomain.com/api/auth/callback/apple`。添加所有必要的返回 URL。
       * 点击 `下一步`，然后点击 `完成`。
       * 点击 `继续`，然后点击 `保存`。

    5. **创建客户端密钥：**
       * 进入 `密钥` 标签页。
       * 点击 `+` 图标以创建新密钥。
       * **密钥名称：** 输入密钥名称（例如，“使用 Apple 登录密钥”）。
       * 向下滚动并选择 `使用 Apple 登录` 的复选框。
       * 点击 `使用 Apple 登录` 旁边的 `配置` 按钮。
       * 选择您之前创建的 **主 App ID**。
       * 点击 `保存`，然后点击 `继续`，再点击 `注册`。
       * **下载密钥：** 立即下载 `.p8` 密钥文件。&#x2A;*此文件只能下载一次。** 记下密钥 ID（在创建密钥后可在密钥页面找到）和您的团队 ID（可在 Apple 开发者账户设置中找到）。

    6. **生成客户端密钥（JWT）：**
       Apple 要求使用下载的 `.p8` 密钥、密钥 ID 和团队 ID 动态生成 JSON Web 令牌（JWT）。此 JWT 作为您的 `clientSecret`。

       我们建议您在身份验证配置中使用下方的 [`jose` 示例](#configure-the-provider) 生成客户端密钥 JWT。您也可以按照 [Apple 的文档](https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret) 手动生成，然后将生成的 JWT 作为 `clientSecret` 传入。

       **注意：** Apple 会拒绝未来过期时间超过 15,777,000 秒（六个月）的客户端密钥 JWT。为了保持身份验证不中断，您需要在过期前重新生成客户端密钥。
  </Step>

  <Step>
    Apple 的 OAuth 实现要求 `clientSecret` 使用 JWT（JSON Web 令牌），而不是共享密钥字符串。

    根据 [Apple 文档](https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret)，这些 JWT：

    * 未来过期时间不能超过 15,777,000 秒（六个月）
    * 必须使用您的私钥进行加密签名

    安装 `jose` 包以生成客户端密钥 JWT。

    <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 jose
        ```
      </CodeBlockTab>

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

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

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

  <Step>
    ### 配置提供者 [#配置提供者]

    要配置提供者，您需要将其添加到身份验证实例的 `socialProviders` 选项中。

    您还需要在身份验证实例配置中将 `https://appleid.apple.com` 添加到 `trustedOrigins` 数组中，以允许与 Apple 的身份验证服务器通信。

    ```ts title="auth.ts"
    import { betterAuth } from "better-auth"
    import { importPKCS8, SignJWT } from "jose";

    // 生成 '使用 Apple 登录' 所需的客户端密钥 JWT。
    async function generateAppleClientSecret(clientId, teamId, keyId, privateKey) {
        const key = await importPKCS8(privateKey, "ES256");
        const now = Math.floor(Date.now() / 1000);
        return new SignJWT({})
            .setProtectedHeader({ alg: "ES256", kid: keyId })
            .setIssuer(teamId)
            .setSubject(clientId)
            .setAudience("https://appleid.apple.com")
            .setIssuedAt(now)
            .setExpirationTime(now + 180 * 24 * 60 * 60)
            .sign(key);
    }

    export const auth = betterAuth({
        socialProviders: {
            apple: async () => ({ // [!code highlight]
                clientId: process.env.APPLE_CLIENT_ID as string, // [!code highlight]
                clientSecret: await generateAppleClientSecret(
                    process.env.APPLE_CLIENT_ID!, // [!code highlight]
                    process.env.APPLE_TEAM_ID!, // [!code highlight]
                    process.env.APPLE_KEY_ID!, // [!code highlight]
                    process.env.APPLE_PRIVATE_KEY!, // [!code highlight]
                ), // [!code highlight]
                // 可选
                appBundleIdentifier: process.env.APPLE_APP_BUNDLE_IDENTIFIER as string, // [!code highlight]
            }), // [!code highlight]
        },
        // 为 Apple 登录流程添加 appleid.apple.com 到受信任来源
        trustedOrigins: ["https://appleid.apple.com"], // [!code highlight]
    })
    ```

    在原生 iOS 上，它使用的不是服务 ID，而是应用 ID（bundle ID）作为客户端 ID，因此如果在 `signIn.social` 中将服务 ID 作为 `clientId` 并使用 `idToken`，则会抛出错误：`JWTClaimValidationFailed: unexpected "aud" claim value`。因此，当您想使用 ID Token 通过 Apple 登录时，需要提供 `appBundleIdentifier`。

    对于多个静态受众（例如服务 ID 加原生 bundle ID），请优先使用 `clientId: string[]` 或 `audience: string[]`，而不是仅设置 `appBundleIdentifier`。当受众必须依赖请求时（例如读取 `x-platform` 头），请使用自定义的 [`verifyIdToken`](/docs/concepts/oauth#verifyidtoken) 回调——它会将请求上下文作为第三个参数传入。

    <Callout type="warn">
      Apple 仅在首次授权时返回 `email` 声明；此后的每次登录都会省略它，而且 Apple 不提供可稍后获取它的用户信息端点。有关持久化的指导以及 `mapProfileToUser` 回退，请参见 [Handling Providers Without Email](/docs/concepts/oauth#处理不提供邮箱的提供商)。
    </Callout>
  </Step>
</Steps>

<Callout type="warn">
  **本地主机和非 TLS 限制**

  Apple 登录不支持 `localhost` 或非 HTTPS URL。开发期间：

  * 您不能使用 `http://localhost` 作为返回 URL
  * 您必须使用具有有效 HTTPS/TLS 证书的域

  此限制由 Apple 的安全要求强制执行，无法绕过。
</Callout>

## 使用方法 [#使用方法]

### Apple 登录 [#apple-登录]

要使用 Apple 登录，您可以使用客户端提供的 `signIn.social` 函数。`signIn` 函数接收一个包含以下属性的对象：

* `provider`：要使用的提供者。应设置为 `apple`。

```ts title="auth-client.ts" /
import { createAuthClient } from "better-auth/client"
const authClient =  createAuthClient()

const signIn = async () => {
    const data = await authClient.signIn.social({
        provider: "apple"
    })
}
```

### 使用 ID 令牌进行 Apple 登录 [#使用-id-令牌进行-apple-登录]

要使用 ID 令牌进行 Apple 登录，您可以使用 `signIn.social` 函数传递 ID 令牌。

当您在客户端拥有 Apple 的 ID 令牌并希望在服务器端使用它进行登录时，这非常有用。

<Callout>
  如果提供了 ID 令牌，则不会发生重定向，用户将直接登录。
</Callout>

```ts title="auth-client.ts"
await authClient.signIn.social({
    provider: "apple",
    idToken: {
        token: // Apple ID 令牌,
        nonce: // 随机数（可选）
        accessToken: // 访问令牌（可选）
    }
})
```

## 生成 Apple 客户端密钥（JWT） [#生成-apple-客户端密钥jwt]

如果您之前使用过本节中的页面内生成器，请改用上面的 [`jose` 示例](#configure-the-provider)。它使用 Better Auth 的异步提供者配置，通过您的客户端 ID、团队 ID、密钥 ID 和私钥生成 Apple 客户端密钥 JWT。

该示例使用 180 天的过期时间，低于 Apple 的六个月限制。通过配置生成令牌，可以避免将静态生成的令牌复制到您的身份验证设置中。

或者，您仍然可以按照 [Apple 的文档](https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret) 手动生成客户端密钥 JWT，然后将生成的 JWT 作为 `clientSecret` 传入。如果您选择这种方式，请记得在它过期前替换它。

