# 驗證方法

雖然 DID 錢包是主要且最安全的驗證方法，但 `@arcblock/did-connect-react` 仍為多種替代登入方法提供了強大的支援，以提供靈活性並滿足不同使用者的偏好。本節詳細介紹如何將 OAuth (社群登入)、Passkeys (無密碼驗證) 和统一登录站点群登入整合到您的應用程式中。

這些方法透過特定的 React Context Providers 啟用，並透過相應的 hooks 存取，這些 hooks 應巢狀嵌套在主 `SessionProvider` 中。

---

## OAuth (社群登入)

整合 Google、GitHub 或 Apple 等熱門社群登入供應商，讓使用者能用現有帳號登入。此功能由 `OAuthProvider` 和 `useOAuth` hook 管理。

### 1. 使用 `OAuthProvider` 設定

若要啟用 OAuth 功能，請用 `OAuthProvider` 包裹您的元件樹。通常會將其放置在 `SessionProvider` 內部。

```jsx OAuthProvider Setup icon=logos:react
import { SessionProvider } from '@arcblock/did-connect-react';
import { OAuthProvider } from '@arcblock/did-connect-react/lib/OAuth';

function App() {
  return (
    <SessionProvider>
      <OAuthProvider
        onBindOAuth={(provider) => console.log(`${provider.provider} 已綁定！`)}
        onUnbindOAuth={(account) => console.log(`${account.provider} 已解除綁定！`)}>
        {/* 您的應用程式元件 */}
      </OAuthProvider>
    </SessionProvider>
  );
}
```

**`OAuthProvider` 的 Props**

<x-field-group>
  <x-field data-name="children" data-type="ReactNode" data-required="true" data-desc="將有權存取 OAuth context 的子元件。"></x-field>
  <x-field data-name="locale" data-type="string" data-default="en" data-desc="用於 UI 訊息和提示的語言。"></x-field>
  <x-field data-name="onBindOAuth" data-type="function" data-desc="使用者成功綁定 OAuth 帳號後觸發的回呼函式。"></x-field>
  <x-field data-name="onUnbindOAuth" data-type="function" data-desc="使用者成功解除綁定 OAuth 帳號後觸發的回呼函式。"></x-field>
  <x-field data-name="onSwitchPassport" data-type="function" data-desc="使用者切換 passport 後觸發的回呼函式。"></x-field>
  <x-field data-name="session" data-type="object" data-desc="目前的使用者 session 物件，passport 切換 UI 需要。"></x-field>
</x-field-group>

### 2. 使用 `useOAuth` Hook

`useOAuth` hook 提供了與 OAuth 驗證流程互動的函式。

```jsx useOAuth Hook icon=logos:react
import { useOAuth } from '@arcblock/did-connect-react/lib/OAuth';

function SocialLoginButtons() {
  const { loginOAuth, getOAuthConfigList, bindAuthLoading } = useOAuth();
  const [providers, setProviders] = React.useState([]);

  React.useEffect(() => {
    getOAuthConfigList().then(setProviders);
  }, [getOAuthConfigList]);

  const handleLogin = async (provider) => {
    try {
      const result = await loginOAuth({ provider: provider.provider });
      console.log('登入成功：', result);
      // 處理登入結果，例如更新 session
    } catch (error) {
      console.error('登入失敗：', error.message);
    }
  };

  if (providers.length === 0) {
    return <p>未設定第三方登入方式。</p>;
  }

  return (
    <div>
      {providers.map((p) => (
        <button key={p.provider} onClick={() => handleLogin(p)} disabled={bindAuthLoading}>
          使用 {p.provider} 登入
        </button>
      ))}
    </div>
  );
}
```

### 核心函式

- **`loginOAuth({ provider }, { action, ... })`**: 為特定供應商啟動登入流程。將會開啟一個彈出視窗供使用者驗證。
- **`bindOAuth({ session, oauthItem })`**: 將一個新的社群帳號綁定到目前登入的使用者。
- **`unbindOAuth({ session, connectedAccount })`**: 從目前使用者解除綁定一個社群帳號。
- **`getOAuthConfigList()`**: 非同步地取得在 Blocklet 設定中設定的已啟用 OAuth 供應商列表。
- **`switchOAuthPassport(user)`**: 開啟一個對話方塊，用於在與同一 OAuth 身分相關聯的不同使用者帳號 (passports) 之間切換。

---

## Passkeys (無密碼)

使用基於 WebAuthn 標準的 Passkeys，啟用現代、安全且無密碼的驗證。這讓使用者可以使用生物辨識 (指紋、臉部 ID)、裝置 PIN 碼或安全金鑰登入。

### 1. 使用 `PasskeyProvider` 設定

使用 `PasskeyProvider` 包裹您的應用程式以啟用 Passkey 功能。

```jsx PasskeyProvider Setup icon=logos:react
import { SessionProvider } from '@arcblock/did-connect-react';
import { PasskeyProvider } from '@arcblock/did-connect-react/lib/Passkey';

function App() {
  return (
    <SessionProvider>
      <PasskeyProvider
        onAddPasskey={(result) => console.log('Passkey 已新增！', result)}
        onRemovePasskey={(result) => console.log('Passkey 已移除！', result)}>
        {/* 您的應用程式元件 */}
      </PasskeyProvider>
    </SessionProvider>
  );
}
```

**`PasskeyProvider` 的 Props**

<x-field-group>
  <x-field data-name="children" data-type="ReactNode" data-required="true" data-desc="將有權存取 Passkey context 的子元件。"></x-field>
  <x-field data-name="locale" data-type="string" data-default="en" data-desc="用於 UI 訊息和提示的語言。"></x-field>
  <x-field data-name="onAddPasskey" data-type="function" data-desc="使用者成功新增 Passkey 後觸發的回呼函式。"></x-field>
  <x-field data-name="onRemovePasskey" data-type="function" data-desc="使用者成功移除 Passkey 後觸發的回呼函式。"></x-field>
  <x-field data-name="onSwitchPassport" data-type="function" data-desc="使用者切換 passport 後觸發的回呼函式。"></x-field>
  <x-field data-name="session" data-type="object" data-desc="目前的使用者 session 物件，passport 切換 UI 需要。"></x-field>
</x-field-group>

### 2. 使用 `usePasskey` Hook

`usePasskey` hook 提供了 Passkey 註冊和驗證所需的所有函式。

```jsx usePasskey Hook icon=logos:react
import { usePasskey } from '@arcblock/did-connect-react/lib/Passkey';
import { useSession } from '@arcblock/did-connect-react';

function PasskeyAuth() {
  const { loginPasskey, connectPasskey, connecting } = usePasskey();
  const { session } = useSession();

  const handleLogin = async () => {
    try {
      const result = await loginPasskey();
      console.log('Passkey 登入成功：', result);
      // 處理登入結果
    } catch (error) {
      console.error('Passkey 登入失敗：', error.message);
    }
  };

  const handleConnect = async () => {
    // 這應該在使用者已經登入時呼叫
    // 並且想要在他們的個人資料設定中新增一個 passkey。
    if (!session.user) {
      alert('您必須登入才能新增 passkey。');
      return;
    }
    try {
      await connectPasskey();
    } catch (error) {
      console.error('連接 passkey 失敗：', error.message);
    }
  };

  return (
    <div>
      <button onClick={handleLogin}>使用 Passkey 登入</button>
      {session.user && (
        <button onClick={handleConnect} disabled={connecting}>
          {connecting ? '連接中...' : '新增 Passkey'}
        </button>
      )}
    </div>
  );
}
```

### 核心函式
- **`loginPasskey({ action, ... })`**: 啟動 Passkey 驗證流程 (也稱為 assertion)。這用於登入現有使用者。
- **`connectPasskey(extraParams)`**: 一個高階函式，用於建立一個新的 Passkey 並將其綁定到目前使用者的帳號。它處理完整的註冊儀式。
- **`disconnectPasskey({ session, connectedAccount })`**: 在成功驗證後，從目前使用者的帳號中移除一個 Passkey。
- **`createPasskey(params)`**: 啟動 WebAuthn 註冊過程的較低階函式。`connectPasskey` 在內部使用此函式。
- **`verifyPasskey(params)`**: 啟動 WebAuthn 驗證過程的較低階函式。`loginPasskey` 和 `disconnectPasskey` 在內部使用此函式。
- **`switchPassport(user)`**: 開啟一個對話方塊，用於在可能共享 Passkeys 的不同使用者帳號 (passports) 之間切換。

---

## 统一登录站点群登入

统一登录站点群登入允許一組關聯的 Blocklet 應用程式 (一個「统一登录站点群」) 共享單一登入 session。當使用者登入指定的「主」站點時，他們可以在任何「成員」站點上自動進行驗證，無需再次登入。

### 1. 概念與設定

此功能依賴於您 Blocklet 設定中的主從站點設定。需要 `FederatedProvider` 元件來處理此功能在現代瀏覽器中運作所需的跨網域通訊，特別是用於管理第三方 cookie 的存取。

與其他 provider 一樣，`FederatedProvider` 應包含在您的元件樹中，通常位於 `SessionProvider` 內部。

```jsx FederatedProvider Setup icon=logos:react
import { SessionProvider } from '@arcblock/did-connect-react';
import { FederatedProvider } from '@arcblock/did-connect-react/lib/Federated';

function App() {
  return (
    <SessionProvider>
      <FederatedProvider>
        {/* 您的應用程式元件 */}
      </FederatedProvider>
    </SessionProvider>
  );
}
```

### 2. 使用方法

统一登录站点群登入邏輯直接整合到 session 管理中。您可以使用 `useSession` 回傳的 `session` 物件上的 `loginFederated` 函式來觸發它。

`loginFederated` 支援兩種模式：
- **`auto`**: 如果使用者在主站點上有活動的 session，則嘗試靜默登入使用者。這非常適合在頁面載入時進行檢查。
- **`manual`**: 啟動標準的 DID Connect 登入流程。成功登入後，它也會與主站點建立统一登录站点群 session。

```jsx Federated Login Example icon=logos:react
import { useSession } from '@arcblock/did-connect-react';
import React from 'react';

function FederatedLogin() {
  const { session } = useSession();
  const { federatedEnabled, loginFederated } = session;

  // 在元件掛載時嘗試自動登入
  React.useEffect(() => {
    if (federatedEnabled) {
      loginFederated((err) => {
        if (err) {
          console.warn('自動统一登录站点群登入失敗：', err);
        } else {
          console.log('自動统一登录站点群登入成功！');
        }
      }, { mode: 'auto' });
    }
  }, [federatedEnabled, loginFederated]);

  const handleManualLogin = () => {
    loginFederated((err) => {
      if (err) console.error('手動统一登录站点群登入失敗：', err);
    }, { mode: 'manual' });
  };

  if (!federatedEnabled) {
    return <p>未啟用统一登录站点群登入。</p>;
  }

  return (
    <button onClick={handleManualLogin}>
      使用主站點帳號登入
    </button>
  );
}
```