Skip to content

Google OAuth

Google OAuth integration for admin panel authentication using Laravel Socialite.

Admin users authenticate via Google OAuth, providing:

  • Single Sign-On (SSO) with Google Workspace
  • Secure authentication without password management
  • Domain restriction for authorized organizations
  • Session-based authentication with CSRF protection
Terminal window
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret
GOOGLE_REDIRECT_URI=${APP_URL}/auth/google/callback
# Comma-separated list of allowed email domains
GOOGLE_OAUTH_ALLOWED_DOMAINS="yourcompany.com,another.com"
  1. Go to Google Cloud Console
  2. Create or select a project
  3. Navigate to APIs & ServicesCredentials
  4. Click Create CredentialsOAuth client ID
  5. Select Web application
  6. Add authorized redirect URI: https://yourdomain.com/auth/google/callback
  7. Copy Client ID and Client Secret to .env
Route Method Name Middleware Description
/auth/google/redirect GET auth.google.redirect guest, throttle:10,1 Redirect to Google OAuth
/auth/google/callback GET auth.google.callback guest, throttle:10,1 Handle OAuth callback
/logout POST logout Logout (handled by App\Livewire\Actions\Logout)

Defined in backend/routes/auth.php.

app/Http/Controllers/Auth/GoogleAuthController.php
class GoogleAuthController extends Controller
{
public function __construct(
private readonly GoogleAuthDomainService $domainService
) {}
public function redirect(): RedirectResponse
{
return Socialite::driver('google')->redirect();
}
public function callback(): RedirectResponse
{
try {
$googleUser = Socialite::driver('google')->user();
// Domain restriction via GoogleAuthDomainService
if (!$this->domainService->isEmailAllowed($googleUser->getEmail())) {
return redirect()->route('filament.admin.auth.login')
->with('error', $this->domainService->getValidationErrorMessage());
}
// Find or create user (with transaction safety).
// New OAuth users are assigned RoleEnum::SupplierManager.
$user = $this->findOrCreateUser($googleUser);
Auth::login($user, remember: true);
request()->session()->regenerate();
// Supplier managers are sent straight to their default panel;
// everyone else honors the intended URL, both via getDefaultPanelUrl().
if ($user->isSupplierManager()) {
session()->forget('url.intended');
return redirect()->to($user->getDefaultPanelUrl());
}
return redirect()->intended($user->getDefaultPanelUrl());
} catch (\Exception $e) {
Log::error('Google OAuth failed', ['error' => $e->getMessage()]);
return redirect()->route('filament.admin.auth.login')
->with('error', 'Authentication failed. Please try again.');
}
}
}

Role assignment & post-login routing: findOrCreateUser() links Google accounts to existing users (by google_id, then email) and, for genuinely new users, creates the record and assigns RoleEnum::SupplierManager as the default role. After login, User::getDefaultPanelUrl() decides the landing page: supplier managers go straight to their default panel (url.intended is cleared first), while all other users honor the intended URL. See Roles and Permissions.

routes/auth.php
Route::middleware('guest')->group(function (): void {
Route::get('auth/google/redirect', [GoogleAuthController::class, 'redirect'])
->middleware('throttle:10,1') // 10 attempts per minute per IP
->name('auth.google.redirect');
Route::get('auth/google/callback', [GoogleAuthController::class, 'callback'])
->middleware('throttle:10,1')
->name('auth.google.callback');
});
Route::post('logout', App\Livewire\Actions\Logout::class)
->name('logout');
database/migrations/create_users_table.php
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('google_id')->nullable()->unique();
$table->string('avatar')->nullable();
$table->timestamp('email_verified_at')->nullable();
$table->rememberToken();
$table->timestamps();
});
app/Models/User.php
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable, HasRoles;
protected $fillable = [
'name',
'email',
'google_id',
'avatar',
];
protected $hidden = [
'remember_token',
];
}
app/Filament/Pages/Auth/Login.php
class Login extends BaseLogin
{
public function mount(): void
{
parent::mount();
// Redirect if already authenticated
if (Filament::auth()->check()) {
redirect()->intended(Filament::getUrl());
}
}
protected function getFormSchema(): array
{
return []; // No form fields - OAuth only
}
protected function getViewData(): array
{
return [
'googleAuthUrl' => route('auth.google.redirect'),
];
}
}
{{-- resources/views/filament/pages/auth/login.blade.php --}}
<x-filament-panels::page.simple>
<div class="text-center">
<h2 class="text-2xl font-bold mb-4">Sign in to Admin Panel</h2>
<a href="{{ $googleAuthUrl }}"
class="inline-flex items-center px-6 py-3 bg-white border border-gray-300 rounded-lg shadow-sm hover:bg-gray-50 transition">
<svg class="w-5 h-5 mr-3" viewBox="0 0 24 24">
{{-- Google icon SVG --}}
</svg>
<span>Continue with Google</span>
</a>
</div>
</x-filament-panels::page.simple>

Restrict access to specific email domains (comma-separated):

config/services.php
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_REDIRECT_URI'),
'oauth_allowed_domains' => array_filter(
array_map('trim', explode(',', env('GOOGLE_OAUTH_ALLOWED_DOMAINS', 'yourcompany.com,another.com')))
),
],

Domain validation is handled by GoogleAuthDomainService, not inline in the controller.

Source: backend/app/Services/GoogleAuthDomainService.php

config/session.php
'lifetime' => 120, // 2 hours
'expire_on_close' => false,
'encrypt' => true,
'same_site' => 'lax',
'secure' => env('APP_ENV') === 'production',

All POST routes are automatically protected by Laravel’s CSRF middleware.

  1. Visit /auth/google/redirect in browser
  2. Sign in with Google account
  3. Verify redirect to the user’s default panel (supplier managers land on their supplier panel)
  4. Check user record created in database with the supplier-manager role
use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\User as SocialiteUser;
test('google oauth creates user and logs in', function () {
$googleUser = Mockery::mock(SocialiteUser::class);
$googleUser->shouldReceive('getId')->andReturn('123456');
$googleUser->shouldReceive('getName')->andReturn('Test User');
$googleUser->shouldReceive('getEmail')->andReturn('test@company.com');
$googleUser->shouldReceive('getAvatar')->andReturn('https://avatar.url');
Socialite::shouldReceive('driver')
->with('google')
->andReturn(Mockery::mock([
'user' => $googleUser,
]));
$response = $this->get('/auth/google/callback');
// New OAuth users are supplier managers and get routed to their default
// panel via getDefaultPanelUrl(), so assert the redirect + auth state
// rather than a hardcoded path.
$response->assertRedirect();
$this->assertAuthenticated();
$this->assertDatabaseHas('users', [
'email' => 'test@company.com',
'google_id' => '123456',
]);
});

Error: redirect_uri_mismatch

Solution:

  1. Check Google Cloud Console redirect URIs match exactly
  2. Include protocol (https://) and path (/auth/google/callback)
  3. No trailing slash

Error: GoogleAuthDomainService::getValidationErrorMessage() returns “Only @company.com or @another.com email addresses are allowed to access this system.” (the allowed domains, each prefixed with @, joined by “ or “). When the allowed-domains list is empty it instead returns “Google OAuth authentication is not properly configured.”

Solution:

  1. Check GOOGLE_OAUTH_ALLOWED_DOMAINS in .env (comma-separated list)
  2. Verify user’s email domain matches one of the allowed domains
  3. An empty allowed-domains list denies all sign-insisEmailAllowed() runs in_array() over an empty array, so no domain matches. There is no allow-all mode; at least one domain must be configured

Symptom: User logged out after redirect

Solutions:

  1. Ensure SESSION_DOMAIN matches application domain
  2. Check SESSION_SECURE_COOKIE for HTTPS
  3. Verify session driver is configured correctly