Google OAuth Setup - luckydeva03/barbershop_app GitHub Wiki

๐Ÿ” Google OAuth Setup

Panduan lengkap setup Google OAuth untuk login dengan akun Google.

๐Ÿ“‹ Prerequisites

  • Google Account
  • Google Cloud Console access
  • Domain atau localhost untuk testing

๐Ÿš€ Step-by-Step Setup

1. Google Cloud Console Setup

A. Create New Project

  1. Buka Google Cloud Console
  2. Klik "Select Project" โ†’ "New Project"
  3. Isi Project Name: Barbershop App OAuth
  4. Klik "Create"

B. Enable Google+ API

  1. Di project baru, buka "APIs & Services" โ†’ "Library"
  2. Search "Google+ API" atau "People API"
  3. Klik "Enable"

C. Configure OAuth Consent Screen

  1. Buka "APIs & Services" โ†’ "OAuth consent screen"
  2. Pilih "External" (untuk testing) atau "Internal" (untuk organisasi)
  3. 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
  1. Scopes: Tambahkan scope berikut:

    • email
    • profile
    • openid
  2. Test users (untuk mode testing):

    • Tambahkan email yang akan digunakan untuk testing

D. Create OAuth 2.0 Credentials

  1. Buka "APIs & Services" โ†’ "Credentials"
  2. Klik "+ Create Credentials" โ†’ "OAuth 2.0 Client IDs"
  3. Pilih "Web application"
  4. 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
  1. Klik "Create"
  2. Simpan Client ID dan Client Secret

2. Laravel Configuration

A. Install Laravel Socialite

composer require laravel/socialite

B. Environment Configuration

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

C. Config Services

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'),
    ],
];

3. Database Migration

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();
    });
}

4. Model Configuration

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';
    }
}

5. Controller Implementation

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.');
    }
}

6. Routes Configuration

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');
});

7. Frontend Integration

Login Page Button

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>

User Dashboard - Account Linking

<!-- 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>

8. Profile Photo Display

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>

๐Ÿ”ง Advanced Configuration

1. Custom Scopes

// Request additional permissions
return Socialite::driver('google')
    ->scopes(['email', 'profile', 'phone']) // Add phone if needed
    ->redirect();

2. State Parameter untuk Security

// Add state for CSRF protection
return Socialite::driver('google')
    ->with(['state' => Str::random(40)])
    ->redirect();

3. Environment-Specific Settings

// 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',
],

๐Ÿ”’ Security Best Practices

1. Validate Email Domain

// 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.');
}

2. Rate Limiting

// app/Http/Kernel.php
'google-auth' => [
    'throttle:5,1', // 5 attempts per minute
],

3. Audit Logging

// Log OAuth activities
Log::info('Google OAuth login', [
    'user_id' => $user->id,
    'google_id' => $googleUser->getId(),
    'email' => $googleUser->getEmail(),
    'ip' => request()->ip(),
]);

๐Ÿงช Testing

1. Test Google OAuth Flow

// 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');
    }
}

2. Manual Testing Checklist

  • 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

๐Ÿšจ Troubleshooting

Common Issues:

1. "Error 400: redirect_uri_mismatch"

Solution:
- Cek Authorized redirect URIs di Google Console
- Pastikan URL exact match (termasuk http/https)
- Periksa .env GOOGLE_REDIRECT_URL

2. "Error 403: access_denied"

Solution:
- User tidak ada di test users list (mode testing)
- OAuth consent screen belum approved
- Domain tidak terdaftar di authorized domains

3. "Invalid client_id or client_secret"

Solution:
- Periksa GOOGLE_CLIENT_ID di .env
- Periksa GOOGLE_CLIENT_SECRET di .env
- Pastikan tidak ada extra spaces
- Re-download credentials dari Google Console

4. Profile photo tidak muncul

Solution:
- Periksa Google avatar URL
- Add fallback default avatar
- Check image loading errors di browser console

Debug Mode:

// Temporary debug di callback controller
dd([
    'google_user' => $googleUser->user,
    'email' => $googleUser->getEmail(),
    'id' => $googleUser->getId(),
    'avatar' => $googleUser->getAvatar(),
]);

๐Ÿ“š Additional Resources


Next: WhatsApp Integration untuk setup booking via WhatsApp.

โš ๏ธ **GitHub.com Fallback** โš ๏ธ