🐘
Laravel github認証(token)
環境
- Laravel 8.77.1
- socialite
- PHP 8.1.1
- MySQL
- Next.js 12.0.7
バックエンド:https://localhost
フロントエンド:http://localhost:3000
docker
懸念
cookieのapi_tokenはhttponlyにできない(JSで読み込めなくなりAPIを投げるときにセットできない)のでセキュリティ上の懸念はあります。
下記最低限の実装です。
バックエンド
socialiteのインストールを行う
$ composer require laravel/socialite
$guardsのapiのdriverをtokenにする
app/config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => true,
],
],
ログイン失敗時どこにリダイレクトするかを修正
app/app/Http/Middleware/Authenticate.php
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return 'http://localhost:3000';
}
}
ルーティング設定
- /login/{provider}(githubの認証ログイン)
- /auth/{provider}/callback(githubからのコールバック)
- /me(ログインユーザの取得)
app/routes/api.php
//認証成功すればユーザ情報取得 失敗すれば401が返る
Route::middleware('auth:api')->get('/me', function (Request $request) {
return $request->user();
});
Route::get('/login/{provider}', [OAuthController::class, 'getProviderOAuthURL'])
->where('provider', 'github')->name('oauth.request');
Route::get('/auth/{provider}/callback', [OAuthController::class, 'handleProviderCallback'])
->where('provider', 'github')->name('oauth.callback');
app/app/Http/Controllers/Auth/OAuthController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Laravel\Socialite\Facades\Socialite;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use App\Enums\Provider;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Cookie;
class OAuthController extends Controller
{
/**
* (各認証プロバイダーの)OAuth認可画面URL取得API
* @param string $provider 認証プロバイダーとなるサービス名
* @return \Illuminate\Http\JsonResponse
*/
public function getProviderOAuthURL(string $provider)
{
$redirectUrl = Socialite::driver($provider)
->scopes(['read:user', 'public_repo'])
->redirect()
->getTargetUrl();
return response()->json([
'redirect_url' => $redirectUrl,
]);
}
public static function generateToken()
{
return Str::random(80);
}
/**
* ソーシャルログイン処理
* @return App\User
*/
public static function handleProviderCallback()
{
$githubUser = Socialite::driver('github')->stateless()->user();
$user = User::where('github_id', $githubUser->id)->first();
$token = self::generateToken();
if ($user) {
$user->update([
'name' => $githubUser->name,
'email' => $githubUser->email,
'bio' => $githubUser->user['bio'],
'avatar_url' => $githubUser->user['avatar_url'],
'github_token' => $githubUser->token,
'github_refresh_token' => $githubUser->refreshToken,
'api_token' => hash('sha256', $token)
]);
} else {
$user = User::create([
'name' => $githubUser->name,
'email' => $githubUser->email,
'provider' => Provider::GITHUB,
'bio' => $githubUser->user['bio'],
'avatar_url' => $githubUser->user['avatar_url'],
'github_id' => $githubUser->id,
'github_token' => $githubUser->token,
'github_refresh_token' => $githubUser->refreshToken,
'api_token' => hash('sha256', $token)
]);
}
Auth::login($user);
$cookie = cookie('api_token', $token, '10000000', null, null, null, false);
return redirect('http://localhost:3000')->cookie($cookie);
}
}
app/database/migrations/2014_10_12_000000_create_users_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name')->nullable();
$table->string('email');
//todo:github以外のログイン認証も追加予定
$table->enum('provider', ['github']);
$table->string('github_id');
$table->string('avatar_url')->nullable();
$table->text('bio')->nullable();
$table->text('github_token');
$table->text('github_refresh_token')->nullable();
$table->rememberToken();
$table->string('api_token', 80)
->unique()
->nullable()
->default(null);
$table->timestamp('created_at')->default(\DB::raw('CURRENT_TIMESTAMP'));
$table->timestamp('updated_at')->default(\DB::raw('CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP'));
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
migrateを実行(テーブル生成)
$ php artisan migrate
フロント
src/pages/index.tsx
import type { NextPage } from 'next'
import axios from 'axios'
const handleSocialLoginRequest = async (provider: any) => {
const { data } = await axios.get(`https://localhost/api/login/${provider}`);
console.log("data:",data.redirect_url);
window.location.href = data.redirect_url;
}
const Home: NextPage = () => {
return (
<>
<button type="submit" onClick={() => {handleSocialLoginRequest('github');}}>github login</button>
</>
)
}
export default Home
src/pages/me.tsx
import type { NextPage } from 'next'
import axios from 'axios'
import { parseCookies, setCookie, destroyCookie } from 'nookies'
const handleSocialLoginRequest = async () => {
const cookies = parseCookies()
const { data } = await axios.get('https://localhost/api/me', {
headers: {
'Authorization' : 'Bearer ' + cookies['api_token']
},
});
console.log("data:",data);
return data;
}
const Me: NextPage = () => {
const data = handleSocialLoginRequest();
return (
<>
<div>me!!</div>
</>
)
}
export default Me
Discussion