【Laravel 10】Googleログイン機能にドメイン制限を実装してみた
Social Databank Tech Blog Advent Calendar 2023の24日目です。
こんにちは!
ソーシャルデータバンク株式会社でインターンをしている tatata-keshi です。
私は以前、社内向けのWebアプリでログイン機能を作成する際に Laravel Socialite を用いてGoogleログイン機能を実装しました。
その際に、社員さんだけがログインできサービスを利用できるようにするため、ドメイン制限機能を実装しました。
この記事では実装の流れについて解説します!
1. Google Cloud Platform の設定
はじめに、Laravelのシンプルなログイン機能を作成していきます。
Laravel側のコードを書く前に、Google Cloud Platformの コンソール にアクセスし各種設定をしていきます。
1-1. プロジェクトを作成する
Google Cloud Platformにアクセスしたら、まずプロジェクトを作成します。
1-2. クライアントID, クライアントシークレットを取得する
「APIとサービス」→「OAuth同意画面」から、UserTypeで「外部」選択して「作成」をしてください。
「外部」を選択するとすべてのユーザーからのアクセスが可能になります。今回はLaravel側でドメイン制限を実装するのでここは外部を選んで問題ないです!
次に「APIとサービス」→「認証情報」から「+認証情報を作成」をクリックし、「OAuthクライアントID」を選択してください。
アプリケーションの種類で「ウェブアプリケーション」を選択して生成元のURIとリダイレクトURIを入力したら「作成」を押してください。
ここで作成した認証情報のクライアントIDとクライアントシークレットは後ほど使います
2. ログイン機能の作成
ここからはアプリケーション側の実装です。
2-1. Laravel Socialiteをインストールする
はじめにLaravel Socialiteをインストールします。
composer require laravel/socialite
Laravel Socialiteについては以下のドキュメントも参考にしてみてください。Google以外にもFacebookやX(旧Twitter)、GitHubなどでの認証がサポートされています。
2-2. .envの設定をする
.envに先ほど作成したクライアントIDとクライアントシークレット、コールバックURLを追加します。
GOOGLE_CLIENT_ID=**********************
GOOGLE_CLIENT_SECRET=**********************
GOOGLE_REDIRECT_URI="${APP_URL}/login/callback"
その後.envに追記する内容を参照させるためconfig/service.php
に追記します。
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
'scheme' => 'https',
],
'postmark' => [
'token' => env('POSTMARK_TOKEN'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
// ここから追記
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_CALLBACK_URL'),
],
];
2-3. ログイン処理を実装する
ここまでの準備ができたらログインの処理を実装していきます。
はじめにLoginControllerを作成します。処理は以下の通りです。
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
class LoginController extends Controller
{
/**
* OAuthプロバイダへリダイレクトする
*
* @return RedirectResponse
*/
public function redirectToGoogle(): RedirectResponse
{
return Socialite::driver('google')->with(['prompt' => 'select_account'])->redirect();
}
/**
* OAuthプロバイダからのコールバックを処理する
*
* @return RedirectResponse
*/
public function handleGoogleCallback(): RedirectResponse
{
$googleUser = Socialite::driver('google')->user();
$user = User::updateOrCreate([
'google_id' => $googleUser->getId(),
], [
'name' => $googleUser->getName(),
'email' => $googleUser->getEmail(),
'avatar' => $googleUser->getAvatar(),
]);
Auth::login($user);
return redirect()->route('user.index');
}
/**
* ログアウトする
*
* @return RedirectResponse
*/
public function logout(): RedirectResponse
{
Auth::logout();
return redirect()->route('welcome');
}
}
ルーティングはこんな感じです。
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserController;
use App\Http\Controllers\Auth\LoginController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/
Route::get('/', function () {
return view('welcome');
})->name('welcome');
Route::get('/login', [LoginController::class,'show'])->name('login.show');
Route::get('/login/redirect', [LoginController::class,'redirectToGoogle'])->name('login.redirect');
Route::get('/login/callback', [LoginController::class,'handleGoogleCallback'])->name('login.callback');
Route::get('/login/fail', [LoginController::class,'failLogin'])->name('login.fail');
Route::get('/logout', [LoginController::class,'logout'])->name('logout');
Route::middleware('auth')->group(function () {
Route::get('/user/index', [UserController::class,'index'])->name('user.index');
});
3. ドメイン制限の追加
続いてドメイン制限を追加していきます。
3-1. ドメイン制限処理を追加する
先ほど作成したLoginController.php
のhandleGoogleCallback()
にドメイン制限処理を追記していきます。
// 許可するドメインをここに記述
$allowedEmailDomains = ['example.com', 'example.co.jp'];
$userEmailDomain = substr(strrchr($googleUser->getEmail(), "@"), 1);
if (!in_array($userEmailDomain, $allowedEmailDomains)) {
return redirect()->route('login.fail');
}
この書き方だと許可するドメインをコード上にベタ書きしてしまっているので、必要に応じてDBに保存させるなどしてください。
ドメイン制限処理を追加したLoginController.php
が以下のようになります。
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
class LoginController extends Controller
{
/**
* OAuthプロバイダへリダイレクトする
*
* @return RedirectResponse
*/
public function redirectToGoogle(): RedirectResponse
{
return Socialite::driver('google')->with(['prompt' => 'select_account'])->redirect();
}
/**
* OAuthプロバイダからのコールバックを処理する
*
* @return RedirectResponse
*/
public function handleGoogleCallback(): RedirectResponse
{
$googleUser = Socialite::driver('google')->user();
// 許可するドメインをここに記述
$allowedEmailDomains = ['example.com', 'example.co.jp'];
$userEmailDomain = substr(strrchr($googleUser->getEmail(), "@"), 1);
if (!in_array($userEmailDomain, $allowedEmailDomains)) {
return redirect()->route('login.fail');
}
$user = User::updateOrCreate([
'google_id' => $googleUser->getId(),
], [
'name' => $googleUser->getName(),
'email' => $googleUser->getEmail(),
'avatar' => $googleUser->getAvatar(),
]);
Auth::login($user);
return redirect()->route('user.index');
}
/**
* ログイン失敗時の画面を表示する
*
* @return View
*/
public function failLogin(): View
{
return view('login.fail');
}
/**
* ログアウトする
*
* @return RedirectResponse
*/
public function logout(): RedirectResponse
{
Auth::logout();
return redirect()->route('welcome');
}
}
4. サンプルコード
以下サンプルコードになります。
Discussion