> ## 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）            |
| 会话 key | `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()`   |
| `active`     | 账号是否处于活跃状态    | `active()`  |

### 适用场景

* 后台任务、队列处理中的自动发帖
* 使用 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: 回调 (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();
```

在后台任务或 Console 中，也可以用保存在数据库里的值组装 `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 更新后一定更新数据库。详情请参考 [Socialite](/zh/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        |
| 只做后台任务      | App Password |
| 需要代表用户操作    | OAuth        |
| 优先考虑运营简单    | App Password |
| 重视安全性与权限控制  | OAuth        |

<Tip>
  纠结时，按目的划分最直观。“应用自己动作”就用 App Password，“用户来操作”就用 OAuth。两者也可以组合使用，比如通知用 App Password、用户登录用 OAuth 是常见的组合。
</Tip>

## 参考链接

* [Basic client](/zh/packages/laravel-bluesky/basic-client) —— 认证后的 API 操作
* [Socialite](/zh/packages/laravel-bluesky/socialite) —— OAuth 流程详解
* [通知通道](/zh/packages/laravel-bluesky/notification) —— 通过 App Password / OAuth 发送通知
* Source: [invokable/laravel-bluesky](https://github.com/invokable/laravel-bluesky)


## Related topics

- [Google Sheets API for Laravel](/zh/packages/laravel-google-sheets/index.md)
- [BlueskyManager 与 HasShortHand](/zh/packages/laravel-bluesky/bluesky-manager.md)
- [Basic client - Laravel Bluesky](/zh/packages/laravel-bluesky/basic-client.md)
- [通知通道 - Laravel Bluesky](/zh/packages/laravel-bluesky/notification.md)
- [Socialite - Laravel Bluesky](/zh/packages/laravel-bluesky/socialite.md)
