0. はじめに
大阪のLaravel初学者サウナーこと、kazumakishimoto(@kazuma_dev)です!
Laravel Socialite
のGoogle / Twitter
ソーシャルログインです!
※AWSverは下記記事参照
0-1. 全体の流れ
0-2. 本記事の対象者
-
Laravel Socialite
のGoogle / Twitter
ソーシャルログイン
0-3. 事前準備
-
Google / Twitter
アカウント作成済み(※TwitterAPIは申請が必要) -
grfl
やhoge
はサンプル名なので適宜変更して下さい
0-4. 要件
- GoogleAPI連携
- TwitterAPI連携
- Laravel Socialite実装
0-5. 使用画像のイメージ
1. Google
1-1. プロジェクト作成
1-2. OAuth同意画面
1-3. OAuthクライアントID
2. Twitter
2-1. TwitterAPI申請
- 下記記事を参照にTwitterAPI申請する
2-2. アプリ作成
2-3. OAuth設定
2-4. APIキー/トークン発行
3. Laravel
3-1. Laravel Socialite
local
$ docker-compose exec app bash
# COMPOSER_MEMORY_LIMIT=-1 composer require laravel/socialite
Package manifest generated successfully.
# exit
3-2. config
3-2-1. app.php
config/app.php
'providers' => [
// 略
Laravel\Socialite\SocialiteServiceProvider::class,
],
'aliases' => [
// 略
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
]
3-2-2. services.php
config/services.php
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('APP_URL') . '/login/google/callback',
],
'twitter' => [
'client_id' => env('TWITTER_CLIENT_ID'),
'client_secret' => env('TWITTER_CLIENT_SECRET'),
'redirect' => env('APP_URL') . '/login/twitter/callback',
],
3-3. Route
web.php
# SNSアカウントログイン
Route::prefix('login')->name('login.')->group(function () {
Route::get('/{provider}', 'Auth\LoginController@redirectToProvider')->name('{provider}');
Route::get('/{provider}/callback', 'Auth\LoginController@handleProviderCallback')->name('{provider}.callback');
});
# SNSアカウントユーザー登録
Route::prefix('register')->name('register.')->group(function () {
Route::get('/{provider}', 'Auth\RegisterController@showProviderUserRegistrationForm')->name('{provider}');
Route::post('/{provider}', 'Auth\RegisterController@registerProviderUser')->name('{provider}');
});
3-4. Migration
database/migrations/2014_10_12_000000_create_users_table.php
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('twitter_id')->nullable();
$table->string('name')->unique();
$table->string('age')->nullable();
$table->string('gender')->nullable();
$table->string('avatar')->nullable();
$table->text('introduction')->nullable();
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password')->nullable();
$table->rememberToken();
$table->timestamps();
});
}
3-5. Model
app/Models/User.php
protected $fillable = [
'twitter_id', 'name', 'age', 'gender', 'avatar', 'introduction', 'email', 'password',
];
3-6. Controller
3-6-1. LoginController.php
app/Http/Controllers/Auth/LoginController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
// ゲストユーザー用のユーザーIDを定数として定義
private const GUEST_USER_ID = 1;
// ゲストログイン処理
public function guestLogin()
{
// id=1 のゲストユーザー情報がDBに存在すれば、ゲストログインする
if (Auth::loginUsingId(self::GUEST_USER_ID)) {
return redirect('/');
}
return redirect('/');
}
// SNS認証ページへユーザーをリダイレクト
public function redirectToProvider(string $provider)
{
return Socialite::driver($provider)->redirect();
}
// ログイン
public function handleProviderCallback(Request $request, string $provider)
{
// 認証結果の受け取り
$providerUser = Socialite::driver($provider)->user();
// Google
if ($provider === 'google') {
// Googleから取得したユーザー情報からメールアドレスを取得
$user = User::where('email', $providerUser->getEmail())->first();
// Twitter
} elseif ($provider === 'twitter') {
// Twitterから取得したユーザー情報からユーザーIDを取得
$user = User::where('twitter_id', $providerUser->getId())->first();
}
// ログイン処理
if ($user) {
$this->guard()->login($user, true);
return $this->sendLoginResponse($request);
}
// Google
if ($provider === 'google') {
$data = [
'provider' => $provider,
'email' => $providerUser->getEmail(),
'token' => $providerUser->token,
];
return redirect()->route('register.{provider}', $data);
// Twitter
} elseif ($provider === 'twitter') {
$data = [
'provider' => $provider,
'twitter_id' => $providerUser->getId(),
'token' => $providerUser->token,
'tokenSecret' => $providerUser->tokenSecret,
];
// DBにユーザー情報がなければ作成する
return redirect()->route('register.{provider}', $data);
}
}
}
3-6-2. RegisterController.php
app/Http/Controllers/Auth/RegisterController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Socialite\Facades\Socialite;
use App\Rules\CustomPasswordValidation;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
$messages = [
'regex' => ':attributeに「/」と半角スペースは使用できません。',
];
return Validator::make($data, [
'name' => ['required', 'regex:/^(?!.*\s).+$/u', 'regex:/^(?!.*\/).*$/', 'max:15', 'unique:users'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', new CustomPasswordValidation, 'confirmed'],
],
$messages
);
}
public function showProviderUserRegistrationForm(Request $request, string $provider)
{
$token = $request->token;
$providerUser = Socialite::driver($provider);
// Google
if ($provider === 'google') {
$providerUser = $providerUser->userFromToken($token);
$data = [
'provider' => $provider,
'email' => $providerUser->getEmail(),
'token' => $providerUser->token,
];
return view('auth.social_register', $data);
// Twitter
} elseif ($provider === 'twitter') {
$tokenSecret = $request->tokenSecret;
$providerUser = $providerUser->userFromTokenAndSecret($token, $tokenSecret);
$data = [
'provider' => $provider,
'twitter_id' => $providerUser->getId(),
'token' => $providerUser->token,
'tokenSecret' => $providerUser->tokenSecret,
];
return view('auth.social_register', $data);
}
}
public function registerProviderUser(Request $request, string $provider)
{
// Google
if ($provider === 'google') {
$request->validate([
'name' => ['required', 'string', 'min:1', 'max:15', 'unique:users'],
'token' => ['required', 'string'],
]);
// Twitter
} elseif ($provider === 'twitter') {
$request->validate([
'name' => ['required', 'string', 'min:1', 'max:15', 'unique:users'],
'token' => ['required', 'string'],
'tokenSecret' => ['required', 'string'],
]);
}
$token = $request->token;
$providerUser = Socialite::driver($provider);
// Google
if ($provider === 'google') {
$providerUser = $providerUser->userFromToken($token);
// Twitter
} elseif ($provider === 'twitter') {
$tokenSecret = $request->tokenSecret;
$providerUser = $providerUser->userFromTokenAndSecret($token, $tokenSecret);
}
// Google
if ($provider === 'google') {
$user = User::create([
'name' => $request->name,
'avatar' => asset(config('user.avatar_path.default')),
'email' => $providerUser->getEmail(),
'password' => null,
]);
// Twitter
} elseif ($provider === 'twitter') {
$user = User::create([
'name' => $request->name,
'avatar' => asset(config('user.avatar_path.default')),
'email' => $request->email,
'twitter_id' => $providerUser->getId(),
'password' => null,
]);
}
$this->guard()->login($user, true);
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'avatar' => asset(config('user.avatar_path.default')),
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
}
3-7. View
3-7-1. login.blade.php
resources/views/auth/login.blade.php
<div>
<a href="{{ route('login.{provider}', ['provider' => 'google']) }}" class="btn btn-block btn-danger mb-4 col-lg-8 col-md-9 col-sm-10 col-xs-12"><i class="fab fa-google mr-1"></i>Googleでログイン</a>
</div>
<div>
<a href="{{ route('login.{provider}', ['provider' => 'twitter']) }}" class="btn btn-block btn-info mb-4 col-lg-8 col-md-9 col-sm-10 col-xs-12"><i class="fab fa-twitter mr-1"></i>Twitterでログイン</a>
</div>
3-7-2. register.blade.php
resources/views/auth/register.blade.php
<div>
<a href="{{ route('login.{provider}', ['provider' => 'google']) }}" class="btn btn-block btn-danger mb-4"><i class="fab fa-google mr-1"></i>Googleで登録</a>
</div>
<div>
<a href="{{ route('login.{provider}', ['provider' => 'twitter']) }}" class="btn btn-block btn-info mb-4"><i class="fab fa-twitter mr-1"></i>Twitterで登録</a>
</div>
3-7-3. social_register.blade.php
resources/views/auth/social_register.blade.php
@extends('app')
@section('title', 'ユーザー登録 - grfl')
@section('content')
@include('nav')
<main>
<div class="container">
<div class="row">
<div class="mx-auto col col-12 col-sm-11 col-md-9 col-lg-7 col-xl-6">
<div class="card mt-3">
<div class="card-body text-center">
<h2 class="h3 card-title text-center mt-2">ユーザー登録</h2>
@include('error_card_list')
<div class="card-text">
<form method="POST" action="{{ route('register.{provider}', ['provider' => $provider]) }}">
@csrf
<input type="hidden" name="token" value="{{ $token }}">
@if ($provider === 'twitter')
<input type="hidden" name='tokenSecret' value="{{ $tokenSecret }}">
@endif
<div class="md-form">
<label for="name">ユーザー名</label>
<input class="form-control" type="text" id="name" name="name" required>
<small>英数字3〜16文字(登録後の変更はできません)</small>
</div>
<div class="md-form">
<label for="email">メールアドレス</label>
@if ($provider === 'google')
<input class="form-control" type="text" id="email" name="email" value="{{ $email }}" disabled>
@elseif ($provider === 'twitter')
<input class="form-control" type="text" id="email" name="email" required>
@endif
</div>
<button class="btn btn-block blue-gradient mt-2 mb-2" type="submit">ユーザー登録</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
@include('footer')
@endsection
3-8. 環境変数
ec2-user
$ ssh ec2-user@xx.xxx.xxx.xxx -i ~/.ssh/hoge.pem
$ cd /var/www/grfl/src
$ sudo vi .env
.env
# Google Auth
+ GOOGLE_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com
+ GOOGLE_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx
# Twitter Auth
+ TWITTER_CLIENT_ID=TwitterAPI Key
+ TWITTER_CLIENT_SECRET=TwitterAPI Secret Key
+ TWITTER_CALLBACKURL=https://grfl.work/login/twitter/callback
ec2-user
$ php artisan config:cache
補足
開発環境(FW/ツールのバージョンなど)
ツール | バージョン |
---|---|
Vue.js | 2.6.14 |
jQuery | 3.4.1 |
PHP | 7.4.1 |
Laravel | 6.20.43 |
MySQL | 5.7.36 |
Nginx | 1.18.0 |
Composer | 2.0.14 |
npm | 6.14.6 |
Git | 2.33.1 |
Docker | 20.10.11 |
docker-compose | v2.2.1 |
PHPUnit | 8.0 |
CircleCI | 2.1 |
heroku | 7.59.4 |
MacBook Air | M1,2020 |
macOS | Monterey 12.3 |
Homebrew | 3.3.8 |
ディレクトリ構造
【ルートディレクトリ】
├─ .circleci
│ └─ config.yml
├─ aws / CloudFormation
│ └─ ec2.yml
├─ docker
│ └─ mysql
│ └─ nginx
│ └─ php
│ └─ phpmyadmin
├─ src
│ └─ 【Laravelのパッケージ】
└─ docker-compose.yml
Reference