> ## 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

> Authentification OAuth de Laravel Bluesky. Configuration et utilisation de Socialite avec les endpoints DPoP et PAR spécifiques à AT Protocol.

## Vue d'ensemble

L'OAuth de Bluesky repose sur AT Protocol et diffère considérablement des fournisseurs Socialite classiques comme GitHub ou Google.

<Warning>
  L'implémentation OAuth de Bluesky est fondamentalement différente des autres fournisseurs Socialite. Elle utilise les endpoints DPoP (Demonstrated Proof of Possession) et PAR (Pushed Authorization Requests). Le `client_secret` n'est pas nécessaire ; une clé privée est utilisée à la place.
</Warning>

### Différences avec l'OAuth classique

| Élément                    | Socialite classique           | Bluesky Socialite                          |
| -------------------------- | ----------------------------- | ------------------------------------------ |
| Méthode d'authentification | OAuth 2.0                     | AT Protocol OAuth (DPoP)                   |
| Identification du client   | `client_id` + `client_secret` | Clé privée + URL de `client-metadata.json` |
| `client_id`                | Chaîne fixe enregistrée       | URL de `client-metadata.json`              |
| Identifiant utilisateur    | Adresse e-mail, etc.          | DID (`did:plc:...`) ou handle              |
| Indice de connexion        | Non requis                    | Handle / DID / URL de PDS                  |

### Flux d'authentification

```mermaid theme={null}
sequenceDiagram
    participant User as Utilisateur
    participant App as Application<br>Laravel
    participant Bluesky as Serveur<br>Bluesky

    User->>App: Envoi du formulaire de connexion (saisie du handle)
    App->>Bluesky: Requête PAR (incluant login_hint)
    Bluesky-->>App: request_uri
    App->>Bluesky: Redirection vers l'endpoint d'autorisation
    User->>Bluesky: Approbation de la connexion sur Bluesky
    Bluesky-->>App: Callback (code + iss)
    App->>Bluesky: Échange de token avec DPoP
    Bluesky-->>App: Token d'accès + OAuthSession
    App->>User: Connexion terminée
```

## Installation et configuration

### Création de la clé privée

Commencez par générer une clé privée. Cela peut se faire sans inscription auprès de 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..."
```

Copiez la valeur affichée dans votre fichier `.env`.

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

<Info>
  Pour Bluesky, aucune inscription de `client_id` ou `client_secret` n'est nécessaire. La simple configuration de la clé privée suffit pour utiliser l'authentification OAuth.
</Info>

### Portées OAuth par défaut

Le package est configuré avec des portées OAuth par défaut prenant en charge trois cas d'usage principaux.

1. **Connexion Socialite** — `atproto`, `account:email`, `include:app.bsky.authViewAll` pour activer l'authentification utilisateur et l'accès à l'e-mail
2. **Publication** — `include:app.bsky.authCreatePosts` et `blob:*/*` pour permettre la création de publications et l'upload d'images/vidéos
3. **Notifications DM** — `rpc:chat.bsky.convo.sendMessage` et `rpc:chat.bsky.convo.getConvoForMembers` pour activer l'envoi de messages directs à des fins de notification

Vous pouvez personnaliser les portées en définissant la variable d'environnement `BLUESKY_OAUTH_SCOPE`.

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

Pour plus de détails sur les portées disponibles, consultez la [documentation AT Protocol Permission Requests](https://atproto.com/guides/permission-requests).

### Développement local

Par défaut, `http://localhost` et `http://127.0.0.1:8000/` sont configurés, donc aucune configuration supplémentaire n'est nécessaire pour le développement local.

```dotenv theme={null}
# À configurer uniquement si vous modifiez le port
BLUESKY_CLIENT_ID=
BLUESKY_REDIRECT=http://127.0.0.1:8080/
```

### Environnement de production

Si la route nommée `bluesky.oauth.redirect` existe, aucune configuration dans `.env` n'est nécessaire. Configurez-la si vous avez modifié le nom de route par défaut.

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

## Configuration des routes

Il est recommandé de nommer la route de callback `bluesky.oauth.redirect`. Le package utilise ce nom en interne.

```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');
```

### Gestion du callback en développement local

Pendant le développement local, l'URL de callback de Bluesky est fixée à `http://127.0.0.1:8000/`. Il est pratique de faire le routage au niveau des routes.

```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());
    }

    // ...
});
```

## Implémentation du contrôleur

```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)
    {
        // Page du formulaire pour saisir l'indice de connexion
        return view('login');
    }

    public function redirect(Request $request)
    {
        // Vous pouvez passer un handle, DID ou URL de PDS comme login_hint (facultatif)
        $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')) {
            // Pour le débogage en développement. En production, remplacez par une gestion d'erreur appropriée
            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;

        // Enregistrer l'OAuthSession dans la session 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');
    }
}
```

## Informations utilisateur (OAuthSession)

Voici les principales méthodes de l'`OAuthSession` obtenues via `$user->session`.

| Méthode         | Description               | Exemple               |
| --------------- | ------------------------- | --------------------- |
| `did()`         | DID Bluesky               | `did:plc:xxxxxx`      |
| `handle()`      | Handle Bluesky            | `alice.bsky.social`   |
| `displayName()` | Nom d'affichage           | `Alice`               |
| `avatar()`      | URL de l'avatar           | `https://...`         |
| `issuer()`      | URL du PDS                | `https://bsky.social` |
| `token()`       | Token d'accès             |                       |
| `refresh()`     | Token de rafraîchissement |                       |

Utilisez `toArray()` pour consulter toutes les propriétés.

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

## Configuration de la base de données

Ajoutez les colonnes spécifiques à Bluesky à la table `users`. Le DID est l'identifiant unique de l'utilisateur 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();
});
```

## Réutilisation de l'OAuthSession

Vous pouvez appeler l'API en utilisant l'OAuthSession enregistrée en session.

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

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

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

Lorsque la session Laravel n'est pas disponible (Job, Console, etc.), récupérez les données depuis la base de données pour reconstituer l'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,
    // Pour les comptes hors bsky.social, spécifiez également iss
    // 'iss'        => $user->iss,
]);

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

## Rafraîchissement automatique du token

Le refresh token ne pouvant être utilisé qu'une seule fois, il faut impérativement le réenregistrer en base après un rafraîchissement. Utilisez l'événement `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();
    }
}
```

Au début du rafraîchissement, l'événement `OAuthSessionRefreshing` est également émis. À ce moment, le `refresh_token` devient invalide. Il est plus sûr de le supprimer de la base à cet instant.

```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 vous ajoutez le trait `WithBluesky` au modèle User et implémentez `tokenForBluesky()`, vous pouvez obtenir un client authentifié via `$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();
```

## Personnalisation de client-metadata

Le package définit automatiquement les routes `bluesky.oauth.client-metadata` et `bluesky.oauth.jwks`. Aucune modification n'est habituellement nécessaire, mais vous pouvez personnaliser via `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();
    });
}
```

## Comportement en cas de non-authentification

Si l'`OAuthSession` est null ou qu'il n'y a pas de refresh token, une exception `Unauthenticated` est levée et l'utilisateur est redirigé vers la route `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](/fr/packages/laravel-bluesky/index.md)
- [Tutoriel bot - Laravel Bluesky](/fr/packages/laravel-bluesky/bot-tutorial.md)
- [Client de base - Laravel Bluesky](/fr/packages/laravel-bluesky/basic-client.md)
- [Laravel Socialite (authentification sociale)](/fr/socialite.md)
- [Comparaison des méthodes d'authentification - Laravel Bluesky](/fr/packages/laravel-bluesky/authentication.md)
