🔐 Implementing Social Login in Laravel: A Complete Guide including Google and Facebook Authentication

Social authentication has become essential for modern web applications, with over 90% of users preferring to log in using their existing social accounts rather than creating new passwords. In this comprehensive guide, we'll implement social login functionality in a Laravel application using Laravel Socialite, covering both Google and Facebook authentication from setup to production deployment.

📋 Table of Contents

Introduction

Social login provides a seamless authentication experience for your users, allowing them to sign in using their existing Google or Facebook accounts. This approach offers several benefits:

  • Reduced friction: Users don't need to remember another password
  • Faster registration: Account creation takes seconds instead of minutes
  • Improved security: Leverages the robust security of major platforms
  • Better user data: Access to verified profile information

This guide will walk you through the complete implementation process, from setting up OAuth credentials to handling user creation and authentication in both development and production environments.

Prerequisites

Before we begin, ensure you have:

  • Laravel 10+ application
  • PHP 8.1 or higher
  • Composer installed
  • Basic understanding of Laravel authentication
  • Access to Google Cloud Console and Facebook Developers
  • SSL certificate for production deployment

Setting Up OAuth Credentials

Google OAuth Setup

  1. Go to Google Cloud Console

  2. Create a new project or select an existing one

  3. Navigate to "APIs & Services" > "OAuth consent screen"

  4. Choose the user type:

    • For development/testing: Select "External"
      • Allows testing with any Google account
      • App remains in testing mode
      • Support for up to 100 test users
      • No verification required for testing
    • For production/internal apps: Select "Internal"
      • Only for Google Workspace organizations
      • Limited to organization users
      • No verification needed
      • Automatic approval for internal users
  5. Fill in the required app information:

    App name: Your App Name
    User support email: [email protected]
    Developer contact information: [email protected]
    App logo: (optional but recommended)
    App domain: your-domain.com (for production)
    
  6. Important: Add scopes you'll need:

    • ../auth/userinfo.email (to get user's email)
    • ../auth/userinfo.profile (to get user's profile info)

2. Create OAuth Credentials

  1. Go to "APIs & Services" > "Credentials"

  2. Click "Create Credentials" > "OAuth client ID"

  3. Choose "Web application" as the application type

  4. Set a descriptive name for your OAuth client

  5. Add authorized redirect URIs:

    • Development: http://localhost/auth/google/callback
    • Production: https://your-domain.com/auth/google/callback

    Note: For Docker setups, use http://localhost without port numbers

  6. Click "Create"

  7. Save your Client ID and Client Secret securely

Pro Tip: You can create separate OAuth clients for development and production environments for better security isolation.

Facebook OAuth Setup

1. Creating a Business Account

  1. Go to Facebook Business
  2. Click "Create Account"
  3. Fill in your business information:
    Business Name: Your Business Name
    Business Email: [email protected]
    Business Address: Your actual business address
    Business Phone Number: Your contact number
    
  4. Verify your business email
  5. Complete business details:
    • Industry category
    • Business type
    • Primary business activity

2. Creating a Developer Account

  1. Go to Facebook Developers
  2. Click "Get Started" and log in with your business account
  3. Accept the Facebook Platform Terms and Policies
  4. Complete developer registration:
    Developer contact email: [email protected]
    Developer website: https://your-domain.com
    Privacy policy URL: https://your-domain.com/privacy
    

3. Creating a New App

  1. In Facebook Developers Console, click "Create App"
  2. Choose "Consumer" as the app type (for user authentication)
  3. Fill in the basic app information:
    App Name: Your App Name
    App Contact Email: [email protected]
    Business Account: Select your business account
    
  4. Click "Create App"

4. Configuring Facebook Login

  1. In your app dashboard, find "Facebook Login" and click "Set up"

  2. Choose "Web" platform

  3. Enter your Site URL: http://localhost (for development)

  4. Go to Facebook Login > Settings in the left sidebar

  5. Configure OAuth settings:

    Client OAuth Login: Yes
    Web OAuth Login: Yes
    Force Web OAuth Reauthentication: No
    Embedded Browser OAuth Login: Yes (for mobile web)
    
  6. Add OAuth redirect URIs:

    • Development: http://localhost/auth/facebook/callback
    • Production: https://your-domain.com/auth/facebook/callback
  7. Configure App Domains:

    • Add localhost for development
    • Add your production domain when ready
  8. Important: Request required permissions:

    • email (essential for user identification)
    • public_profile (for basic profile information)

Security Note: Facebook requires HTTPS in production. During development, add your app to "Development Mode" to allow HTTP.

Implementation Steps

Installing Required Packages

Install Laravel Socialite package:

composer require laravel/socialite

Optional: Install additional packages for enhanced functionality:

composer require intervention/image  # For profile image processing
composer require spatie/laravel-permission  # For role-based access control

Configuration

1. Update Environment Variables

Add these to your .env file:

# Google OAuth
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_REDIRECT_URI="${APP_URL}/auth/google/callback"

# Facebook OAuth  
FACEBOOK_CLIENT_ID=your-facebook-app-id
FACEBOOK_CLIENT_SECRET=your-facebook-app-secret
FACEBOOK_REDIRECT_URI="${APP_URL}/auth/facebook/callback"

Important: Never commit your .env file to version control. Use .env.example for sharing configuration structure.

2. Update Services Configuration

Edit config/services.php:

<?php

return [
    // ... existing services ...

    'google' => [
        'client_id' => env('GOOGLE_CLIENT_ID'),
        'client_secret' => env('GOOGLE_CLIENT_SECRET'),
        'redirect' => env('GOOGLE_REDIRECT_URI'),
    ],

    'facebook' => [
        'client_id' => env('FACEBOOK_CLIENT_ID'),
        'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
        'redirect' => env('FACEBOOK_REDIRECT_URI'),
    ],
];

Database Migration

Create a migration to add social login columns:

php artisan make:migration add_social_login_columns_to_users_table --table=users

Edit the migration file:

<?php

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

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('google_id')->nullable()->after('id');
            $table->string('facebook_id')->nullable()->after('google_id');
            $table->text('google_token')->nullable();
            $table->text('facebook_token')->nullable();
            $table->text('google_refresh_token')->nullable();
            $table->text('facebook_refresh_token')->nullable();
            $table->timestamp('email_verified_at')->nullable()->change(); // Make email verification optional for social login
            $table->string('avatar')->nullable(); // Store profile image URL
        });
    }

    public function down(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn([
                'google_id',
                'facebook_id', 
                'google_token',
                'facebook_token',
                'google_refresh_token',
                'facebook_refresh_token',
                'avatar',
            ]);
        });
    }
};

Run the migration:

php artisan migrate

Creating the Social Login Controller

Generate the controller:

php artisan make:controller Auth/SocialLoginController

Implement the controller with enhanced error handling and logging:

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\User as SocialiteUser;
use Exception;

class SocialLoginController extends Controller
{
    public function redirectToGoogle(): RedirectResponse
    {
        return Socialite::driver('google')
            ->scopes(['email', 'profile'])
            ->redirect();
    }

    public function handleGoogleCallback(): RedirectResponse
    {
        try {
            /** @var SocialiteUser $googleUser */
            $googleUser = Socialite::driver('google')->user();

            // Validate required data
            if (!$googleUser->getEmail()) {
                Log::warning('Google authentication: No email provided');
                return redirect()->route('login')
                    ->with('error', 'Email address is required for registration.');
            }

            $user = $this->findOrCreateUser($googleUser, 'google');
            
            Auth::login($user, true); // Remember user
            
            Log::info('User logged in via Google', ['user_id' => $user->id]);

            return redirect()->intended(route('dashboard', absolute: false));
            
        } catch (Exception $e) {
            Log::error('Google authentication failed', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);

            return redirect()->route('login')
                ->with('error', 'Google authentication failed. Please try again.');
        }
    }

    public function redirectToFacebook(): RedirectResponse
    {
        return Socialite::driver('facebook')
            ->scopes(['email'])
            ->redirect();
    }

    public function handleFacebookCallback(): RedirectResponse
    {
        try {
            /** @var SocialiteUser $facebookUser */
            $facebookUser = Socialite::driver('facebook')->user();

            // Validate required data
            if (!$facebookUser->getEmail()) {
                Log::warning('Facebook authentication: No email provided');
                return redirect()->route('login')
                    ->with('error', 'Email address is required for registration.');
            }

            $user = $this->findOrCreateUser($facebookUser, 'facebook');
            
            Auth::login($user, true); // Remember user
            
            Log::info('User logged in via Facebook', ['user_id' => $user->id]);

            return redirect()->intended(route('dashboard', absolute: false));
            
        } catch (Exception $e) {
            Log::error('Facebook authentication failed', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);

            return redirect()->route('login')
                ->with('error', 'Facebook authentication failed. Please try again.');
        }
    }

    /**
     * Find or create a user based on social provider data
     */
    private function findOrCreateUser(SocialiteUser $socialUser, string $provider): User
    {
        // Check if user exists by social provider ID
        $user = User::where($provider . '_id', $socialUser->getId())->first();
        
        if ($user) {
            // Update tokens and profile data
            $user->update([
                $provider . '_token' => $socialUser->token,
                $provider . '_refresh_token' => $socialUser->refreshToken,
                'avatar' => $socialUser->getAvatar(),
            ]);
            return $user;
        }

        // Check if user exists by email
        $user = User::where('email', $socialUser->getEmail())->first();
        
        if ($user) {
            // Link social account to existing user
            $user->update([
                $provider . '_id' => $socialUser->getId(),
                $provider . '_token' => $socialUser->token,
                $provider . '_refresh_token' => $socialUser->refreshToken,
                'avatar' => $user->avatar ?: $socialUser->getAvatar(),
            ]);
            return $user;
        }

        // Create new user
        return User::create([
            'name' => $socialUser->getName(),
            'email' => $socialUser->getEmail(),
            'avatar' => $socialUser->getAvatar(),
            'email_verified_at' => now(), // Social accounts are pre-verified
            $provider . '_id' => $socialUser->getId(),
            $provider . '_token' => $socialUser->token,
            $provider . '_refresh_token' => $socialUser->refreshToken,
        ]);
    }
}

Handling User Creation

Since Laravel requires a password for user creation, we need to generate a random password for social login users. We'll use an observer to handle this automatically.

1. Create UserObserver

php artisan make:observer UserObserver --model=User

2. Update User Model

Add the ObservedBy attribute and fillable fields:

<?php

namespace App\Models;

use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

#[ObservedBy(UserObserver::class)]
class User extends Authenticatable
{
    use HasApiTokens, Notifiable;

    protected $fillable = [
        'name',
        'email',
        'password',
        'google_id',
        'facebook_id',
        'google_token',
        'facebook_token',
        'google_refresh_token',
        'facebook_refresh_token',
        'avatar',
        'email_verified_at',
    ];

    protected $hidden = [
        'password',
        'remember_token',
        'google_token',
        'facebook_token',
        'google_refresh_token',
        'facebook_refresh_token',
    ];

    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }

    /**
     * Check if user registered via social login
     */
    public function isSocialUser(): bool
    {
        return !empty($this->google_id) || !empty($this->facebook_id);
    }

    /**
     * Get user's profile image
     */
    public function getProfileImageAttribute(): string
    {
        if ($this->avatar) {
            return $this->avatar;
        }

        // Fallback to Gravatar
        return 'https://www.gravatar.com/avatar/' . md5(strtolower($this->email)) . '?d=mp&s=150';
    }
}

3. Implement UserObserver

<?php

namespace App\Observers;

use App\Models\User;
use Illuminate\Support\Str;

class UserObserver
{
    public function creating(User $user): void
    {
        // Generate random password for social login users
        if (!$user->password) {
            $user->password = bcrypt(Str::random(32));
        }
    }

    public function created(User $user): void
    {
        // Log user creation
        Log::info('New user created', [
            'user_id' => $user->id,
            'email' => $user->email,
            'social_login' => $user->isSocialUser(),
        ]);
    }
}

Adding Routes

Add these routes to routes/auth.php:

<?php

use App\Http\Controllers\Auth\SocialLoginController;
// ... other imports

Route::middleware('guest')->group(function (): void {
    // ... existing routes ...

    // Social Login Routes
    Route::prefix('auth')->name('auth.')->group(function () {
        // Google OAuth
        Route::get('google', [SocialLoginController::class, 'redirectToGoogle'])
            ->name('google');
        Route::get('google/callback', [SocialLoginController::class, 'handleGoogleCallback'])
            ->name('google.callback');

        // Facebook OAuth
        Route::get('facebook', [SocialLoginController::class, 'redirectToFacebook'])
            ->name('facebook');
        Route::get('facebook/callback', [SocialLoginController::class, 'handleFacebookCallback'])
            ->name('facebook.callback');
    });
});

Implementing the Frontend

Enhanced React/TypeScript component with better UX:

import { Head, Link, useForm } from '@inertiajs/react';
import { Button } from '@/Components/ui/button';
import { LoaderCircle } from 'lucide-react';
import { FormEventHandler, useState } from 'react';

interface LoginProps {
    status?: string;
    canResetPassword: boolean;
}

export default function Login({ status, canResetPassword }: LoginProps) {
    const [isLoading, setIsLoading] = useState<string | null>(null);
    
    const { data, setData, post, processing, errors, reset } = useForm({
        email: '',
        password: '',
        remember: false,
    });

    const submit: FormEventHandler = (e) => {
        e.preventDefault();
        post(route('login'), {
            onFinish: () => reset('password'),
        });
    };

    const handleSocialLogin = (provider: 'google' | 'facebook') => {
        setIsLoading(provider);
        window.location.href = route(`auth.${provider}`);
    };

    return (
        <AuthLayout 
            title="Log in to your account" 
            description="Enter your email and password below or use social login"
        >
            <Head title="Log in" />

            <div className="flex flex-col gap-6">
                {/* Social Login Buttons */}
                <div className="grid grid-cols-2 gap-4">
                    <Button
                        variant="outline"
                        type="button"
                        onClick={() => handleSocialLogin('google')}
                        disabled={isLoading !== null}
                        className="w-full"
                    >
                        {isLoading === 'google' ? (
                            <LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
                        ) : (
                            <svg className="mr-2 h-4 w-4" viewBox="0 0 24 24">
                                <path
                                    fill="#4285F4"
                                    d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
                                />
                                <path
                                    fill="#34A853"
                                    d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
                                />
                                <path
                                    fill="#FBBC05"
                                    d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
                                />
                                <path
                                    fill="#EA4335"
                                    d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
                                />
                            </svg>
                        )}
                        Continue with Google
                    </Button>
                    
                    <Button
                        variant="outline"
                        type="button"
                        onClick={() => handleSocialLogin('facebook')}
                        disabled={isLoading !== null}
                        className="w-full"
                    >
                        {isLoading === 'facebook' ? (
                            <LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
                        ) : (
                            <svg className="mr-2 h-4 w-4" viewBox="0 0 24 24" fill="#1877F2">
                                <path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
                            </svg>
                        )}
                        Continue with Facebook
                    </Button>
                </div>

                <div className="relative">
                    <div className="absolute inset-0 flex items-center">
                        <span className="w-full border-t" />
                    </div>
                    <div className="relative flex justify-center text-xs uppercase">
                        <span className="bg-background px-2 text-muted-foreground">
                            Or continue with email
                        </span>
                    </div>
                </div>

                {/* Regular Login Form */}
                <form onSubmit={submit} className="space-y-4">
                    {/* Your existing form fields here */}
                </form>
            </div>
        </AuthLayout>
    );
}

Testing Your Implementation

1. Local Development Testing

Test your implementation with these steps:

# Clear cache
php artisan config:clear
php artisan cache:clear
php artisan route:clear

# Start development server
php artisan serve

# Or with Docker
docker compose up -d

2. Verify OAuth Configuration

Create a simple test route to verify your configuration:

// routes/web.php (temporary - remove after testing)
Route::get('/test-oauth', function () {
    return response()->json([
        'google_configured' => config('services.google.client_id') !== null,
        'facebook_configured' => config('services.facebook.client_id') !== null,
        'app_url' => config('app.url'),
    ]);
})->middleware('web');

3. Test Both Providers

  1. Google Login Test:

    • Click "Continue with Google"
    • Authorize your app
    • Verify successful login and user creation
  2. Facebook Login Test:

    • Click "Continue with Facebook"
    • Authorize your app
    • Verify successful login and user creation
  3. Error Handling Test:

    • Try accessing callback URLs directly
    • Test with denied permissions
    • Verify proper error messages

Common Issues and Solutions

1. Invalid Redirect URI

Problem: "redirect_uri_mismatch" error

Solutions:

  • Ensure redirect URI in .env exactly matches OAuth console configuration
  • Remove trailing slashes and extra spaces
  • For Docker: use http://localhost (no port number)
  • Clear Laravel cache: php artisan config:clear

2. Facebook HTTPS Requirement

Problem: Facebook requires HTTPS in production

Solutions:

  • Enable "Development Mode" for local testing
  • Use ngrok for HTTPS tunnel during development:
    ngrok http 80
    
  • Ensure SSL certificate in production

Problem: "This app isn't verified" warning

Solutions:

  • For testing: Use "External" user type with test users
  • Add test users in Google Cloud Console
  • For production: Complete app verification process
  • Use "Internal" type for organization-only apps

4. Email Not Provided

Problem: Social provider doesn't return email

Solutions:

  • Request email scope explicitly
  • Check user's privacy settings
  • Implement fallback email collection
  • Validate email before user creation

5. Token Storage and Security

Problem: Storing sensitive OAuth tokens

Solutions:

  • Encrypt token columns in database
  • Implement token refresh logic
  • Use environment-specific encryption keys
  • Regular token cleanup for expired tokens

6. Database Migration Issues

Problem: Migration fails or conflicts

Solutions:

# Check migration status
php artisan migrate:status

# Rollback specific migration
php artisan migrate:rollback --step=1

# Fresh migration (caution: data loss)
php artisan migrate:fresh

Security Considerations

1. Data Validation and Sanitization

// In your controller
private function validateSocialUser(SocialiteUser $user): void
{
    if (!filter_var($user->getEmail(), FILTER_VALIDATE_EMAIL)) {
        throw new InvalidArgumentException('Invalid email address');
    }
    
    if (strlen($user->getName()) > 255) {
        throw new InvalidArgumentException('Name too long');
    }
}

2. Rate Limiting

Add rate limiting to prevent abuse:

// In routes/auth.php
Route::middleware(['throttle:auth'])->group(function () {
    Route::get('auth/google', [SocialLoginController::class, 'redirectToGoogle']);
    // ... other routes
});

3. CSRF Protection

Ensure CSRF tokens are properly handled:

// In your frontend component
<meta name="csrf-token" content="{{ csrf_token() }}" />

4. Secure Token Storage

Consider encrypting social tokens:

// In your User model
protected $casts = [
    'google_token' => 'encrypted',
    'facebook_token' => 'encrypted',
    'google_refresh_token' => 'encrypted',
    'facebook_refresh_token' => 'encrypted',
];

5. Environment Security

  • Use strong, unique APP_KEY
  • Store OAuth credentials in environment variables
  • Use different credentials for staging/production
  • Regularly rotate OAuth secrets
  • Enable 2FA for OAuth console accounts

Conclusion

Implementing social login in Laravel using Socialite significantly improves user experience while maintaining security. This comprehensive guide covered:

  • ✅ Complete OAuth setup for Google and Facebook
  • ✅ Secure implementation with proper error handling
  • ✅ Database design for social authentication
  • ✅ Frontend integration with modern UX
  • ✅ Production-ready security considerations
  • ✅ Common troubleshooting solutions

Key Takeaways

  1. Always prioritize security: Validate data, use HTTPS, and protect sensitive tokens
  2. Plan for errors: Implement comprehensive error handling and logging
  3. Test thoroughly: Verify functionality in both development and production
  4. Monitor actively: Track authentication metrics and security events
  5. Stay updated: Regularly update dependencies and OAuth configurations

Follow me on LinkedIn for more Laravel authentication tips and best practices!

Have questions about implementing social login or need help troubleshooting? Drop a comment below and I'll help you out!

Comments (0)
Leave a comment

© 2026 All rights reserved.