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

# Socialite - Laravel Bluesky

> Autenticación OAuth de Laravel Bluesky. Configuración y uso de Socialite con los endpoints DPoP y PAR propios de AT Protocol.

## Descripción general

El OAuth de Bluesky se basa en AT Protocol y difiere considerablemente de los proveedores habituales de Socialite como GitHub o Google.

<Warning>
  La implementación del OAuth de Bluesky es fundamentalmente distinta a la de otros proveedores de Socialite. Usa DPoP (Demonstrated Proof of Possession) y el endpoint PAR (Pushed Authorization Requests). No se necesita `client_secret`; en su lugar se utiliza una clave privada.
</Warning>

### Diferencias con OAuth convencional

| Elemento                   | Socialite convencional        | Bluesky Socialite                             |
| -------------------------- | ----------------------------- | --------------------------------------------- |
| Método de autenticación    | OAuth 2.0                     | AT Protocol OAuth (DPoP)                      |
| Identificación del cliente | `client_id` + `client_secret` | Clave privada + URL de `client-metadata.json` |
| `client_id`                | Cadena fija registrada        | URL de `client-metadata.json`                 |
| Identificador de usuario   | Correo electrónico, etc.      | DID (`did:plc:...`) o handle                  |
| Login hint                 | No necesario                  | Se puede pasar handle / DID / URL del PDS     |

### Flujo de autenticación

```mermaid theme={null}
sequenceDiagram
    participant User as Usuario
    participant App as Aplicación<br>Laravel
    participant Bluesky as Servidor<br>de Bluesky

    User->>App: Envía el formulario de login (introduce el handle)
    App->>Bluesky: Petición PAR (incluye login_hint)
    Bluesky-->>App: request_uri
    App->>Bluesky: Redirige al endpoint de autorización
    User->>Bluesky: Aprueba el login en Bluesky
    Bluesky-->>App: Callback (code + iss)
    App->>Bluesky: Intercambio de tokens usando DPoP
    Bluesky-->>App: Token de acceso + OAuthSession
    App->>User: Login completado
```

## Instalación y configuración

### Crear la clave privada

Genera primero la clave privada. Este paso se puede hacer sin registrar nada en Bluesky.

```bash theme={null}
php artisan bluesky:new-private-key
```

```
Please set this private key in .env

BLUESKY_OAUTH_PRIVATE_KEY="...url-safe base64 encoded key..."
```

Copia el valor generado en `.env`.

```dotenv theme={null}
BLUESKY_OAUTH_PRIVATE_KEY="..."
```

<Info>
  En Bluesky no es necesario registrar `client_id` ni `client_secret`. Basta con configurar la clave privada para poder usar la autenticación OAuth.
</Info>

### Scopes OAuth por defecto

El paquete se configura con scopes OAuth por defecto que cubren tres casos de uso principales.

1. **Login con Socialite** — con `atproto`, `account:email` e `include:app.bsky.authViewAll` se habilita la autenticación del usuario y el acceso al correo.
2. **Publicaciones** — con `include:app.bsky.authCreatePosts` y `blob:*/*` se permite crear publicaciones y subir imágenes/vídeos.
3. **Notificaciones DM** — con `rpc:chat.bsky.convo.sendMessage` y `rpc:chat.bsky.convo.getConvoForMembers` se habilita el envío de DMs para notificaciones.

Puedes personalizar los scopes configurando la variable de entorno `BLUESKY_OAUTH_SCOPE`.

```dotenv theme={null}
BLUESKY_OAUTH_SCOPE="atproto account:email include:app.bsky.authViewAll"
```

Para más detalles sobre los scopes disponibles, consulta la [documentación de AT Protocol Permission Requests](https://atproto.com/guides/permission-requests).

### Desarrollo local

Como por defecto ya están configurados `http://localhost` y `http://127.0.0.1:8000/`, no se necesita configuración adicional para desarrollo local.

```dotenv theme={null}
# Configura solo si has cambiado el puerto
BLUESKY_CLIENT_ID=
BLUESKY_REDIRECT=http://127.0.0.1:8080/
```

### Entorno de producción

Si existe la ruta con el nombre `bluesky.oauth.redirect`, no hace falta configurar `.env`. Si has cambiado el nombre de la ruta por defecto, configúralo aquí.

```dotenv theme={null}
BLUESKY_REDIRECT=/bluesky/callback
```

## Configuración de rutas

Se recomienda que el nombre de la ruta de callback sea `bluesky.oauth.redirect`. El paquete utiliza internamente este nombre.

```php theme={null}
// routes/web.php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\SocialiteController;

Route::get('login', [SocialiteController::class, 'login'])->name('login');
Route::match(['get', 'post'], 'redirect', [SocialiteController::class, 'redirect']);
Route::get('bluesky/callback', [SocialiteController::class, 'callback'])
     ->name('bluesky.oauth.redirect');
```

### Gestión del callback en desarrollo local

Durante el desarrollo local, la URL de callback de Bluesky se fija en `http://127.0.0.1:8000/`. Es cómodo redirigir a nivel de ruta.

```php theme={null}
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::get('/', function (Request $request) {
    if (app()->isLocal() && $request->has('iss')) {
        return to_route('bluesky.oauth.redirect', $request->query());
    }

    // ...
});
```

## Implementación del controlador

```php theme={null}
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;
use Revolution\Bluesky\Session\OAuthSession;

class SocialiteController extends Controller
{
    public function login(Request $request)
    {
        // Página del formulario donde se introduce el login hint
        return view('login');
    }

    public function redirect(Request $request)
    {
        // Puedes pasar el handle, DID o URL del PDS como login_hint (opcional)
        $hint = $request->input('login_hint');
        $request->session()->put('hint', $hint);

        return Socialite::driver('bluesky')
                        ->hint($hint)
                        ->redirect();
    }

    public function callback(Request $request)
    {
        if ($request->missing('code')) {
            // Depuración durante desarrollo. En producción usa un manejo de errores adecuado
            dd($request);
        }

        $hint = $request->session()->pull('hint');

        /** @var \Laravel\Socialite\Two\User $user */
        $user = Socialite::driver('bluesky')
                         ->hint($hint)
                         ->user();

        /** @var OAuthSession $session */
        $session = $user->session;

        // Guarda el OAuthSession en la sesión de Laravel
        $request->session()->put('bluesky_session', $session->toArray());

        $loginUser = User::updateOrCreate([
            'did' => $session->did(),
        ], [
            'iss'           => $session->issuer(),
            'handle'        => $session->handle(),
            'name'          => $session->displayName(),
            'avatar'        => $session->avatar(),
            'access_token'  => $session->token(),
            'refresh_token' => $session->refresh(),
        ]);

        auth()->login($loginUser, true);

        return to_route('bluesky.dashboard');
    }
}
```

## Información del usuario (OAuthSession)

Estos son los principales métodos del `OAuthSession` que puedes obtener desde `$user->session`.

| Método          | Descripción       | Ejemplo               |
| --------------- | ----------------- | --------------------- |
| `did()`         | DID de Bluesky    | `did:plc:xxxxxx`      |
| `handle()`      | Handle de Bluesky | `alice.bsky.social`   |
| `displayName()` | Nombre mostrado   | `Alice`               |
| `avatar()`      | URL del avatar    | `https://...`         |
| `issuer()`      | URL del PDS       | `https://bsky.social` |
| `token()`       | Token de acceso   |                       |
| `refresh()`     | Refresh token     |                       |

Para ver todas las propiedades, usa `toArray()`.

```php theme={null}
/** @var OAuthSession $session */
dump($session->toArray());
```

## Configuración de base de datos

Añade a la tabla `users` las columnas específicas de Bluesky. El DID es el identificador único del usuario en Bluesky.

```php theme={null}
// database/migrations/xxxx_add_bluesky_columns_to_users_table.php

Schema::table('users', function (Blueprint $table) {
    $table->string('did')->nullable()->unique();
    $table->string('iss')->nullable();
    $table->string('handle')->nullable();
    $table->string('avatar')->nullable();
    $table->text('access_token')->nullable();
    $table->text('refresh_token')->nullable();
});
```

## Reutilización de OAuthSession

Puedes llamar a las APIs usando el OAuthSession guardado en la sesión.

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

$session = OAuthSession::create(session('bluesky_session'));

$timeline = Bluesky::withToken($session)->getTimeline();
```

En contextos donde la sesión de Laravel no está disponible, como Jobs o Console, construye el OAuthSession con datos obtenidos de la BD.

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

$session = OAuthSession::create([
    'did'           => $user->did,
    'refresh_token' => $user->refresh_token,
    // Para cuentas que no son de bsky.social, indica también iss
    // 'iss'        => $user->iss,
]);

$timeline = Bluesky::withToken($session)
                   ->refreshSession()
                   ->getTimeline();
```

## Refresco automático de tokens

Como el refresh token solo se puede usar una vez, tras cada refresco es imprescindible volver a guardarlo en la BD. Se utiliza el evento `OAuthSessionUpdated`.

```bash theme={null}
php artisan make:listener OAuthSessionUpdatedListener
```

```php theme={null}
namespace App\Listeners;

use App\Models\User;
use Revolution\Bluesky\Events\OAuthSessionUpdated;

class OAuthSessionUpdatedListener
{
    public function handle(OAuthSessionUpdated $event): void
    {
        if (empty($event->session->did())) {
            return;
        }

        session()->put('bluesky_session', $event->session->toArray());

        User::firstWhere('did', $event->session->did())
            ->fill([
                'iss'           => $event->session->issuer(),
                'handle'        => $event->session->handle(),
                'name'          => $event->session->displayName(),
                'avatar'        => $event->session->avatar(),
                'access_token'  => $event->session->token(),
                'refresh_token' => $event->session->refresh(),
            ])->save();
    }
}
```

Al iniciar el refresco también se emite el evento `OAuthSessionRefreshing`. En ese momento el `refresh_token` ya no es válido, así que es más seguro eliminarlo de la BD.

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

public function handle(OAuthSessionRefreshing $event): void
{
    if (empty($event->session->did())) {
        return;
    }

    User::firstWhere('did', $event->session->did())
        ->fill(['refresh_token' => ''])
        ->save();
}
```

## Trait WithBluesky

Si añades el trait `WithBluesky` al modelo User e implementas `tokenForBluesky()`, puedes obtener un cliente autenticado con `$user->bluesky()`.

```php theme={null}
namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Revolution\Bluesky\Session\OAuthSession;
use Revolution\Bluesky\Traits\WithBluesky;

class User extends Authenticatable
{
    use WithBluesky;

    protected function tokenForBluesky(): OAuthSession
    {
        return OAuthSession::create([
            'did'           => $this->did,
            'refresh_token' => $this->refresh_token,
            'iss'           => $this->iss,
        ]);
    }
}
```

```php theme={null}
$profile = auth()->user()
                 ->bluesky()
                 ->refreshSession()
                 ->getProfile();
```

## Personalizar client-metadata

El paquete define automáticamente las rutas `bluesky.oauth.client-metadata` y `bluesky.oauth.jwks`. Normalmente no hace falta cambiar nada, pero puedes personalizarlas con `OAuthConfig`.

```php theme={null}
// AppServiceProvider

use Revolution\Bluesky\Socialite\OAuthConfig;

public function boot(): void
{
    OAuthConfig::clientMetadataUsing(function () {
        return collect(config('bluesky.oauth.metadata'))
            ->merge([
                'client_id'    => route('bluesky.oauth.client-metadata'),
                'jwks_uri'     => route('bluesky.oauth.jwks'),
                'redirect_uris' => [url('bluesky/callback')],
            ])
            ->reject(fn ($item) => is_null($item))
            ->toArray();
    });
}
```

## Comportamiento sin autenticación

Si el `OAuthSession` es null o no hay refresh token, se lanza la excepción `Unauthenticated` y se redirige a la ruta `login`.

```php theme={null}
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->redirectGuestsTo('/bluesky/login');
})
```

<Info>
  Source: [docs/socialite.md](https://github.com/invokable/laravel-bluesky/blob/main/docs/socialite.md)
</Info>


## Related topics

- [Laravel Bluesky](/es/packages/laravel-bluesky/index.md)
- [Laravel Socialite (autenticación social)](/es/socialite.md)
- [Basic client - Laravel Bluesky](/es/packages/laravel-bluesky/basic-client.md)
- [Tutorial de bots - Laravel Bluesky](/es/packages/laravel-bluesky/bot-tutorial.md)
- [Comparativa de métodos de autenticación - Laravel Bluesky](/es/packages/laravel-bluesky/authentication.md)
