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é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
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.
- Connexion Socialite —
atproto, account:email, include:app.bsky.authViewAll pour activer l’authentification utilisateur et l’accès à l’e-mail
- Publication —
include:app.bsky.authCreatePosts et blob:*/* pour permettre la création de publications et l’upload d’images/vidéos
- 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.
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');
}
}
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.
/** @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();
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');
})