🐽
Laravel + Inertia なアンケートサービスにGoogleログイン(Socialite)を後付けした話

個人開発中のサービスで Googleログインを初めて実装した ので、
そのときのメモを簡易的にまとめておきます。
今回は Laravel + Inertia + Vue 構成です。
「とりあえず動かす」→「既存ユーザと紐付ける」までをゴールにしています。
作成したのはこちらのサービスです(宣伝!)
Laravel + Inertia で作ったアンケートサービス『リクエスとん』です。
触ってもらえるととても喜びます 🙏
Laravel Socialite を導入
composer require laravel/socialite
config/services.php に Google を追加
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_REDIRECT_URI'),
],
.env に追加(※値は伏せる)
GOOGLE_CLIENT_ID=xxxx
GOOGLE_CLIENT_SECRET=xxxx
GOOGLE_REDIRECT_URI=https://your-domain.com/auth/google/callback
# ローカル用
# GOOGLE_REDIRECT_URI=http://localhost:8000/auth/google/callback
Googleログイン用コントローラ作成
php artisan make:controller Auth/GoogleLoginController
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Hash;
class GoogleLoginController extends Controller
{
public function redirect()
{
// Google認証画面へ
return Socialite::driver('google')->redirect();
}
public function callback()
{
// stateless() はローカル環境対策
$googleUser = Socialite::driver('google')->stateless()->user();
// ① google_id で検索
$user = User::where('google_id', $googleUser->id)->first();
if (!$user) {
// ② email で既存ユーザ確認
$user = User::where('email', $googleUser->email)->first();
if ($user) {
// ③ 既存ユーザに Google ID を紐付け
$user->update([
'google_id' => $googleUser->id,
]);
} else {
// ④ 完全新規ユーザ
$user = User::create([
// Googleの表示名は本名の可能性があるため仮名を生成
'name' => 'リクエスとん' . substr(Str::uuid(), 0, 6),
'email' => $googleUser->email,
'google_id' => $googleUser->id,
'password' => Hash::make(Str::random(40)),
]);
}
}
Auth::login($user);
return redirect()->route('dashboard');
}
}
Userテーブルに google_id を追加
return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table
->string('google_id')
->nullable()
->unique()
->after('email')
->comment('Google OAuth ID');
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropUnique(['google_id']);
$table->dropColumn('google_id');
});
}
};
Userモデル修正
protected $fillable = [
'name',
'email',
'password',
'last_login_at',
'google_id',
'is_delete',
];
protected $hidden = [
'email',
'password',
'remember_token',
'last_login_at',
'google_id',
'is_delete',
];
// Googleログイン済みかどうか判定用
protected $appends = ['has_google_login'];
public function getHasGoogleLoginAttribute()
{
return !is_null($this->google_id);
}
web.php にルーティング追加
Route::get('/auth/google', [GoogleLoginController::class, 'redirect'])
->name('auth.google');
Route::get('/auth/google/callback', [GoogleLoginController::class, 'callback']);
フロント(ログイン / 登録画面)
<div class="mt-8 flex justify-center">
<a :href="route('auth.google')" class="gsi-material-button w-full max-w-[300px]">
<div class="gsi-material-button-state"></div>
<div class="gsi-material-button-content-wrapper">
<div class="gsi-material-button-icon">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"
xmlns:xlink="http://www.w3.org/1999/xlink" style="display: block;">
<path fill="#EA4335"
d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z">
</path>
<path fill="#4285F4"
d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z">
</path>
<path fill="#FBBC05"
d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z">
</path>
<path fill="#34A853"
d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z">
</path>
<path fill="none" d="M0 0h48v48H0z"></path>
</svg>
</div>
<span class="gsi-material-button-contents">Googleで続行</span>
<span style="display: none;">Googleで続行</span>
</div>
</a>
</div>
<div class="flex items-center my-6">
<div class="flex-1 border-t border-gray-200"></div>
<span class="px-3 text-xs text-gray-400">または</span>
<div class="flex-1 border-t border-gray-200"></div>
</div>
<!-- メールやその他のログイン方法(省略) -->
ボタンのコードは公式ガイドラインから取得
CSSは別ファイルに切り出し。
resources/css/app.css
@import './google-button.css'; // <- 追加.
@tailwind base;
@tailwind components;
@tailwind utilities;
ログイン方法による分岐(Inertia)
<script setup>
import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue'
const page = usePage()
const user = computed(() => page.props.auth.user)
const hasGoogleLogin = computed(() => user.value?.has_google_login)
</script>
<template>
<div v-if="hasGoogleLogin">
Googleログイン中
</div>
<div v-else>
その他のログイン方法
</div>
</template>
Google Cloud 側の設定
Google Cloud Console で以下を設定。
- APIとサービス → 認証情報
- OAuth クライアント ID を作成
- 種別:ウェブアプリ
- リダイレクト URI に
/auth/google/callback を指定
.env の GOOGLE_REDIRECT_URI と 完全一致 させるのが重要。
まとめ
- Socialite を使えば実装自体はかなり素直
- 既存ユーザとの email紐付け を入れておくとUXが良い
- Googleの表示名は本名率高めなので注意
- stateless() はローカル環境で特に助かる
「とりあえずGoogleログインを入れたい」場合の参考になれば。
Discussion