Panoramica
L’OAuth di Bluesky è basato su AT Protocol e differisce sostanzialmente dai provider Socialite tipici come GitHub o Google.
L’implementazione dell’OAuth di Bluesky è fondamentalmente diversa da quella degli altri provider Socialite. Usa DPoP (Demonstrated Proof of Possession) e l’endpoint PAR (Pushed Authorization Requests). Non è necessario client_secret: si usa invece una chiave privata.
Differenze rispetto al normale OAuth
| Elemento | Socialite tradizionale | Bluesky Socialite |
|---|
| Metodo di autenticazione | OAuth 2.0 | AT Protocol OAuth (DPoP) |
| Identificazione del client | client_id + client_secret | Chiave privata + URL di client-metadata.json |
client_id | Stringa fissa registrata | URL di client-metadata.json |
| Identificatore dell’utente | Email, ecc. | DID (did:plc:...) o handle |
| Login hint | Non necessario | Puoi passare handle / DID / URL del PDS |
Flusso di autenticazione
Installazione e configurazione
Creazione della chiave privata
Prima genera la chiave privata. Puoi farlo senza registrarti su Bluesky.
php artisan bluesky:new-private-key
Please set this private key in .env
BLUESKY_OAUTH_PRIVATE_KEY="...url-safe base64 encoded key..."
Copia il valore emesso in .env.
BLUESKY_OAUTH_PRIVATE_KEY="..."
Con Bluesky non è necessario registrare client_id o client_secret. Puoi usare l’autenticazione OAuth con la sola configurazione della chiave privata.
Scope OAuth predefiniti
Il pacchetto è configurato con scope OAuth predefiniti che coprono tre casi d’uso principali.
- Login con Socialite — con
atproto, account:email e include:app.bsky.authViewAll abilita l’autenticazione utente e l’accesso all’email
- Post — con
include:app.bsky.authCreatePosts e blob:*/* consente la creazione di post e l’upload di immagini/video
- Notifiche DM — con
rpc:chat.bsky.convo.sendMessage e rpc:chat.bsky.convo.getConvoForMembers abilita l’invio di messaggi diretti per le notifiche
Puoi personalizzare gli scope impostando la variabile d’ambiente BLUESKY_OAUTH_SCOPE.
BLUESKY_OAUTH_SCOPE="atproto account:email include:app.bsky.authViewAll"
Per i dettagli degli scope disponibili consulta la documentazione AT Protocol Permission Requests.
Sviluppo locale
Per impostazione predefinita sono configurati http://localhost e http://127.0.0.1:8000/, quindi in sviluppo locale non serve nessuna configurazione aggiuntiva.
# Imposta solo se hai cambiato la porta
BLUESKY_CLIENT_ID=
BLUESKY_REDIRECT=http://127.0.0.1:8080/
Ambiente di produzione
Se esiste una rotta chiamata bluesky.oauth.redirect, non serve configurare .env. Configuralo solo se hai cambiato il nome della rotta predefinito.
BLUESKY_REDIRECT=/bluesky/callback
Configurazione delle rotte
Come nome della rotta di callback è consigliato bluesky.oauth.redirect. Il pacchetto usa questo nome internamente.
// 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');
Gestione del callback in sviluppo locale
Durante lo sviluppo locale, l’URL di callback da Bluesky è fisso su http://127.0.0.1:8000/. È comodo smistarlo a livello di rotta.
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());
}
// ...
});
Implementazione del controller
<?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)
{
// Pagina con il form per inserire il login hint
return view('login');
}
public function redirect(Request $request)
{
// Puoi passare handle, DID o URL del PDS come login_hint (opzionale)
$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')) {
// Per debug in sviluppo. In produzione sostituisci con una gestione errori adeguata
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;
// Salva l'OAuthSession nella sessione 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');
}
}
I metodi principali dell’OAuthSession ottenibile da $user->session sono i seguenti.
| Metodo | Descrizione | Esempio |
|---|
did() | DID Bluesky | did:plc:xxxxxx |
handle() | Handle Bluesky | alice.bsky.social |
displayName() | Nome visualizzato | Alice |
avatar() | URL dell’avatar | https://... |
issuer() | URL del PDS | https://bsky.social |
token() | Access token | |
refresh() | Refresh token | |
Per vedere tutte le proprietà usa toArray().
/** @var OAuthSession $session */
dump($session->toArray());
Configurazione del database
Aggiungi alla tabella users le colonne specifiche di Bluesky. Il DID è l’identificatore univoco dell’utente 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();
});
Riutilizzo di OAuthSession
Puoi invocare le API usando l’OAuthSession salvato in sessione.
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Session\OAuthSession;
$session = OAuthSession::create(session('bluesky_session'));
$timeline = Bluesky::withToken($session)->getTimeline();
Nei job o nella Console, dove la sessione Laravel non è disponibile, ricostruisci l’OAuthSession dai dati del DB.
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Session\OAuthSession;
$session = OAuthSession::create([
'did' => $user->did,
'refresh_token' => $user->refresh_token,
// Per gli account non su bsky.social specifica anche iss
// 'iss' => $user->iss,
]);
$timeline = Bluesky::withToken($session)
->refreshSession()
->getTimeline();
Aggiornamento automatico del token
Il refresh token può essere usato una sola volta, quindi dopo l’aggiornamento devi risalvarlo obbligatoriamente in DB. Usa l’evento 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();
}
}
All’inizio del refresh viene emesso anche l’evento OAuthSessionRefreshing. A quel punto il refresh_token diventa invalido, quindi conviene rimuoverlo dal DB per sicurezza.
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
Aggiungendo il trait WithBluesky al model User e implementando tokenForBluesky(), puoi ottenere un client autenticato tramite $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();
Il pacchetto definisce automaticamente le rotte bluesky.oauth.client-metadata e bluesky.oauth.jwks. Di norma non serve modificare nulla, ma puoi personalizzarle tramite 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();
});
}
Comportamento in assenza di autenticazione
Se OAuthSession è null o manca il refresh token, viene lanciata un’eccezione Unauthenticated e vieni reindirizzato alla rotta login.
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->redirectGuestsTo('/bluesky/login');
})