socialiteproviders/pinterest
Pinterest OAuth provider for Laravel Socialite. Lets your Laravel app authenticate users with Pinterest, handling redirects, callbacks, and user profile retrieval through the familiar Socialite API. Install via Composer and configure your Pinterest app credentials.
This is the monorepo of ~200 OAuth2/OIDC providers for Laravel Socialite — you almost never install "the package" as a whole. The workflow is: install Laravel Socialite, then install only the provider split package(s) you need, then register the shared manager service provider and its listener.
composer require laravel/socialite
composer require socialiteproviders/pinterest # one per provider
Register the manager provider and the SocialiteWasCalled listener. On Laravel 11/12 (bootstrap/providers.php + event discovery) the provider is auto-discovered, but the listener mapping is not — add it explicitly:
// app/Providers/AppServiceProvider.php (Laravel 11+)
use Illuminate\Support\Facades\Event;
use SocialiteProviders\Manager\SocialiteWasCalled;
public function boot(): void
{
Event::listen(SocialiteWasCalled::class, \SocialiteProviders\Pinterest\PinterestExtendSocialite::class);
}
On Laravel 10 and below, add SocialiteProviders\Manager\ServiceProvider::class to config/app.php providers and the SocialiteWasCalled listener to app/Providers/EventServiceProvider.php $listen.
Configure credentials in config/services.php — every provider reads from a key named after the driver:
'pinterest' => [
'client_id' => env('PINTEREST_CLIENT_ID'),
'client_secret' => env('PINTEREST_CLIENT_SECRET'),
'redirect' => env('PINTEREST_REDIRECT_URI'),
],
First use case — the canonical two-route flow:
Route::get('/auth/pinterest', fn () => Socialite::driver('pinterest')->redirect());
Route::get('/auth/pinterest/callback', function () {
$user = Socialite::driver('pinterest')->user();
// $user->getId(), ->getNickname(), ->getName(), ->getEmail(), ->getAvatar()
});
Look first at the provider's own README (src/{Provider}/README.md in the repo) — required scopes, extra config keys, and any non-standard getUserByToken behaviour are documented there, not in the root docs.
Store tokens, not just identity. $user->token, $user->refreshToken, $user->expiresIn are what you need later for API calls. Persist them on a social_accounts table keyed by (provider, provider_user_id) rather than columns on users — it keeps multi-provider accounts and re-linking clean.
Runtime/dynamic config for multi-tenant. Providers resolve credentials from config("services.$driver"), so you can swap tenants at request time:
config(['services.pinterest' => $tenant->oauthConfig()]);
return Socialite::driver('pinterest')->redirect();
Do this before the redirect and cache the tenant id in the session so the callback uses the same credentials.
Stateless and token-based flows. SPA/mobile backends should skip the session round-trip:
Socialite::driver('pinterest')->stateless()->redirect();
// and on callback
$user = Socialite::driver('pinterest')->stateless()->user();
For "Login with X" from a mobile SDK, use userFromToken($accessToken) where the provider supports it, falling back to Socialite::driver($d)->userFromTokenAndSecret(...).
Scopes and extra params. ->setScopes(['boards:read', 'pins:read']) and ->with(['state' => $custom]) both round-trip; read $request->input('state') in the callback for post-login redirects. Prefer setScopes() over with(['scope' => ...]) so the provider's default scopes still merge in.
Overriding a provider without forking. If you need a tweak (extra header, different endpoint), register a custom driver in a service provider instead of editing vendor code:
Socialite::extend('pinterest', fn ($app) => new MyPinterestProvider(
$app['request'], $app['config']['services.pinterest']['client_id'],
$app['config']['services.pinterest']['client_secret'],
$app['config']['services.pinterest']['redirect']
));
Testing. Socialite::shouldReceive('driver->user')->andReturn($socialiteUser) against a faked Laravel\Socialite\Two\User is the cheap path; keep provider-specific parsing (the mapUserToObject bit) covered by a unit test with a canned API payload — that is exactly the layer the 5.0.0 Pinterest change touched.
5.0.0 is a major bump with a one-line changelog. The only listed change is the Pinterest user-id fix, but a 4.x → 5.0.0 jump in this repo has historically come with raised php, illuminate/*, and socialiteproviders/manager constraints and dropped EOL Laravel versions. Treat the changelog as incomplete: read composer.json on the tag and run composer why-not socialiteproviders/providers 5.0.0 before upgrading. If you only install split packages (socialiteproviders/pinterest), the monorepo tag may not correspond to your installed version at all — check the split package's own tags.
Pinterest user IDs are now the stable account id — expect churn. If you already persist Pinterest identities, $user->getId() may return a different value than before for the same person, which silently creates a duplicate account on their next login. Before deploying: log the old and new IDs side by side for a week, or match on email and reconcile. Anyone who built a (provider, provider_user_id) unique index should plan a backfill, not rely on updateOrCreate alone.
Redirect URI mismatch is the #1 failure. The value in config/services.php must byte-match the app console (scheme, host, trailing slash, port). A mismatch throws a generic InvalidStateException/provider error page, not a clear message. Also remember php artisan config:cache — env changes after caching are ignored, and every cached-config bug looks like "credentials are wrong".
InvalidStateException after login is usually the session, not the code: stateless() on one leg but not the other, a callback hitting a different domain/subdomain than the redirect, or a session cookie dropped by SameSite during the cross-site POST-back. Verify your session driver is not array in the environment where it fails.
Missing scopes fail late. Many providers return a valid token with narrow scopes, and ->user() then throws a 403 from the profile endpoint. Pinterest, LinkedIn, and Twitter/X in particular need explicit scopes for anything beyond basic identity — the error surfaces deep in SocialiteProviders\Manager\OAuth2\AbstractProvider::getUserByToken, so wrap ->user() in a try/catch and log the provider's raw response body.
Token expiry and refresh. Not all providers implement refreshToken(). Check the provider class before designing a job that refreshes; where it exists, refreshToken($refreshToken) returns a fresh Laravel\Socialite\Two\User carrying the new token. Store expires_in as an absolute timestamp, not a duration.
Debugging. Socialite::driver($d)->user() swallows useful detail. Drop to ->getAccessTokenResponse($code) / ->getUserByToken($token) to see the raw provider payload, and remember some providers (Twitter/X) require PKCE handled by the provider — swapping in a hand-rolled flow breaks it.
Version-pin per provider. Because each provider ships independently, a single careless composer update can pull a provider major that changes the user object shape. Constrain the ones you depend on (socialiteproviders/pinterest:^4.0) if you are not ready for the 5.x line.
How can I help you explore Laravel packages today?