やりたいこと
- いわゆるソーシャルログイン
- とりあえずFacebook(とTwitter)に対応したい
方針
make:authで生成した各種ファイルを利用
php artisan make:auth
準備
当然のことながらLaravelをインストール。.envの設定を済ませておく。
socialiteのインストール
composer require laravel/socialite
その他の設定
providers登録等はいらない(恐らくは5.5からのAuto dicoveryのおかげ)。
実装
以下のような感じでソーシャルログインを実装する。
- SNSログインボタンを設置
- SNSで認証+ユーザー情報を取得する(emailの取得が必須)
- 該当のemailがなければ登録後、ログイン(1回目)
- 該当のemailがあればそのままログイン(2回目以降)
Model
モデルとしてはUser(usersテーブル)を利用するが、標準ではpasswordが必須になっているのでマイグレーションファイルでnullを許可しておく。
(ソーシャルログインでは自社サービスとしてはpasswordを要求しないので)
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
+ $table->string('password')->nullable();
$table->rememberToken();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('users');
}
}
マイグレート実行。
php artisan migrate
Controller
処理はControllerに記述します。以下は最低限度の実装。
ソーシャルログインボタンからのリンク処理とCallback処理の2つ。
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Socialite;
use App\User;
use Auth;
class LoginController extends Controller
{
use AuthenticatesUsers;
protected $redirectTo = '/home';
public function __construct()
{
$this->middleware('guest')->except('logout');
}
//ログインボタンからリンク
public function socialLogin($social)
{
return Socialite::driver($social)->redirect();
}
//Callback処理
public function handleProviderCallback($social)
{
//ソーシャルサービス(情報)を取得
$userSocial = Socialite::driver($social)->stateless()->user();
//emailで登録を調べる
$user = User::where(['email' => $userSocial->getEmail()])->first();
//登録(email)の有無で分岐
if($user){
//登録あればそのままログイン(2回目以降)
Auth::login($user);
return redirect('/home');
}else{
//なければ登録(初回)
$newuser = new User;
$newuser->name = $userSocial->getName();
$newuser->email = $userSocial->getEmail();
$newuser->save();
//そのままログイン
Auth::login($newuser);
return redirect('/home');
}
}
}
Route
必要なルートを定義します。上記メソッドに対応したログインボタンからのリンクとCallbackのURLを定義。
<?php
Route::get('/', function () {
return view('welcome');
});
Auth::routes();
Route::get('/home', 'HomeController@index');
+//ログインボタンからのリンク
+Route::get('/login/{social}', 'Auth\LoginController@socialLogin')->where('social', 'facebook|twitter');
+//コールバック用
+Route::get('/login/{social}/callback', 'Auth\LoginController@handleProviderCallback')->where('social', 'facebook|twitter');
View
大げさなコードを貼り付けていますが、基本はlogin.blade.phpの一部にログインボタンとリンクを追加しただけ。
ボタンの引数にfacebookとかtwitterとか渡してます。
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Login') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('login') }}">
@csrf
+ <div class="form-group row">
+ <label for="name" class="col-sm-4 col-form-label text-md-right">Login With</label>
+ <div class="col-md-6">
+ <a href="{{ url('login/facebook')}}" class="btn btn-social-icon btn-facebook"><i class="fa fa-facebook"></i></a>
+ <a href="{{ url('login/twitter')}}" class="btn btn-social-icon btn-twitter"><i class="fa fa-twitter"></i></a>
+ </div>
+ </div>
<div class="form-group row">
<label for="email" class="col-sm-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required autofocus>
@if ($errors->has('email'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>
@if ($errors->has('password'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<div class="col-md-6 offset-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>
<label class="form-check-label" for="remember">
{{ __('Remember Me') }}
</label>
</div>
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Login') }}
</button>
<a class="btn btn-link" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
あと、Facebookとかのアイコンを利用するためにfont-awesomeを導入しておきます。
共通layoutを定義しているapp.blade.phpの<head>部分の最後に入れておきます。
<head>
.
.
+<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
</head>
config/services.phpと.envの設定
そもそもsocialiteの動作のためには、各SNSサービスのIDやSecret、Callback用のURLが必要になります。
各サービスの開発者向けサイトに行って、取得しておきます。
config/services.php
外部サービスに関する情報はservices.phpに記述します。以下を追記します。
実際の値は.evnから取得するようにしておきます。
'facebook' => [
'client_id' => env('FACEBOOK_API_ID'),
'client_secret' => env('FACEBOOK_API_SECRET'),
'redirect' => env('FACEBOOK_CALLBACKURL'),
],
.env
上記に対応する設定を記述します。
CallbackのURLは下記でngrokで取得した URL+/login/facebook/callback とします。
FACEBOOK_API_ID=23792659xxxxxxxx
FACEBOOK_API_SECRET=9526653842f242201d157402xxxxxxxx
FACEBOOK_CALLBACKURL=https://c3fxxxxx.ngrok.io/login/facebook/callback
Facebook開発者サイトでの情報取得・登録
Callback URLは.envで設定したものと同じである必要があります。
テスト
最近のソーシャルログインや各種アプリ開発は開発環境にもhttpsを要求してきます。要はlocalhostじゃテストしにくい状況となっています。
それを解決するためにngrokを利用します。ngrokはローカルURLをグローバルURLにマップしてくれます。
こちらが大変参考になります。
ngrokのインストール
brew cask install ngrok
ngrokの起動
Laravel(php artisan serve)を利用してるので、8000を指定して起動します。
ngrok http 8000
動作確認
https://hostname/login からソーシャルボタンをクリックしてログインできるか確認します。