> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 認證方式比較 - Laravel Bluesky

> 說明 Laravel Bluesky 的 App Password (LegacyAgent) 與 OAuth (OAuthAgent) 的差異、內部架構與程式碼範例。

## 概觀

Laravel Bluesky 支援兩種認證方式。認證後的 API 呼叫在兩種方式下皆相同。

| 項目        | App Password                    | OAuth                              |
| --------- | ------------------------------- | ---------------------------------- |
| 內部類別      | `LegacyAgent` / `LegacySession` | `OAuthAgent` / `OAuthSession`      |
| 進入點       | `Bluesky::login()`              | `Bluesky::withToken(OAuthSession)` |
| 私鑰        | 不需要                             | 需要 `BLUESKY_OAUTH_PRIVATE_KEY`     |
| 使用者授權操作   | 不需要                             | 需要（在瀏覽器中同意）                        |
| 背景執行      | ✅ 擅長                            | ✅ 可以（需儲存 refresh\_token）           |
| Session 鍵 | `accessJwt` / `refreshJwt`      | `access_token` / `refresh_token`   |
| 是否廢止      | **無**                           | —                                  |
| 主要用途      | 自動貼文、通知、批次處理                    | 使用者代理操作、Socialite 整合               |

<Info>
  `LegacyAgent` 的命名意指「OAuth 之前的認證方式」，但 App Password 本身並未被廢止。在通知或自動貼文等不需使用者操作的情境中，App Password 更為簡單且適用。
</Info>

## 架構

```mermaid theme={null}
classDiagram
    class BlueskyManager {
        +login(identifier, password) Factory
        +withToken(session) Factory
        +agent() Agent
        +check() bool
        +refreshSession() Factory
    }
    class LegacyAgent {
        +session LegacySession
        +http() PendingRequest
        +refreshSession() self
    }
    class OAuthAgent {
        +session OAuthSession
        +http() PendingRequest
        +refreshSession() self
    }
    class LegacySession {
        +accessJwt
        +refreshJwt
        +did
        +handle
        +token() string
        +refresh() string
    }
    class OAuthSession {
        +access_token
        +refresh_token
        +did
        +iss
        +token() string
        +refresh() string
        +issuer() string
    }

    BlueskyManager --> LegacyAgent : login()
    BlueskyManager --> OAuthAgent : withToken(OAuthSession)
    BlueskyManager --> LegacyAgent : withToken(LegacySession)
    LegacyAgent --> LegacySession : holds
    OAuthAgent --> OAuthSession : holds
```

`BlueskyManager` 是 Facade `Bluesky` 的實體。使用 `login()` 或 `withToken()` 設定 agent 後，兩種認證方式後續的 API 呼叫都使用相同的方法。

## App Password (LegacyAgent)

### 認證流程

```mermaid theme={null}
sequenceDiagram
    participant App as Laravel 應用程式
    participant Bluesky as Bluesky 伺服器

    App->>Bluesky: createSession(identifier, password)
    Bluesky-->>App: LegacySession (accessJwt + refreshJwt)
    App->>App: LegacyAgent::create(session)
    App->>Bluesky: API 呼叫 (Bearer accessJwt)
    Bluesky-->>App: 回應
    Note over App: accessJwt 過期時
    App->>Bluesky: refreshSession (refreshJwt)
    Bluesky-->>App: 新的 accessJwt + refreshJwt
```

### Bluesky::login()

只要在 `.env` 中設定 App Password 並呼叫 `login()` 即可。

```dotenv theme={null}
BLUESKY_IDENTIFIER=your-handle.bsky.social
BLUESKY_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx
```

```php theme={null}
use Revolution\Bluesky\Facades\Bluesky;

$response = Bluesky::login(
    identifier: config('bluesky.identifier'),
    password: config('bluesky.password'),
)->post('Hello Bluesky');
```

### LegacySession 的重複使用

每次呼叫 `login()` 都會發出 API 請求。將 session 快取後重複使用會更有效率。

```php theme={null}
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Session\LegacySession;

// 首次登入並儲存 session
Bluesky::login(
    identifier: config('bluesky.identifier'),
    password: config('bluesky.password'),
);
cache()->put('bluesky_session', Bluesky::agent()->session()->toArray(), now()->addDay());

// 之後從快取還原
$session = LegacySession::create(cache('bluesky_session', []));
Bluesky::withToken($session);

// 若 access token 已過期則更新
if (! Bluesky::check()) {
    Bluesky::refreshSession();
}

$response = Bluesky::post('Hello from cached session');
```

### LegacySession 的主要鍵

| 鍵            | 內容            | 方法          |
| ------------ | ------------- | ----------- |
| `accessJwt`  | access token  | `token()`   |
| `refreshJwt` | refresh token | `refresh()` |
| `did`        | Bluesky DID   | `did()`     |
| `handle`     | handle        | `handle()`  |
| `email`      | email 位址      | `email()`   |
| `active`     | 帳號是否為有效狀態     | `active()`  |

### 適合的使用情境

* 於背景 job、佇列處理中的自動貼文
* 使用 Laravel Notification 頻道發送通知
* 批次處理、排程
* 以應用程式自身的帳號進行貼文

## OAuth (OAuthAgent)

### 認證流程

```mermaid theme={null}
sequenceDiagram
    participant User as 使用者
    participant App as Laravel 應用程式
    participant Bluesky as Bluesky 伺服器

    User->>App: 送出登入表單（輸入 handle）
    App->>Bluesky: PAR 請求（含 login_hint）
    Bluesky-->>App: request_uri
    App->>Bluesky: 重新導向至授權端點
    User->>Bluesky: 於 Bluesky 完成登入授權
    Bluesky-->>App: callback（code + iss）
    App->>Bluesky: 以 DPoP 進行 token 交換
    Bluesky-->>App: OAuthSession (access_token + refresh_token)
    App->>User: 登入完成
    App->>Bluesky: API 呼叫 (DPoP access_token)
    Bluesky-->>App: 回應
```

### Bluesky::withToken()

將透過 Socialite 取得的 `OAuthSession` 傳入 `withToken()`。

```php theme={null}
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Session\OAuthSession;

// 從 Laravel session 還原（Web 請求）
$session = OAuthSession::create(session('bluesky_session'));
$timeline = Bluesky::withToken($session)->getTimeline();
```

在背景 job 或 Console 中，也可以從 DB 儲存的值組合出 `OAuthSession`。

```php theme={null}
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Session\OAuthSession;

$session = OAuthSession::create([
    'did'           => $user->did,
    'refresh_token' => $user->refresh_token,
    // 非 bsky.social 帳號時需要指定 iss
    // 'iss'        => $user->iss,
]);

$response = Bluesky::withToken($session)
                   ->refreshSession()
                   ->post('Hello from OAuth');
```

### OAuthSession 的主要鍵

| 鍵                     | 內容                    | 方法              |
| --------------------- | --------------------- | --------------- |
| `access_token`        | access token          | `token()`       |
| `refresh_token`       | refresh token（只能使用一次） | `refresh()`     |
| `did` / `sub`         | Bluesky DID           | `did()`         |
| `iss`                 | 授權伺服器 URL             | `issuer()`      |
| `profile.handle`      | handle                | `handle()`      |
| `profile.displayName` | 顯示名稱                  | `displayName()` |

<Warning>
  OAuth 的 refresh\_token 只能使用一次。請透過 `OAuthSessionUpdated` 事件，在 token 更新後務必更新 DB。詳情請參考 [Socialite](/zh-TW/packages/laravel-bluesky/socialite)。
</Warning>

### 適合的使用情境

* 使用 Socialite 的使用者登入
* 代替使用者呼叫 API 的操作
* 每位使用者需以不同帳號操作的情境

## 認證後 API 呼叫皆共通

無論採用哪種認證方式，`withToken()` 之後都使用相同的 API 方法。

```php theme={null}
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Session\LegacySession;
use Revolution\Bluesky\Session\OAuthSession;

// App Password
Bluesky::login(config('bluesky.identifier'), config('bluesky.password'));

// 或 OAuth
$session = OAuthSession::create(session('bluesky_session'));
Bluesky::withToken($session);

// ↓ 之後的 API 呼叫完全一致 ↓

Bluesky::post('Hello Bluesky');
Bluesky::getTimeline();
Bluesky::getProfile();
Bluesky::searchPosts(q: '#laravel');
```

`BlueskyManager` 會在內部區分使用 `LegacyAgent` 或 `OAuthAgent`，但對呼叫端的程式碼並無影響。

## 該選哪一個？

```mermaid theme={null}
flowchart TD
    A["要使用哪種認證方式？"] --> B{"使用者是否需以 Bluesky 帳號<br>進行登入？"}
    B -->|"是"| C["OAuth (OAuthAgent)<br>Socialite 整合"]
    B -->|"否"| D{"以應用程式自身帳號<br>操作嗎？"}
    D -->|"是"| E["App Password (LegacyAgent)<br>簡單好維運"]
    D -->|"需要多位使用者的代理操作"| C
```

| 情境         | 推薦           |
| ---------- | ------------ |
| 自動貼文、通知、批次 | App Password |
| 使用者登入功能    | OAuth        |
| 僅背景 job    | App Password |
| 需要使用者代理操作  | OAuth        |
| 以運維簡便為優先   | App Password |
| 重視安全性、權限控管 | OAuth        |

<Tip>
  猶豫時依「目的」區分最易理解。若「應用程式自行運作」則選 App Password；若「使用者進行操作」則選 OAuth。也常見兩者結合的架構，例如通知使用 App Password，使用者登入則使用 OAuth。
</Tip>

## 參考連結

* [Basic client](/zh-TW/packages/laravel-bluesky/basic-client) — 認證後的 API 操作
* [Socialite](/zh-TW/packages/laravel-bluesky/socialite) — OAuth 流程的詳細內容
* [通知頻道](/zh-TW/packages/laravel-bluesky/notification) — 使用 App Password / OAuth 的通知
* Source：[invokable/laravel-bluesky](https://github.com/invokable/laravel-bluesky)


## Related topics

- [BlueskyManager 與 HasShortHand](/zh-TW/packages/laravel-bluesky/bluesky-manager.md)
- [Basic client - Laravel Bluesky](/zh-TW/packages/laravel-bluesky/basic-client.md)
- [Socialite - Laravel Bluesky](/zh-TW/packages/laravel-bluesky/socialite.md)
- [通知頻道 - Laravel Bluesky](/zh-TW/packages/laravel-bluesky/notification.md)
- [認證 - GitHub Copilot SDK for Laravel](/zh-TW/packages/laravel-copilot-sdk/auth.md)
