Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Socialite Mastodon Laravel Package

revolution/socialite-mastodon

A Laravel Socialite provider for Mastodon OAuth2 authentication. Add Mastodon login to your Laravel app using Socialite's familiar API, with simple configuration and support for any Mastodon instance. Lightweight driver integrates seamlessly with existing Socialite setups for federated social sign-i...

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install the driver

composer require revolution/socialite-mastodon

The package ships a service provider that auto-registers (Laravel package discovery) and hooks a mastodon driver into Socialite. There is typically nothing to publish — confirm by reading src/ after install; the whole package is a ServiceProvider plus a Provider class, so it is a 10-minute read and worth doing before you build on it.

2. Register an OAuth app on the instance you'll authenticate against

Mastodon is federated, so credentials are per instance, not global. On e.g. https://mastodon.social go to Preferences → Development → New application, set the redirect URI to your callback URL (must match exactly, including scheme and trailing-slash behaviour), and request scopes read:accounts (add write/follow only if you actually post or follow).

3. Configure credentials in config/services.php:

'mastodon' => [
    'client_id'     => env('MASTODON_CLIENT_ID'),
    'client_secret' => env('MASTODON_CLIENT_SECRET'),
    'redirect'      => env('MASTODON_REDIRECT_URI'), // https://yourapp.test/auth/mastodon/callback
],

4. Routes

Route::get('/auth/mastodon/redirect', fn () => Socialite::driver('mastodon')
    ->setInstance(session('mastodon_instance'))
    ->redirect());

Route::get('/auth/mastodon/callback', function () {
    $user = Socialite::driver('mastodon')
        ->setInstance(session('mastodon_instance'))
        ->user();

    // $user->getId(), ->getNickname(), ->getName(), ->getAvatar()
});

setInstance() is the one API you must get right — it accepts the instance host (with or without scheme); everything else is standard Socialite. First use case: a "Sign in with Mastodon" button where the user first types their instance domain. If you only ever support one instance (your own community server, or mastodon.social), hard-code it in config and skip the picker entirely — that removes most of the complexity below.

Implementation Patterns

Instance-aware login flow. The instance is part of the auth request, so persist it before redirecting:

public function redirect(Request $request)
{
    $instance = Str::of($request->input('instance'))
        ->lower()->trim()->after('@')->trim('/')
        ->start('https://')->toString();

    $request->session()->put('mastodon_instance', $instance);

    return Socialite::driver('mastodon')->setInstance($instance)->redirect();
}

Validate the host first (filter_var + a DNS//api/v1/instance HEAD check) — otherwise you're making server-side requests to attacker-supplied URLs.

Persist users on a composite key. Mastodon account IDs are only unique within an instance. Your users table needs mastodon_instance + mastodon_id as a unique pair; a bare provider_id column will collide across servers.

User::updateOrCreate(
    ['mastodon_instance' => $instance, 'mastodon_id' => $social->getId()],
    ['name' => $social->getName() ?? $social->getNickname(),
     'handle' => $social->getNickname(), // acct, e.g. "alice" or "[email protected]"
     'avatar' => $social->getAvatar()]
);

Credential strategy — three tiers. Pick one deliberately:

  • Single instance: static client_id/client_secret in services.php. Simplest, ship this first.
  • Curated list: pre-register apps on 3–5 popular instances, store credentials in a table keyed by host, fall back to an error message for unknown hosts.
  • Dynamic registration: POST {instance}/api/v1/apps with client_name, redirect_uris, scopes, then cache the returned client_id/client_secret per host. This is the only path that truly works for arbitrary instances, and it is not something you should assume the package does — implement it in your own service class and feed the credentials to the driver.

Scopes. Mastodon has no email scope because the API never returns an email address. Request read:accounts for the profile and be explicit — don't rely on the package default.

API access after login. Store the returned access_token (Mastodon tokens don't expire by default but are revocable). For server-to-server calls use Laravel's HTTP client with withToken() against /api/v1/accounts/verify_credentials rather than round-tripping through Socialite; userFromToken() may be supported but is worth testing before you depend on it.

Testing. Never hit a real instance in CI. Fake the Socialite driver:

Socialite::shouldReceive('driver->setInstance->user')->andReturn(
    (new \Laravel\Socialite\Two\User)->map(['id' => 1, 'nickname' => 'alice', 'name' => 'Alice'])
);

For your own dynamic-registration code, Http::fake(['*/api/v1/apps' => Http::response([...])]).

Gotchas and Tips

  • getEmail() returns null. This breaks Breeze/Jetstream out of the box, and email is NOT NULL UNIQUE in the default migration. Decide up front: prompt for an email after OAuth, or generate a placeholder like {instance}+{id}@users.noreply.invalid. Do not trust a fake-looking address to be deliverable.
  • 401 Unauthorized from /oauth/token almost always means instance/credential mismatch — you registered the app on mastodon.social but the user typed fosstodon.org. Log both the instance and the client_id in the failure path; otherwise this is a miserable bug to chase.
  • Normalize instance input aggressively. Users paste @[email protected], mastodon.social/, https://Mastodon.Social. Strip the leading @user@, trim slashes, lowercase the host, force https. Store the normalized form and compare on it.
  • Redirect URI is byte-exact. A mismatch produces Mastodon's generic error page, not a helpful API error. Register both http://localhost:8000/... (dev) and your production URI on the same app.
  • State/session fragility. Multi-tab logins on different instances will clobber a single session key — use a short-lived signed token in the callback query string, or key the session by a nonce you also put in the redirect state.
  • InvalidStateException after the callback usually means an expired session, a cached config, or a proxy stripping cookies. Catch it and redirect to the login page rather than 500ing.
  • Cache/serialization quirks. If you cache the driver instance across requests, setInstance state leaks between users — build the driver fresh inside each request.
  • Maintenance risk is real but bounded. 13 stars and zero dependents means no safety net. Pin the exact version in composer.json, and know that the fallback is cheap: a Mastodon Socialite provider is roughly getAuthUrl(), getTokenUrl(), getUserByToken() against /api/v1/accounts/verify_credentials, plus mapUserToObject(). Budget half a day to vendor the class into app/Socialite/ if the package ever stalls — you'll likely need to subclass it anyway for custom user mapping, which is the cleanest extension point (override mapUserToObject() or getUserByToken() rather than editing vendor code).
  • Rate limits. Mastodon allows ~300 requests per 5 minutes per account. Cache profile data; do not re-fetch verify_credentials on every page load.
  • Debugging tip: dump the raw ArrayAccess payload before mapping — Mastodon's verify_credentials returns fields Socialite's default mapper drops (note, followers_count, bot, created_at) that you may want for a richer profile.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource