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
- 🔐 Implementing Social Login in Laravel: A Complete Guide including Google and Facebook Authentication
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. Configure the Consent Screen
-
Go to Google Cloud Console
-
Create a new project or select an existing one
-
Navigate to "APIs & Services" > "OAuth consent screen"
-
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
- For development/testing: Select "External"
-
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) -
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
-
Go to "APIs & Services" > "Credentials"
-
Click "Create Credentials" > "OAuth client ID"
-
Choose "Web application" as the application type
-
Set a descriptive name for your OAuth client
-
Add authorized redirect URIs:
- Development:
http://localhost/auth/google/callback - Production:
https://your-domain.com/auth/google/callback
Note: For Docker setups, use
http://localhostwithout port numbers - Development:
-
Click "Create"
-
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
- Go to Facebook Business
- Click "Create Account"
- 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 - Verify your business email
- Complete business details:
- Industry category
- Business type
- Primary business activity
2. Creating a Developer Account
- Go to Facebook Developers
- Click "Get Started" and log in with your business account
- Accept the Facebook Platform Terms and Policies
- 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
- In Facebook Developers Console, click "Create App"
- Choose "Consumer" as the app type (for user authentication)
- Fill in the basic app information:
App Name: Your App Name App Contact Email: [email protected] Business Account: Select your business account - Click "Create App"
4. Configuring Facebook Login
-
In your app dashboard, find "Facebook Login" and click "Set up"
-
Choose "Web" platform
-
Enter your Site URL:
http://localhost(for development) -
Go to Facebook Login > Settings in the left sidebar
-
Configure OAuth settings:
Client OAuth Login: Yes Web OAuth Login: Yes Force Web OAuth Reauthentication: No Embedded Browser OAuth Login: Yes (for mobile web) -
Add OAuth redirect URIs:
- Development:
http://localhost/auth/facebook/callback - Production:
https://your-domain.com/auth/facebook/callback
- Development:
-
Configure App Domains:
- Add
localhostfor development - Add your production domain when ready
- Add
-
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
.envfile to version control. Use.env.examplefor 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
-
Google Login Test:
- Click "Continue with Google"
- Authorize your app
- Verify successful login and user creation
-
Facebook Login Test:
- Click "Continue with Facebook"
- Authorize your app
- Verify successful login and user creation
-
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
.envexactly 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
3. Google Consent Screen Issues
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
- Always prioritize security: Validate data, use HTTPS, and protect sensitive tokens
- Plan for errors: Implement comprehensive error handling and logging
- Test thoroughly: Verify functionality in both development and production
- Monitor actively: Track authentication metrics and security events
- 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!