Implementing Laravel Socialite: Authentication with Google, GitHub, and Considerations

Video thumbnail

Social authentication in Laravel is excellently managed through the official Laravel Socialite package. This package allows us to easily integrate social media login links into our application.

Beyond the technical installation, I want to give you a perspective on why using AI is so interesting in this development. These types of integrations are often tedious because, in many cases, they can only be properly tested in production environments due to social media callback URL restrictions.

Credential Configuration

Just like with payment platforms such as PayPal or Stripe, for social authentication, we need a pair of keys: the Client ID and the Secret ID. These are obtained by creating an application in the developer panel of each platform:

  • Google and GitHub: These are the easiest to configure.
  • Facebook and X (Twitter): They tend to be more complex because their administration panels change constantly.

Once obtained, they are configured in the config/services.php file. In my case, I prefer to define the keys directly there or through environment variables to keep the code clean.

config\services.php

 // SOCIAL
    'github' => [
        'client_id' => env('GITHUB_CLIENT_ID', 'Iv23li***OI'),
        'client_secret' => env('GITHUB_CLIENT_SECRET', '5d6f3e***efa4'),
        'redirect' => '/auth/github/callback',
    ],

    'google' => [
        'client_id' => env('GOOGLE_CLIENT_ID', '9045***0384-***'),
        'client_secret' => env('GOOGLE_CLIENT_SECRET', 'GOCSPX-***'),
        'redirect' => '/auth/google/callback',
    ],

Implementation of Redirect Routes

For the system to work, we need a pair of main routes: Redirect and Callback.

  1. Redirect: This is the function responsible for generating the token and redirecting the user to the corresponding social network. Programmatically, Socialite handles the entire authorization flow.
  2. Callback: This is where we receive the user information once the social network authorizes access.
return Socialite::driver('github')->redirect();

I leave you with the rest of the solution:

app\Http\Controllers\Social\SocialController.php

<?php

namespace App\Http\Controllers\Social;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;

class SocialController extends Controller
{
    /**
     * Redirect the user to the provider authentication page.
     *
     * @param string $provider
     * @return \Symfony\Component\HttpFoundation\RedirectResponse
     */
    public function redirect(string $provider)
    {
        return Socialite::driver($provider)->redirect();
    }

    /**
     * Obtain the user information from the provider.
     *
     * @param string $provider
     * @return \Illuminate\Http\RedirectResponse
     */
    public function callback(string $provider)
    {
        try {
            $socialUser = Socialite::driver($provider)->user();
        } catch (\Exception $e) {
            return redirect()->route('login')->withErrors(['error' => 'Could not authenticate with ' . ucfirst($provider)]);
        }

        // Try to find user by social ID
        $user = User::where($provider . '_id', $socialUser->getId())->first();

        if ($user) {
            Auth::login($user);
            return redirect()->intended('https://academy.desarrollolibre.net/');
        }

        // No user found by social ID, check for email
        $email = $socialUser->getEmail();

        if (!$email) {
            // Store social data in session to use after they provide an email
            session([
                'social_user' => [
                    'id' => $socialUser->getId(),
                    'name' => $socialUser->getName() ?? $socialUser->getNickname() ?? 'User',
                    'avatar' => $socialUser->getAvatar(),
                    'provider' => $provider,
                ]
            ]);

            return redirect()->route('social.email.request');
        }

        // Try to find user by email
        $user = User::where('email', $email)->first();

        if ($user) {
            // Link social ID to existing user
            $user->update([
                $provider . '_id' => $socialUser->getId(),
                'avatar' => $user->avatar ?? $socialUser->getAvatar(),
            ]);
        } else {
            // Create new user
            $user = User::create([
                'name' => $socialUser->getName() ?? $socialUser->getNickname() ?? 'User',
                'email' => $email,
                $provider . '_id' => $socialUser->getId(),
                'avatar' => $socialUser->getAvatar(),
                'password' => Hash::make(Str::random(24)),
                'email_verified_at' => $provider === 'google' ? now() : null,
            ]);

            event(new \Illuminate\Auth\Events\Registered($user));
        }

        Auth::login($user);

        return redirect()->intended('https://academy.desarrollolibre.net/');
    }
}

The routes:

routes/web.php

<?php

use App\Http\Controllers\Social\SocialController;
use Illuminate\Support\Facades\Route;

// auth social
Route::get('/auth/{provider}/redirect', [SocialController::class, 'redirect'])->name('social.redirect');
Route::get('/auth/{provider}/callback', [SocialController::class, 'callback'])->name('social.callback');


We add the options in the login window:

resources\views\pages\auth\login.blade.php

<div class="grid grid-cols-2 gap-4">
    <a href="{{route('social.redirect', 'github')}}" variant="subtle" class="w-full !py-3 bg-gray-800/50 border-gray-700 hover:bg-gray-800 transition-colors">
        <span class="text-white">GitHub</span>
    </a>

    <a href="{{route('social.redirect', 'google')}}" variant="subtle" class="w-full !py-3 bg-gray-800/50 border-gray-700 hover:bg-gray-800 transition-colors">
        <span class="text-white">Google</span>
    </a>
</div>

We modify the user migration to add additional fields used in social authentication:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('github_id')->nullable()->unique();
            $table->string('google_id')->nullable()->unique();
            $table->string('avatar')->nullable();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn(['github_id', 'google_id', 'avatar']);
        });
    }
};

Social User Registration and Login

In the Callback, we check if the user already exists in our database.

  • If they exist: We log them in directly.
  • If they don't exist: We register them. In this step, it is recommended to save the provider_id and the avatar.

Since social users do not have a defined password, the ideal is to generate a random 24-character hash. This ensures that no one can enter through the traditional login form without guessing that impossible key, keeping the account secure.

The User Data Challenge: Email

A critical point is the handling of the user model. Laravel uses the User model by default, where the email is the primary identifier. However, many social networks (with the exception of Google) do not always return the email by default:

  • GitHub, Facebook, and X: Sometimes they only provide the nickname or username.
  • Solution: If the email is indispensable for your application (as in my case for password recovery), you have two options: adapt your app so it doesn't depend on the email or implement an additional window to request it from the user before completing registration.

app\Http\Controllers\Social\SocialController.php

<?php
***
class SocialController extends Controller
{
    ***
    
    public function showEmailForm()
    {
        if (!session()->has('social_user')) {
            return redirect()->route('login');
        }

        return view('social.email');
    }

    public function storeEmail(\Illuminate\Http\Request $request)
    {
        $request->validate([
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
        ]);

        $socialData = session('social_user');

        if (!$socialData) {
            return redirect()->route('login');
        }

        $user = User::create([
            'name' => $socialData['name'],
            'email' => $request->email,
            $socialData['provider'] . '_id' => $socialData['id'],
            'avatar' => $socialData['avatar'],
            'password' => Hash::make(Str::random(24)),
        ]);

        event(new \Illuminate\Auth\Events\Registered($user));

        Auth::login($user);

        session()->forget('social_user');

        return redirect()->intended('https://academy.desarrollolibre.net/');
    }
}

The routes:

routes/web.php

<?php
***
// provide email when social network DOES NOT return it
Route::get('/social/email', [SocialController::class, 'showEmailForm'])->name('social.email.request');
Route::post('/social/email', [SocialController::class, 'storeEmail'])->name('social.email.store');

As you can see, if the login is via GitHub, the data is recorded in the session and it moves to a screen that requests the email to register the user:

Production Testing and Callback URLs

It is fundamental to understand that this development is difficult to test on localhost. Social networks require a valid return URL (HTTPS and real domain). If you try to use a .test or localhost domain, most platforms will reject the request.

That is why I used AI to generate much of the static interface flow and logic. Being a process with a lot of "trial and error" in production, having solid code from the start saves a lot of time.

Configuration in Developer Panels

  • GitHub: In Settings > Developer Settings, you create an OAuth App and configure the Authorization callback URL.
  • Google Cloud: You must create a project, enable the corresponding APIs, and in "Credentials" create an OAuth client ID. It is vital to configure the "Authorized JavaScript origins" and "Redirect URIs".

Learn how to integrate Laravel Socialite to enable social login. Client IDs, secrets, and callbacks are easily and securely implemented, along with key usage tips.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

I agree to receive announcements of interest about this Blog.

Andrés Cruz

ES En español