はじめに
LaravelとNuxt.jsのリポジトリを完全に分離してSNSログイン認証を実装してみた。
SocialiteはLaravelリポジトリ内のbladeを使う前提で設計されており、紹介されている記事もこの方法ばかりで、結構苦労した。
Firebaseのログイン認証やAuth0を利用する方法もあるようだが、今回はSocialiteを使用する。
SocialiteをそのままAPIとして流用すると、セッションエラーや、CORSエラーが出る。リダイレクトによるCORSエラーは解決方法がないようなので、リダイレクト先をフロントエンドにし、フロントエンドからクエリをAPIに送るようにする。
主にこちらの記事を参考にしてます。ほぼ同じです。ありがとうございます。
使用環境
- laravel v9.5.1
- Nuxt.js v2.15.7
Google Cloud Platform の登録
登録方法はこちらを参照する。
注意点は、「承認済みのリダイレクト URI」には、フロントエンドのコールバック先を入力する。
ファイルは後ほど作成するが、 http://localhost:3000/login/googleCallback
にしておく。
忘れないうちに、laravelの.envファイルにクライアントIDとクライアントシークレットを保存する。CALLBACKは、「承認済みのリダイレクト URI」に登録したものと同じにする。
GOOGLE_CLIENT_ID=******
GOOGLE_CLIENT_SECRET=******
GOOGLE_CALLBACK=http://localhost:3000/login/googleCallback
Laravel側の設定
users_tableの編集
まず、デフォルトのusersテーブルはpasswordが必須になっている。Googleログインではデータベースにパスワードを保存しないため、nullableの設定にしておく。
アバター画像、プロバイダー、プロバイダー先のIDも保存したいので追加する。
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('password')->nullable();
$table->string('avatar')->nullable();
$table->string('provider')->nullable();
$table->string('provider_id')->nullable()->unique();
$table->rememberToken();
$table->timestamps();
});
}
migrateを実行する。
php artisan migrate
モデルのfillableに先程のカラムを追加する。
protected $fillable = [
'name',
'email',
'password',
'avatar',
'provider',
'provider_id'
];
Socialiteの設定
インストール
composer require laravel/socialite
app.phpにproviderとaliaseを追加する。
'providers' => [
// 中略
Laravel\Socialite\SocialiteServiceProvider::class, // 追加
],
'aliases' => Facade::defaultAliases()->merge([
'Socialite' => Laravel\Socialite\Facades\Socialite::class, // 追加
])->toArray(),
services.phpに追加。
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_CALLBACK'),
],
新しくコントローラーを作成する。
php artisan make:controller SocialController
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Socialite;
use Auth;
Use App\Models\User;
class SocialController extends Controller
{
public function redirect($provider)
{
return Socialite::driver($provider)->redirect()->getTargetUrl();
}
public function Callback($provider){
$userSocial = Socialite::driver($provider)->stateless()->user();
$userAcount = User::where(['email' => $userSocial->getEmail()])->first();
if($userAcount){
$user = User::find($userAcount->getAttribute('id'));
Auth::login($userAcount);
}else{
$user = User::create([
'name' => $userSocial->getName(),
'email' => $userSocial->getEmail(),
'avatar' => $userSocial->getAvatar(),
'provider_id' => $userSocial->getId(),
'provider' => $provider,
]);
}
return [
'user' => $user,
'access_token' => $user->createToken('user')->plainTextToken,
];
}
}
API側でリダイレクトさせると、CORS設定をしていてもエラーが出る。リダイレクトして認証サーバーを経由したときはCORSエラーが出てしまう。これの解決方法はないらしい。
そのため、一旦フロントでURLを受け取り、フロントのドメインからコールバック処理を行うようにする。
redirect関数ではredirect()->getTargetUrl()
でURLのみをレスポンスに返す。
Callback関数では、ユーザー登録を行い、レスポンスでaccess_tokenを返す。
ルートの設定
普通にapi.phpに追加すると、Session store not set on request.
のエラーが出る。
セッション許可を与えるためmiddlewareGroupsを編集。apiグループに追加すると、他のAPIにも影響してしまうため、sessionグループを新たに追加する。
protected $middlewareGroups = [
// 中略
'session' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
],
];
use App\Http\Controllers\Auth\SocialController;
Route::group(['middleware' => ['session']], function () {
Route::get('login/{provider}', [SocialController::class,'redirect']);
Route::get('login/{provider}/callback',[SocialController::class,'callback']);
});
CORSの設定
'allowed_origins' => explode(',', env('ALLOWED_ORIGINS', '')),
ALLOWED_ORIGINS=http://localhost:3000
Nuxt.jsの設定
axiosは入っている前提で省略。
CORSの設定
publicRuntimeConfig: {
axios: {
baseURL: process.env.API_URL,
credentials: true // 追加
},
},
Googleログインボタンの実装
<template>
<v-btn @click="login">Google login</v-btn>
</template>
<script>
export default {
methods: {
async login(provider) {
try {
const response = await this.$axios.$get('login/' + provider)
window.location.href = response
} catch (err) {
console.log(err)
}
},
},
}
</script>
ボタンを押すと、api/login/{provider}
を呼び、リダイレクト先のURLをレスポンスから取得する。
URLはhttps://accounts.google.com/o/oauth2/auth/oauthchooseaccount?client_id=******&redirect_uri=******
の形式。
レスポンスを取得できたら、そのままリダイレクトし、Googleログイン画面を表示させる。
アカウントを選択してログインが成功すると、「承認済みのリダイレクト URI」として登録したhttp://localhost:3000/login/googleCallback
に移動する。
callback先のページ実装
<template>
<div>
認証中
</div>
</template>
<script>
export default {
async mounted () {
try {
const response = await this.$axios.$get('login/google/callback', { params: this.$route.query })
this.$store.commit('setToken', {token: response.access_token})
this.$store.commit('setUser', {user: response.user})
this.$router.replace('/')
} catch (error) {
console.log(error)
}
}
}
</script>
Googleログイン画面から戻ると、URLにパラメータが追加されているので、それをそのままAPIに渡す。
api/login/{provider}/callback
を呼び、ユーザー登録処理を行う。
これでusersテーブルにデータが登録される。
ログイン後の処理は、VuexにAccessTokenを保存する。Vuexについてはもとの参考記事が分かりやすくておすすめです。
参考記事