laravel ✕ OAuth2.0 Socialiteを使って取得したトークンを使ってAPIを実行してみる

## 概要

Socialiteを使って取得したトークンを用いてGoogleのAPIを実行し情報を取得する方法を簡単に記載する。

## 前提

- コードは下記の内容が完了しているところから修正を行う

https://qiita.com/miriwo/items/2a068f1e57f6ea182de2

- トークンはDBで保持する

## 方法

1. 下記の様なマイグレーションファイルを作成

    ```database/migrations/2025_06_04_054743_create_oauth_tokens_table.php
    <?php
    
    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Support\Facades\Schema;
    
    return new class extends Migration
    {
        /**
         * Run the migrations.
         */
        public function up(): void
        {
            Schema::create('oauth_tokens', function (Blueprint $table) {
                // カラム定義
                $table->id();
                $table->unsignedBigInteger('user_id');
                $table->string('access_token');
                $table->string('refresh_token')->nullable();
                $table->timestamps();
            
                // 外部キー制約
                $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            });
        }
    
        /**
         * Reverse the migrations.
         */
        public function down(): void
        {
            Schema::dropIfExists('oauth_tokens');
        }
    };
    ```

1. 下記のようなモデルを用意

    ```app/Models/OauthToken.php
    <?php
    
    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Factories\HasFactory;
    use Illuminate\Database\Eloquent\Model;
    
    class OauthToken extends Model
    {
        /** @use HasFactory<\Database\Factories\OauthTokenFactory> */
        use HasFactory;
    
        protected $fillable = [
            'user_id',
            'access_token',
            'refresh_token',
        ];
    }
    ```

1. マイグレーションを実行する
1. ルーティングを下記の様に修正

    ```routes/web.php
    <?php
    
    use App\Http\Controllers\OauthController;
    use Illuminate\Support\Facades\Route;
    
    Route::get('/', function () {
        return view('welcome');
    })->name('home');
    
    Route::middleware([
        'auth:sanctum',
        config('jetstream.auth_session'),
        'verified',
    ])->group(function () {
        Route::get('/dashboard', function () {
            return view('dashboard');
        })->name('dashboard');
    });
    
    // Googleの認証ページへのリダイレクト
    Route::get('/auth/redirect', [OauthController::class, 'redirectToGoogle'])->name('google.redirect');
    
    // Googleからのコールバックを受け取る
    Route::get('/auth/callback', [OauthController::class, 'handleGoogleCallback'])->name('google.callback');
    
    Route::get('/google-cloud-storage-infos', [OauthController::class, 'googleCloudStorageInfos'])->name('google.cloud.storage.infs');
    ```

1. OauthControllerを作成して下記のように記載

    ```app/Http/Controllers/OauthController.php
    <?php
    
    namespace App\Http\Controllers;
    
    use App\Models\OauthToken;
    use Illuminate\Http\Request;
    use App\Models\User;
    use Illuminate\Support\Facades\Auth;
    use Illuminate\Support\Facades\Hash;
    use Illuminate\Support\Facades\Log;
    use Illuminate\Support\Str;
    use Laravel\Socialite\Facades\Socialite;
    use Illuminate\Http\RedirectResponse;
    use Illuminate\Support\Facades\DB;
    use Illuminate\Support\Facades\Http;
    
    class OauthController extends Controller
    {
    
        public function __construct(protected OauthToken $oauthToken)
        {
                
        }
    
        /**
         * Googleの認証ページへのリダイレクトを行う
         *
         * @return RedirectResponse Googleの認証ページへのリダイレクトレスポンス
         */
        public function redirectToGoogle(): RedirectResponse
        {
            return Socialite::driver('google')
                ->scopes(['email', 'profile', 'https://www.googleapis.com/auth/cloud-platform.read-only'])
                ->with(['access_type' => 'offline', 'prompt' => 'consent'])
                ->redirect();
        }
    
        /**
         * Googleからのコールバックを受け取る
         *
         * @return RedirectResponse リダイレクトレスポンス
         */
        public function handleGoogleCallback(): RedirectResponse
        {
            try {
                $googleUser = Socialite::driver('google')->user();
                $accessToken = $googleUser->token;
                $refreshToken = $googleUser->refreshToken;
            } catch (\Exception $e) {
                Log::error($e);
                return redirect(route('home'));
            }
        
            // ユーザーが既に存在するか確認
            $user = User::where('email', $googleUser->getEmail())->first();
        
            if (!$user) {
                // ユーザーが存在しない場合、新規作成
                $user = User::create([
                    'name' => $googleUser->getName(),
                    'email' => $googleUser->getEmail(),
                    'google_id' => $googleUser->getId(),
                    'password' => Hash::make(Str::random(16)),
                ]);
            }
        
            // ユーザーをログインさせる
            Auth::login($user);
        
            try {
                DB::beginTransaction();
                    // 同様のuser_idのレコードがあったら削除
                    $this->oauthToken->where('user_id', $user->id)->delete();
            
                    // 新しいトークンを挿入
                    $this->oauthToken->create([
                        'user_id' => $user->id,
                        'access_token' => $accessToken,
                        'refresh_token' => $refreshToken,
                    ]);
                DB::commit();
            } catch (\Exception $e) {
                DB::rollBack();
                Log::error('トークンの保存中にエラーが発生しました', ['exception' => $e]);
                return redirect(route('home'))->with('error', 'トークンの保存中にエラーが発生しました');
            }
        
            // リダイレクト先を指定
            return redirect(route('dashboard'));
        }
    
        public function googleCloudStorageInfos()
        {
            $oauthToken = $this->oauthToken->where('user_id', Auth::user()->id)->first();
            $accessToken = $oauthToken->access_token;
        
            try {
                $response = Http::withToken($accessToken)->get('https://www.googleapis.com/storage/v1/b', [
                    'project' => 'Google CloudのプロジェクトID' // .envで持ちたいね
                ]);
            
                if ($response->successful()) {
                    $buckets = $response->json();
                    return response()->json($buckets);
                } else {
                    Log::error('Google Cloud API request failed', ['response' => $response->body()]);
                    return response()->json(['error' => 'Failed to retrieve Google Cloud resources'], 500);
                }
            } catch (\Exception $e) {
                Log::error('Exception occurred while fetching Google Cloud resources', ['exception' => $e]);
                return response()->json(['error' => 'An error occurred while fetching Google Cloud resources'], 500);
            }
        }
    }
    ```

1. 一旦ログアウトし、`/auth/redirect`にアクセスし、ログイン後`/google-cloud-storage-infos`にアクセスし下記のようにJSONでGoogle Cloud Storageのバケット一覧が表示されることを確認（自身のCloud Storageにはバケットが無いので空で表示された）

    ![CleanShot 2025-06-04 at 16.26.34@2x.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/1fb729d9-e449-422e-903b-2fca5cdd9145.jpeg)