Practical guide to implementing bulletproof authentication pipelines with Google/Facebook login integration and queued email recovery systems.
Authentication is the front door of any SaaS platform. In production, users expect dual options: seamless social login (Google/Facebook) and traditional email/password workflows.
However, two major problems occur frequently: duplicate account creation loops (when an email-registered user tries to click Google login) and slow or failing password recovery emails. Let's look at how to secure these flows.
To prevent duplicate account conflicts, never link a social login directly to the main users table as a single provider string. Instead, use a dedicated social_accounts table. This allows a single user to link both Google and Facebook to their profile.
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Database migration
Schema::create('linked_social_accounts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('provider_name'); class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// 'google', 'facebook'
$table->string('provider_id'); class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// OAuth Unique User ID
$table->timestamps();
});When a user triggers social login, resolve their email first. If the email exists, link the social provider to the existing account rather than registering a duplicate user.
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// app/Services/SocialAccountsService.php
namespace AppServices;
use AppModelsUser;
use AppModelsLinkedSocialAccount;
use LaravelSocialiteContractsUser as ProviderUser;
class SocialAccountsService
{
public function findOrCreate(ProviderUser $providerUser, string $providerName): User
{
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// 1. Search for existing social link
$account = LinkedSocialAccount::where('provider_name', $providerName)
->where('provider_id', $providerUser->getId())
->first();
if ($account) {
return $account->user;
}
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// 2. Search for existing user email
$user = User::where('email', $providerUser->getEmail())->first();
if (!$user) {
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Register a new user if it doesn't exist
$user = User::create([
'name' => $providerUser->getName(),
'email' => $providerUser->getEmail(),
'password' => bcrypt(str_random(24)), class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Generate secure random password
]);
}
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// 3. Link the social account to the user
LinkedSocialAccount::create([
'user_id' => $user->id,
'provider_name' => $providerName,
'provider_id' => $providerUser->getId(),
]);
return $user;
}
}Sending password reset emails synchronously inside a web request delays the client dashboard response and risks throwing HTTP 504 gateway timeouts if SMTP servers respond slowly.
Make sure your password reset notification implements the ShouldQueue contract, delegating the email transmission to a Redis queue.
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// app/Notifications/ResetPasswordQueued.php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class ResetPasswordQueued extends Notification implements ShouldQueue
{
use Queueable;
public $token;
public function __construct($token)
{
$this->token = $token;
}
public function via($notifiable)
{
return ['mail'];
}
public function toMail($notifiable)
{
$url = url(route('password.reset', [
'token' => $this->token,
'email' => $notifiable->getEmailForPasswordReset(),
], false));
return (new MailMessage)
->subject('Reset Password Notification')
->line('You are receiving this email because we received a password reset request for your account.')
->action('Reset Password', $url)
->line('This password reset link will expire in 60 minutes.');
}
}By queueing notification dispatches and linking social accounts dynamically, you guarantee a fast, secure, and user-friendly authentication system.