Google OAuth Setup - luckydeva03/barbershop_app GitHub Wiki
Panduan lengkap setup Google OAuth untuk login dengan akun Google.
- Google Account
- Google Cloud Console access
- Domain atau localhost untuk testing
- Buka Google Cloud Console
- Klik "Select Project" โ "New Project"
- Isi Project Name:
Barbershop App OAuth
- Klik "Create"
- Di project baru, buka "APIs & Services" โ "Library"
- Search "Google+ API" atau "People API"
- Klik "Enable"
- Buka "APIs & Services" โ "OAuth consent screen"
- Pilih "External" (untuk testing) atau "Internal" (untuk organisasi)
- Isi form OAuth consent screen:
App name โ Barbershop Management System
User support email โ [email protected]
Developer contact โ [email protected]
App domain โ https://yourdomain.com (atau http://localhost:8000)
Authorized domains โ localhost, yourdomain.com
-
Scopes: Tambahkan scope berikut:
email
profile
openid
-
Test users (untuk mode testing):
- Tambahkan email yang akan digunakan untuk testing
- Buka "APIs & Services" โ "Credentials"
- Klik "+ Create Credentials" โ "OAuth 2.0 Client IDs"
- Pilih "Web application"
- Isi form:
Name โ Barbershop App Web Client
Authorized origins โ http://localhost:8000 (untuk development)
โ https://yourdomain.com (untuk production)
Redirect URIs โ http://localhost:8000/auth/google/callback
โ https://yourdomain.com/auth/google/callback
- Klik "Create"
- Simpan Client ID dan Client Secret
composer require laravel/socialite
Edit file .env
dan tambahkan:
# Google OAuth Configuration
GOOGLE_CLIENT_ID=your_google_client_id_here
GOOGLE_CLIENT_SECRET=your_google_client_secret_here
GOOGLE_REDIRECT_URL=http://localhost:8000/auth/google/callback
# Untuk production
# GOOGLE_REDIRECT_URL=https://yourdomain.com/auth/google/callback
Edit config/services.php
:
<?php
return [
// ... existing config
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_REDIRECT_URL'),
],
];
Google OAuth integration sudah include di migration users table:
// Sudah ada di migration users
$table->string('google_id')->nullable()->index();
$table->text('profile_photo')->nullable();
Jika belum ada, buat migration baru:
php artisan make:migration add_google_oauth_to_users_table
# Edit migration file
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('google_id')->nullable()->index();
$table->text('profile_photo')->nullable();
});
}
Update User
model untuk Google OAuth:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens;
protected $fillable = [
'name',
'email',
'password',
'phone',
'google_id', // โ Add this
'profile_photo', // โ Add this
];
protected $hidden = [
'password',
'remember_token',
];
// Google OAuth methods
public function isGoogleUser()
{
return !is_null($this->google_id);
}
public function getProfilePhotoAttribute($value)
{
if ($value) {
return $value;
}
// Default avatar jika tidak ada foto
return 'https://ui-avatars.com/api/?name=' . urlencode($this->name) . '&background=random';
}
}
Buat atau update GoogleAuthController
:
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Support\Str;
class GoogleAuthController extends Controller
{
/**
* Redirect to Google OAuth
*/
public function redirectToGoogle()
{
return Socialite::driver('google')
->scopes(['email', 'profile'])
->redirect();
}
/**
* Handle Google OAuth callback
*/
public function handleGoogleCallback()
{
try {
$googleUser = Socialite::driver('google')->user();
// Check if user already exists
$user = User::where('email', $googleUser->getEmail())->first();
if ($user) {
// Update existing user with Google info
$user->update([
'google_id' => $googleUser->getId(),
'profile_photo' => $googleUser->getAvatar(),
]);
} else {
// Create new user
$user = User::create([
'name' => $googleUser->getName(),
'email' => $googleUser->getEmail(),
'google_id' => $googleUser->getId(),
'profile_photo' => $googleUser->getAvatar(),
'password' => Hash::make(Str::random(24)), // Random password
'email_verified_at' => now(), // Auto verify Google users
]);
}
// Login user
Auth::login($user, true);
// Redirect to intended page or dashboard
return redirect()->intended('/dashboard')
->with('success', 'Successfully logged in with Google!');
} catch (\Exception $e) {
return redirect('/login')
->with('error', 'Google login failed. Please try again.');
}
}
/**
* Link existing account with Google
*/
public function linkGoogleAccount()
{
if (!Auth::check()) {
return redirect('/login')
->with('error', 'Please login first to link your Google account.');
}
try {
$googleUser = Socialite::driver('google')->user();
// Check if Google account is already linked to another user
$existingUser = User::where('google_id', $googleUser->getId())
->where('id', '!=', Auth::id())
->first();
if ($existingUser) {
return redirect('/dashboard')
->with('error', 'This Google account is already linked to another user.');
}
// Link Google account to current user
Auth::user()->update([
'google_id' => $googleUser->getId(),
'profile_photo' => $googleUser->getAvatar(),
]);
return redirect('/dashboard')
->with('success', 'Google account linked successfully!');
} catch (\Exception $e) {
return redirect('/dashboard')
->with('error', 'Failed to link Google account.');
}
}
/**
* Unlink Google account
*/
public function unlinkGoogleAccount()
{
if (!Auth::check()) {
return redirect('/login');
}
$user = Auth::user();
// Check if user has password (safety check)
if (!$user->password) {
return redirect('/dashboard')
->with('error', 'Please set a password before unlinking Google account.');
}
$user->update([
'google_id' => null,
'profile_photo' => null,
]);
return redirect('/dashboard')
->with('success', 'Google account unlinked successfully.');
}
}
Add routes di routes/web.php
:
<?php
use App\Http\Controllers\Auth\GoogleAuthController;
// Google OAuth routes
Route::prefix('auth/google')->group(function () {
Route::get('/', [GoogleAuthController::class, 'redirectToGoogle'])
->name('google.login');
Route::get('/callback', [GoogleAuthController::class, 'handleGoogleCallback'])
->name('google.callback');
Route::post('/link', [GoogleAuthController::class, 'linkGoogleAccount'])
->middleware('auth')
->name('google.link');
Route::post('/unlink', [GoogleAuthController::class, 'unlinkGoogleAccount'])
->middleware('auth')
->name('google.unlink');
});
Tambahkan Google login button di login form:
<!-- resources/views/auth/login.blade.php -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body">
<h5 class="card-title">Login to Your Account</h5>
<!-- Regular Login Form -->
<form method="POST" action="{{ route('login') }}">
@csrf
<!-- ... existing form fields ... -->
<button type="submit" class="btn btn-primary w-100">
Login
</button>
</form>
<!-- Divider -->
<div class="text-center my-3">
<span class="text-muted">or</span>
</div>
<!-- Google Login Button -->
<a href="{{ route('google.login') }}"
class="btn btn-outline-danger w-100">
<i class="fab fa-google me-2"></i>
Continue with Google
</a>
</div>
</div>
</div>
</div>
<!-- resources/views/dashboard.blade.php -->
<div class="card">
<div class="card-header">
<h5>Account Settings</h5>
</div>
<div class="card-body">
@if(auth()->user()->google_id)
<!-- Google Account Linked -->
<div class="alert alert-success">
<i class="fab fa-google me-2"></i>
Your Google account is linked
</div>
<form method="POST" action="{{ route('google.unlink') }}">
@csrf
<button type="submit" class="btn btn-outline-danger">
Unlink Google Account
</button>
</form>
@else
<!-- Google Account Not Linked -->
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
Link your Google account for easier login
</div>
<a href="{{ route('google.login') }}" class="btn btn-outline-primary">
<i class="fab fa-google me-2"></i>
Link Google Account
</a>
@endif
</div>
</div>
Display Google profile photo:
<!-- Profile photo component -->
<div class="user-profile">
@if(auth()->user()->profile_photo)
<img src="{{ auth()->user()->profile_photo }}"
alt="{{ auth()->user()->name }}"
class="rounded-circle"
width="40" height="40">
@else
<div class="avatar-placeholder">
{{ substr(auth()->user()->name, 0, 1) }}
</div>
@endif
<span>{{ auth()->user()->name }}</span>
</div>
// Request additional permissions
return Socialite::driver('google')
->scopes(['email', 'profile', 'phone']) // Add phone if needed
->redirect();
// Add state for CSRF protection
return Socialite::driver('google')
->with(['state' => Str::random(40)])
->redirect();
// config/services.php
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('APP_ENV') === 'production'
? 'https://yourdomain.com/auth/google/callback'
: 'http://localhost:8000/auth/google/callback',
],
// Only allow specific email domains
$allowedDomains = ['gmail.com', 'company.com'];
$emailDomain = substr(strrchr($googleUser->getEmail(), '@'), 1);
if (!in_array($emailDomain, $allowedDomains)) {
return redirect('/login')
->with('error', 'Email domain not allowed.');
}
// app/Http/Kernel.php
'google-auth' => [
'throttle:5,1', // 5 attempts per minute
],
// Log OAuth activities
Log::info('Google OAuth login', [
'user_id' => $user->id,
'google_id' => $googleUser->getId(),
'email' => $googleUser->getEmail(),
'ip' => request()->ip(),
]);
// tests/Feature/GoogleAuthTest.php
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
use Laravel\Socialite\Facades\Socialite;
class GoogleAuthTest extends TestCase
{
public function test_google_redirect()
{
$response = $this->get('/auth/google');
$response->assertRedirect();
$this->assertStringContains('accounts.google.com', $response->headers->get('Location'));
}
public function test_google_callback_creates_new_user()
{
// Mock Google user response
$googleUser = new \Laravel\Socialite\Two\User();
$googleUser->id = '123456789';
$googleUser->email = '[email protected]';
$googleUser->name = 'Test User';
$googleUser->avatar = 'https://example.com/avatar.jpg';
Socialite::shouldReceive('driver->user')
->andReturn($googleUser);
$response = $this->get('/auth/google/callback');
$this->assertDatabaseHas('users', [
'email' => '[email protected]',
'google_id' => '123456789',
]);
$response->assertRedirect('/dashboard');
}
}
- Google redirect berfungsi
- Callback URL accessible
- New user creation works
- Existing user login works
- Profile photo sync works
- Account linking works
- Account unlinking works
- Error handling works
Solution:
- Cek Authorized redirect URIs di Google Console
- Pastikan URL exact match (termasuk http/https)
- Periksa .env GOOGLE_REDIRECT_URL
Solution:
- User tidak ada di test users list (mode testing)
- OAuth consent screen belum approved
- Domain tidak terdaftar di authorized domains
Solution:
- Periksa GOOGLE_CLIENT_ID di .env
- Periksa GOOGLE_CLIENT_SECRET di .env
- Pastikan tidak ada extra spaces
- Re-download credentials dari Google Console
Solution:
- Periksa Google avatar URL
- Add fallback default avatar
- Check image loading errors di browser console
// Temporary debug di callback controller
dd([
'google_user' => $googleUser->user,
'email' => $googleUser->getEmail(),
'id' => $googleUser->getId(),
'avatar' => $googleUser->getAvatar(),
]);
Next: WhatsApp Integration untuk setup booking via WhatsApp.