socialiteproviders/twitter
Twitter/X OAuth provider for Laravel Socialite. Add Twitter login to your Laravel app with a drop-in driver supporting OAuth 1.0a and OAuth 2.0, letting users sign in with their Twitter accounts quickly and securely via the familiar Socialite API. Simple to install and configure.
This provider adds Twitter/X as an OAuth 1.0a driver to Laravel Socialite. It gives you the standard Socialite API (redirect(), user(), $user->getId(), etc.) while handling the OAuth 1.0a request-token / access-token dance that Twitter requires.
1. Install the package
composer require socialiteproviders/twitter
This pulls in socialiteproviders/manager, which is what registers the driver with Laravel Socialite.
2. Register the Socialite listener
Laravel 10 and earlier — in app/Providers/EventServiceProvider.php:
protected $listen = [
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
\SocialiteProviders\Twitter\TwitterExtendSocialite::class.'@handle',
],
];
Laravel 11+ — there is no EventServiceProvider by default, so register it in AppServiceProvider::boot():
use Illuminate\Support\Facades\Event;
use SocialiteProviders\Manager\SocialiteWasCalled;
use SocialiteProviders\Twitter\TwitterExtendSocialite;
public function boot(): void
{
Event::listen(SocialiteWasCalled::class, TwitterExtendSocialite::class.'@handle');
}
Without this listener you will get InvalidArgumentException: Driver [twitter] is not supported.
3. Configure credentials
In config/services.php:
'twitter' => [
'client_id' => env('TWITTER_CLIENT_ID'),
'client_secret' => env('TWITTER_CLIENT_SECRET'),
'redirect' => env('TWITTER_REDIRECT_URI'),
],
In .env:
TWITTER_CLIENT_ID=your_api_key
TWITTER_CLIENT_SECRET=your_api_secret
TWITTER_REDIRECT_URI="${APP_URL}/auth/twitter/callback"
client_id / client_secret are Socialite's generic names; for Twitter they are the API Key and API Secret from the X developer portal.
4. Create the X app
In the X developer portal, enable OAuth 1.0a under User authentication settings and add the exact callback URL. The callback must match TWITTER_REDIRECT_URI character-for-character (scheme, host, path, trailing slash).
5. Add routes and do the first login
// routes/web.php
Route::get('/auth/twitter/redirect', [TwitterAuthController::class, 'redirect']);
Route::get('/auth/twitter/callback', [TwitterAuthController::class, 'callback']);
public function redirect()
{
return Socialite::driver('twitter')->redirect();
}
public function callback(Request $request)
{
if ($request->has('denied')) {
return redirect('/login')->withErrors('Twitter login was cancelled.');
}
$twitterUser = Socialite::driver('twitter')->user();
$user = User::updateOrCreate(
['twitter_id' => $twitterUser->getId()],
[
'name' => $twitterUser->getName() ?? $twitterUser->getNickname(),
'twitter_nickname' => $twitterUser->getNickname(),
'twitter_avatar' => $twitterUser->getAvatar(),
]
);
Auth::login($user, true);
return redirect()->intended('/dashboard');
}
The first real use case is almost always “Sign in with X”: identify the user by getId(), persist the numeric string ID, and use the screen name/avatar for display.
Basic user mapping
The provider maps the account/verify_credentials response into a Socialite user:
| Method | Twitter field | Notes |
|---|---|---|
getId() |
id_str |
Always treat as string |
getNickname() |
screen_name |
Without @ |
getName() |
name |
Display name |
getEmail() |
email |
Often null — see Gotchas |
getAvatar() |
profile_image_url_https |
Usually _normal size |
Use $twitterUser->getRaw() when you need more than the standard fields:
$raw = $twitterUser->getRaw();
$followers = $raw['followers_count'] ?? 0;
$verified = $raw['verified'] ?? false;
$location = $raw['location'] ?? null;
Persist the OAuth 1.0a token pair if you need to act as the user
OAuth 1.0a issues two secrets per user: an access token and a token secret. Socialite exposes both:
$user->token; // oauth_token
$user->tokenSecret; // oauth_token_secret
Store them encrypted, because the pair grants API access as that user:
$user->forceFill([
'twitter_token' => encrypt($twitterUser->token),
'twitter_token_secret' => encrypt($twitterUser->tokenSecret),
])->save();
Do not store these in the session; they are long-lived credentials.
Calling the X API on behalf of a user
Socialite does not sign arbitrary Twitter requests for you after login. The simplest pattern is to hand the stored token pair to an OAuth 1.0a client such as abraham/twitteroauth:
$connection = new \Abraham\TwitterOAuth\TwitterOAuth(
config('services.twitter.client_id'),
config('services.twitter.client_secret'),
decrypt($user->twitter_token),
decrypt($user->twitter_token_secret')
);
$profile = $connection->get('account/verify_credentials');
$tweets = $connection->get('statuses/user_timeline', ['count' => 5]);
Keep this behind a service class so API-tier changes are isolated from your auth code.
Account linking instead of login
For “Connect your X account” flows, use the same redirect/callback pair but do not call Auth::login(). Instead resolve the currently authenticated user and attach the Twitter ID and token pair to that record. Guard against a Twitter account already being linked to another local user.
Customising the provider
Subclass the provider when you need different behaviour, then register the subclass instead of the package listener:
namespace App\Socialite;
use SocialiteProviders\Twitter\Provider;
class TwitterProvider extends Provider
{
// Force the consent screen every time instead of silently reusing authorisation.
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase('https://api.twitter.com/oauth/authorize', $state);
}
// Add extra fields or change ID handling.
protected function mapUserToObject(array $user)
{
return parent::mapUserToObject($user)->map([
'id' => $user['id_str'],
'nickname' => $user['screen_name'],
'name' => $user['name'],
'email' => $user['email'] ?? null,
'avatar' => $user['profile_image_url_https'] ?? null,
]);
}
}
Event::listen(SocialiteWasCalled::class, function ($event) {
$event->extendSocialite('twitter', \App\Socialite\TwitterProvider::class);
});
If you register both the package listener and your own, make sure yours runs last — Socialite's extend() overwrites the driver callback.
Testing
Socialite uses Guzzle internally, so Http::fake() will not intercept the OAuth calls. Mock the facade instead:
Socialite::shouldReceive('driver->user')
->once()
->andReturn((new \Laravel\Socialite\One\User())->map([
'id' => '1234567890',
'nickname' => 'testuser',
'name' => 'Test User',
'email' => null,
'avatar' => 'https://pbs.twimg.com/...',
]));
For the redirect side, assert that the response is a redirect to api.twitter.com/oauth/authenticate.
Driver naming
The package registers the driver as twitter. Config must live under services.twitter, and all calls must use the same lowercase key:
Socialite::driver('twitter');
Email is frequently unavailable. Twitter OAuth 1.0a only returns an email when the X app has email permission enabled and the user has a verified email. Even then, getEmail() can be null. Never make email non-nullable or use it as the primary lookup key. Fall back to asking the user, or use Twitter OAuth 2.0 separately if email is mandatory.
Use id_str, not id. Twitter IDs are large enough to cause precision problems in JavaScript and on 32-bit PHP builds. The provider already maps id_str; store it as a string or BIGINT UNSIGNED, never a signed INT.
Callback URL mismatch is the most common failure. OAuth 1.0a signs the callback URL into the request token. If TWITTER_REDIRECT_URI differs from the X app configuration — even by http vs https, localhost vs 127.0.0.1, or a trailing slash — you will see 401s or “Request token was not issued.” Behind a proxy or load balancer, make sure APP_URL and TrustProxies produce the public HTTPS URL.
OAuth 1.0a needs the session. The request-token secret is stored in the session between redirect and callback. The callback route must use the web middleware group. stateless() is an OAuth 2.0 concept and does not make OAuth 1.0a work for SPAs or token-only APIs — if you need stateless auth, use OAuth 2.0 PKCE instead.
Handle cancellation and replayed callbacks. When the user denies access, Twitter redirects with ?denied=.... Check for that before calling user(). If the user refreshes the callback URL, the state/session token is consumed and you will get InvalidStateException; catch it and restart the flow with a friendly message.
Clear config cache after changing credentials. php artisan config:clear after editing .env or services.php, especially in local development.
Avatar sizes. profile_image_url_https is normally the _normal (48×48) variant. Replace _normal with _400x400 for a usable avatar, and handle the default egg image if you care about presentation.
Twitter/X API tiers change frequently. The provider was last released in May 2023, and X has significantly changed API access since then. Login itself generally works, but user-context endpoints such as timelines or follower lists may require a paid tier. Cache profile data at login instead of calling verify_credentials on every request.
The split repo is read-only. socialiteproviders/twitter is a subtree split of SocialiteProviders/Providers. Issues and pull requests belong in the monorepo, not the split package. If you need a fix, check the monorepo first and consider a local provider subclass until a release lands.
Pin compatible versions. Because the package is older, verify it resolves cleanly against your laravel/socialite and socialiteproviders/manager versions. Run a full login flow in staging after any Socialite upgrade.
Encrypt token secrets and never log them. An OAuth 1.0a token secret is a bearer credential. Treat it like a password: encrypted at rest, masked in logs, and rotated/revoked when a user disconnects their account.
Debugging 401s. Check, in order: the API key/secret, that OAuth 1.0a is enabled in the X app, that the callback URL is whitelisted, and that the user has not revoked access. X error codes such as 32 (could not authenticate), 89 (invalid token), and 215 (bad authentication data) all point at these configuration issues.
How can I help you explore Laravel packages today?