Passer au contenu principal

Vue d’ensemble

L’OAuth de Bluesky repose sur AT Protocol et diffère considérablement des fournisseurs Socialite classiques comme GitHub ou Google.
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.

Différences avec l’OAuth classique

ÉlémentSocialite classiqueBluesky Socialite
Méthode d’authentificationOAuth 2.0AT Protocol OAuth (DPoP)
Identification du clientclient_id + client_secretClé privée + URL de client-metadata.json
client_idChaîne fixe enregistréeURL de client-metadata.json
Identifiant utilisateurAdresse e-mail, etc.DID (did:plc:...) ou handle
Indice de connexionNon requisHandle / DID / URL de PDS

Flux d’authentification

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.
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.
BLUESKY_OAUTH_PRIVATE_KEY="..."
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.

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 Socialiteatproto, account:email, include:app.bsky.authViewAll pour activer l’authentification utilisateur et l’accès à l’e-mail
  2. Publicationinclude:app.bsky.authCreatePosts et blob:*/* pour permettre la création de publications et l’upload d’images/vidéos
  3. Notifications DMrpc: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.
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.

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.
# À 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.
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.
// 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.
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

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éthodeDescriptionExemple
did()DID Blueskydid:plc:xxxxxx
handle()Handle Blueskyalice.bsky.social
displayName()Nom d’affichageAlice
avatar()URL de l’avatarhttps://...
issuer()URL du PDShttps://bsky.social
token()Token d’accès
refresh()Token de rafraîchissement
Utilisez toArray() pour consulter toutes les propriétés.
/** @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.
// 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.
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.
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.
php artisan make:listener OAuthSessionUpdatedListener
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.
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().
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,
        ]);
    }
}
$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.
// 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.
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->redirectGuestsTo('/bluesky/login');
})
Dernière modification le 13 juillet 2026