Courses

Creating a Quiz System with Laravel 10 + Livewire 3: Step-by-Step

Login with Facebook/Google/Github

In addition to the Laravel Breeze starter kit, we will allow users to login and register via GitHub, Google, and Facebook. In this part we will implement this feature using official Laravel package Laravel socialite.

By the end of this lesson, we will have this view:

social login buttons

First, we need to install this package, and add fields to Users table for each provider, where providers ID will be saved.

composer require laravel/socialite
php artisan make:migration "add socialite fields to users table"

database/migrations/xxxx_add_socialite_fields_to_users_table.php:

return new class extends Migration {
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->after('password', function (Blueprint $table) {
$table->string('facebook_id')->nullable();
$table->string('google_id')->nullable();
$table->string('github_id')->nullable();
});
});
}
};

Of course, don't forget to add filables to the User Model.

app/Models/User.php:

class User extends Authenticatable
{
// ...
protected $fillable = [
'name',
'email',
'password',
'facebook_id',
'google_id',
'github_id',
];
// ...
}

And for users migrations, we need to set default for name field, because users name will be updated after being created.

database/migrations/2014_10_12_000000_create_users_table.php:

return new class extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name')->default('');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
};

To be able to connect with the providers, we will need to set credentials for each provider. These values needs to set in your .env file and then used in the config/services.php

.env:

GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
 
FACEBOOK_CLIENT_ID=
FACEBOOK_CLIENT_SECRET=
 
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

config/services.php:

return [
// ...
 
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => '/auth/github/callback',
],
 
'facebook' => [
'client_id' => env('FACEBOOK_CLIENT_ID'),
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
'redirect' => '/auth/facebook/callback',
],
 
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => '/auth/google/callback',
],
 
];

TIP: Be a good developer and always provide env keys into .env.example.

Next, the controller:

php artisan make:controller Auth/SocialiteController

app/Http/Controllers/Auth/SocialiteController.php:

class SocialiteController extends Controller
{
public function loginSocial(Request $request, string $provider): RedirectResponse
{
$this->validateProvider($request);
 
return Socialite::driver($provider)->redirect();
}
 
public function callbackSocial(Request $request, string $provider)
{
$this->validateProvider($request);
 
$response = Socialite::driver($provider)->user();
 
$user = User::firstOrCreate(
['email' => $response->getEmail()],
['password' => Str::password()]
);
$data = [$provider . '_id' => $response->getId()];
 
if ($user->wasRecentlyCreated) {
$data['name'] = $response->getName() ?? $response->getNickname();
 
event(new Registered($user));
}
 
$user->update($data);
 
Auth::login($user, remember: true);
 
return redirect()->intended(RouteServiceProvider::HOME);
}
 
protected function validateProvider(Request $request): array
{
return $this->getValidationFactory()->make(
$request->route()->parameters(),
['provider' => 'in:facebook,google,github']
)->validate();
}
}

Now we need to add routes.

routes/auth.php:

Route::middleware('guest')->group(function () {
// ...
Route::get('auth/{provider}/redirect', [SocialiteController::class, 'loginSocial'])
->name('socialite.auth');
 
Route::get('auth/{provider}/callback', [SocialiteController::class, 'callbackSocial'])
->name('socialite.callback');
});
// ...

And last thing, add links in the login and register pages. Because we will add them in two pages, we will create a Blade Component to reuse them, also if you will want to remove one of the providers, or add new one, you will need to edit only in one file.

php artisan make:component SocialLinks --view

resources/views/components/social-links.blade.php:

<div class="mt-4 space-y-2">
<a class="inline-flex w-full items-center justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-xs font-semibold uppercase tracking-widest text-gray-700 shadow-sm transition duration-150 ease-in-out hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25" href="{{ route('socialite.auth', 'github') }}">
GitHub
</a>
 
<a class="inline-flex w-full items-center justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-xs font-semibold uppercase tracking-widest text-gray-700 shadow-sm transition duration-150 ease-in-out hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25" href="{{ route('socialite.auth', 'google') }}">
Google
</a>
 
<a class="inline-flex w-full items-center justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-xs font-semibold uppercase tracking-widest text-gray-700 shadow-sm transition duration-150 ease-in-out hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25" href="{{ route('socialite.auth', 'facebook') }}">
Facebook
</a>
</div>

In both resources/views/auth/login.blade.php and resources/views/auth/register.blade.php use this component just before layouts end.

// ...
</form>
 
<x-social-links />
</x-guest-layout>

Now your users will be able to login or register using GitHub, Google, or Facebook providers.

social login buttons

avatar

In the "app/Http/Controllers/Auth/SocialiteController.php" code you missing all the important imports...

look like imports are missing from more examples costing errors in VSCODE and make new users frustrated.

Also a typo at: recoures/views/components/social-links.blade.php: clearly need to be resources

Now your users will be able to login or register using GitHub, Google, or Facebook providers. NO they wouldn't you may want to include how to setup this GitHub, Google, or Facebook providers on those sites with the callback urls and so on...

👍 2
avatar

Well, we often miss the "use" section because we think that it's understandable that you should include a new class on top. But ok, will be more careful next time and explain everything.

Also, we add a GitHub repository at the end, to double-check for everyone.

Thanks for proofreading, fixed the typo with "recoures"

avatar

Povilas im just curious is it okay to do the callbackSocial method like this? instead of using firstOrCreate i used updateOrCreate, while putting the name and provider id there altogether in one statement.

public function callbackSocial(Request $request, string $provider): RedirectResponse
{
    $this->validateProvider($request);

    $response = Socialite::driver($provider)->user();

    $user = User::updateOrCreate(
        ['email' => $response->getEmail()],
        [
            'password' => Str::password(),
            'name' => $response->getName() ?? $response->getNickname,
            $provider . '_id' => $response->getId()
        ]
    );
    
    if ($user->wasRecentlyCreated) {
        event(new Registered($user));
    }

    Auth::login($user, remember: true);

    return redirect()->intended(RouteServiceProvider::HOME);
}
avatar

Haven't tried, but it looks good, should work!

avatar

Amazed by the directions. Excellent course!

avatar

Hello,

Is it possible to skip this section? I am building this into a local laravel project where I already am a admin user. Thanks for your reaction.

avatar

You can skip the parts that are not needed for you. This is integrating the social login, so if you don't want/need this, you can definitely skip

avatar

hello Provilas I made an observation with socialite, it is that if there was a pre-existing email in the database and another person tries to connect with their Google account if the emails are similar (because there are people who have the same names and who may have a similar email) and the person who logs in with their Google account will still be logged in through the pre-existing email account.

avatar

I'm not sure what you mean by "similar" emails based on names. It should be a separate thing and you should only check if the email is the same.

Without concrete examples of emails (even redacted, but working in any case) and code - it is impossible to say what went wrong. But most likely, there is a mistake in the query conditions.