To add Login with X (formerly Twitter) to Laravel 13, install Socialite, create an app in the X developer portal, add its keys under the x key in config/services.php and write a redirect route and a callback route. A key detail: X does not always return an email address, so your user table and callback code must work without one. This tutorial shows the full flow.
Targets: Laravel 13 (PHP 8.3 to 8.5) and laravel/socialite. In current Socialite versions the driver for this network is named x.
Step 1: Install Socialite
composer require laravel/socialiteStep 2: Create an app in the X developer portal
- Sign in to the X developer portal and create a project and an app.
- Open the app's user authentication settings and enable OAuth 2.0.
- Choose the app type Web App (a confidential client).
- Add your callback URL, for example
http://localhost:8000/auth/x/callback. - Copy the Client ID and Client Secret.
Portal screens and plan names change often, so follow the labels you see. The callback URL must match your configuration exactly.
Step 3: Add the credentials
X_CLIENT_ID=your-client-id
X_CLIENT_SECRET=your-client-secret
X_REDIRECT_URI=http://localhost:8000/auth/x/callback'x' => [
'client_id' => env('X_CLIENT_ID'),
'client_secret' => env('X_CLIENT_SECRET'),
'redirect' => env('X_REDIRECT_URI'),
],Add the array to config/services.php.
Step 4: Prepare the users table
Because X users may have no email, the users.email column needs to allow null, or you must create a placeholder address. This tutorial makes the email nullable and stores the X ID separately:
php artisan make:migration add_x_id_to_users_table --table=userspublic function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('x_id')->nullable()->unique()->after('email');
$table->string('email')->nullable()->change();
$table->string('password')->nullable()->change();
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('x_id');
});
}Run php artisan migrate and add x_id to the $fillable list of the User model. Changing the email column to nullable can affect other parts of your app, such as password reset, so decide whether that is acceptable for your project.
Step 5: Write the controller
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use Throwable;
class XAuthController extends Controller
{
public function redirect(): RedirectResponse
{
return Socialite::driver('x')->redirect();
}
public function callback(): RedirectResponse
{
try {
$xUser = Socialite::driver('x')->user();
} catch (Throwable $e) {
report($e);
return redirect('/login')->withErrors(['email' => 'Login with X failed. Please try again.']);
}
$user = User::updateOrCreate(
['x_id' => $xUser->getId()],
[
'name' => $xUser->getName() ?: $xUser->getNickname() ?: 'X user',
'email' => $xUser->getEmail(),
'password' => Str::password(32),
]
);
Auth::login($user, remember: true);
return redirect()->intended('/dashboard');
}
}The lookup key is the X ID, never the email. updateOrCreate runs the update part on every login, so a nullable email is simply stored as null when X does not provide one. If you must have an email, redirect users without one to a form that asks for it.
Step 6: Routes and button
use App\Http\Controllers\XAuthController;
use Illuminate\Support\Facades\Route;
Route::get('/auth/x/redirect', [XAuthController::class, 'redirect'])->name('x.redirect');
Route::get('/auth/x/callback', [XAuthController::class, 'callback']);<a href="{{ route('x.redirect') }}">Continue with X</a>How do I test the login?
- Start the app and click the X link.
- Authorize the app with your own X account.
- Check that you are redirected to
/dashboardand that a user row with anx_idexists.
Common mistakes
- Using the old twitter driver name: current Socialite documents the key as
x. Use that name inservices.phpand inSocialite::driver(). - Expecting an email: X may not return one, so do not use email as the identity.
- Callback URL mismatch: update the portal,
.envand your route together. - App without user authentication enabled: the OAuth screen fails until you enable OAuth 2.0 in the app settings.
- Stale config: run
php artisan config:clearafter changing the credentials.
FAQ
Is the Twitter driver still supported?
Laravel's Socialite documentation lists X as the provider and uses the x configuration key. Check the documentation for your installed version.
Can I get the user's avatar?
Yes. The returned user object has getAvatar(). Store the URL if you need it.
Do I need the stateless() method?
Only for stateless apps without sessions. Regular web apps use the default flow.



