概要
モチベーションは以下。
- Laravel初心者なので、機能に色々触れてみたい。
- 最近流行のOAuthを使ってみたい。
- せっかくgcpを触っているのだから、firebaseも触ってみたい。
- パスワード入れるの面倒だからツイッターで入りたい
セキュリティホール見つけるなどしたら教えてください。
構成
プロジェクトフォルダはLaravel6.0をGoogle Cloud PlatformのGoogle App EngineのPHP7.2環境で動かしたメモ でのフォルダ構成と同じ。
今回触ったファイルを主にあげる。
- .env
- secret.json
- app
- User.php
- Auth
- MyEloquentUserProvider.php
- Http
- Controller
- MyAuth
- LoginController.php
- Providers
- AuthServiceProvider.php
- config
- auth.php
- firebase.php
- databasae
- migrations
- 2019_10_12_100647_create_users_table.php
- resources
- views
- login.blade.js
- routes
- web.php
認証の流れ
事前準備
ツイッターで認証APIを作成
Twitter 開発者登録に使う英語例文集を参考に、Twitter 社へ申請。
# The core use case, intent, or business purpose for your use of the Twitter APIs
I want to use Twitter's API to authenticate users in my application.
My application is the website to create charactersheet for Table Talk RPG.
# If you intend to analyze Tweets, Twitter users, or their content, share details about the analyses you plan to conduct and the methods or techniques
No, I have no plan to analyze Tweets at all.
# If your use involves Tweeting, Retweeting, or liking content, share how you will interact with Twitter users or their content
No. A User can tweet own charactersheet. However, My application doesn't use Twitter API to do this. I just add the "tweet" button on my site.
# If you’ll display Twitter content off of Twitter, explain how and where Tweets and Twitter content will be displayed to users of your product or service, including whether Tweets and Twitter content will be displayed at row level or aggregated
I have no plan to display Twitter data on my site or outside of twitter. I use Twitter API to authenticate users.
App の作成
アクセス権限を Read-only にする。
firebase の準備
firebaseプロジェクトの準備
基本設定
リソースロケーションのリージョンは東京にしておく。
Authentifactionの有効化
『ログイン方法』のタブから、 Twitter を選択。
ツイッターの開発アカウントでアプリ連携を確認して入力する
ツイッターのアプリ連携の『Callback URLs』に、Firebaseの画面に書かれているコールバック URL を記入する
『ログイン方法』のタブの下のほうにある、承認済ドメインにファイルを配置するサーバのドメインを追加。ローカル開発の場合は、IP番号を追加でもいい。
秘密鍵のダウンロード
jsonファイルはLaravelのディレクトリに置いておく。
ここではsecret.jsonの名前で保存したとしておく。
Laravelでの作業
Laravel + Nuxt.js + Firebase でいい感じにTwitterによるソーシャルログインを実現するを参考にして行った。
Firebase用のライブラリの導入
composer require kreait/laravel-firebase
Firebaseの秘密鍵の設定
秘密鍵のダウンロードで保存したjsonのパスを環境変数に記入する。
FIREBASE_CREDENTIALS=secret.json
firebaseのconfigファイルを追加する。
<?php
return [
'credentials' => [
'file' => base_path(env('FIREBASE_CREDENTIALS')),
'auto_discovery' => true,
],
];
サービスプロバイダの追加
追加したライブラリをサービスプロバイダに追加する。
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
+ Kreait\Laravel\Firebase\ServiceProvider::class, // ←追加
],
認証用のテーブル作成
php artisan make:migration create_users_table --create=users
<?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->bigIncrements('id');
$table->string('firebase_uid', 255)->unique();
$table->string('name', 255);
$table->string('twitter_screen_name', 255)->nullable();
$table->string('twitter_profile_image_url_https', 1024)->nullable();
$table->timestamps();
$table->rememberToken();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
あわせてユーザのモデルも修正。
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
public $firebase_uid;
public $twitter_screen_name;
public $twitter_profile_image_url_https;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'twitter_screen_name','twitter_profile_image_url_https', 'firebase_uid'
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
環境変数にデータベースの情報を追加する。
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=<laradockで決めたもの>
DB_USERNAME=<laradockで決めたもの>
DB_PASSWORD=<laradockで決めたもの>
テーブルの作成
php artisan migration
ログイン画面の作成
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="ログインテストページ" />
<meta name="keywords" content="TRPG,開発,ツール" />
<meta name="robots" content="index" />
<title>ログインテストページ</title>
<link href="https://www.gstatic.com/firebasejs/ui/4.2.0/firebase-ui-auth.css"rel="stylesheet"/>
<script src="https://www.gstatic.com/firebasejs/6.3.5/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/6.3.5/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/ui/4.2.0/firebase-ui-auth__ja.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script type="text/javascript">
firebase.initializeApp({
"apiKey": "YOUR_API_KEY",
"projectId": "whitemaptools",
"authDomain": "whitemaptools.firebaseapp.com"
});
// FirebaseUI config.
var uiConfig = {
signInSuccessUrl: '/callback',
callbacks: {
signInSuccessWithAuthResult : (authResult,callbackUrl)=>{
// Twitterでのサインインに成功したら、Laravelのログインを呼び出す
var user = authResult.user;
var twitterUser = authResult.additionalUserInfo;
var twitter_screen_name = twitterUser.profile.screen_name;
var twitter_profile_image_url_https = twitterUser.profile.profile_image_url_https;
(async ()=>{
const idToken = await user.getIdToken(true);
document.getElementById('token').value = `${idToken}`;
document.getElementById('twitter_screen_name').value = `${twitter_screen_name}`;
document.getElementById('twitter_profile_image_url_https').value = `${twitter_profile_image_url_https}`;
document.getElementById('loginform').submit();
return;
})();
return false;
},
},
signInOptions: [
firebase.auth.TwitterAuthProvider.PROVIDER_ID,
],
tosUrl: 'agreement',
privacyPolicyUrl: function() {
window.location.assign('privacy-policy');
}
};
var ui = new firebaseui.auth.AuthUI(firebase.auth());
ui.start('#firebaseui-auth-container', uiConfig);
</script>
</head>
<body>
<h1>ログイン</h1>
<div id="firebaseui-auth-container"></div>
<form id="loginform" action="/login" method="post" style="display:none">
{{ csrf_field() }}
<input id="token" name="token">
<input id="twitter_screen_name" name="twitter_screen_name">
<input id="twitter_profile_image_url_https" name="twitter_profile_image_url_https">
</form>
</body>
</html>
#firebaseui-auth-container
のdiv要素を指定して、firebaseのソーシャルログイン用のボタンを表示するようにしている。
ログイン後画面の作成
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
こんにちは!
@if(Auth::check())
{{\Auth::user()->name}} さん
@else
ゲストさん <br />
<a href="/login">ログイン</a>
@endif
</body>
</html>
ルーティング設定
Route::get('/login', function () {
return view('login');
});
Route::post('/login', 'MyAuth\LoginController@authenticate');
Route::get('/home', function () {
return view('home');
});
ログイン用のコントローラ作成
php artisan make:controller MyAuth/LoginController --invokable
<?php
namespace App\Http\Controllers\MyAuth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
/**
* @var Firebase
*/
private $auth;
private $logger;
/**
* コンストラクタインジェクションで $firebase を用意
* @param Firebase $firebase
*/
public function __construct(\Kreait\Firebase\Auth $auth, \Psr\LOg\LoggerInterface $logger)
{
$this->auth = $auth;
$this->middleware('guest')->except('logout');
$this->logger = $logger;
}
/**
* 認証を処理する
*
* @param \Illuminate\Http\Request $request
*
* @return Response
*/
public function authenticate(Request $request)
{
$id_token = $request->input('token');
try {
$verifiedIdToken = $this->auth->verifyIdToken($id_token);
} catch (InvalidToken $e) {
$logger->error("invalidToken", $e->toString());
return response()->json([
'error' => $e->toString(),
]);
}
$uid = $verifiedIdToken->getClaim('sub');
$credentials = ['firebase_uid'=> $uid];
$firebase_user = $this->auth->getUser($uid);
$user = \App\User::firstOrCreate(
['firebase_uid' => $uid],
['name' => $firebase_user->displayName,
'twitter_screen_name' => $request->input('twitter_screen_name'),
'twitter_profile_image_url_https' => $request->input('twitter_profile_image_url_https')]
);
if (Auth::attempt($credentials)) {
// 認証に成功した
// return redirect()->intended('home');
return redirect('/home');
}else{
abort(401, 'Unauthorixed');
}
}
}
パスワード検証の削除
<?php
namespace App\Auth;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
class MyEloquentUserProvider extends EloquentUserProvider
{
public function validateCredentials(UserContract $user, array $credentials)
{
// パスワード認証しない
return true;
// ハッシュ値による認証
// return $this->hasher->check($plain, $user->getAuthPassword());
}
}
<?php
namespace App\Providers;
use App\Auth\MySessionGuard;
use App\Auth\MyEloquentUserProvider;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Auth;
use \Psr\LOg\LoggerInterface;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot(LoggerInterface $logger)
{
$this->registerPolicies();
Auth::provider('my_eloquent', function($app, array $config) {
return new MyEloquentUserProvider($app['hash'], $config['model']);
});
}
}
不要な設定の削除
パスワードを使用しないので、パスワード関連の設定を削除
<?php
return [
'defaults' => [
'guard' => 'web',
- 'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],
'providers' => [
'users' => [
- 'driver' => 'eloquent',
+ 'driver' => 'my_eloquent',
'model' => App\User::class,
],
],
- 'passwords' => [
- 'users' => [
- 'provider' => 'users',
- 'table' => 'password_resets',
- 'expire' => 60,
- ],
- ],
];
これで、firebaseと連携したログインができるようになっている、はず。
参考
Twitter 開発者登録に使う英語例文集
面倒なログイン機能の実装は Firebase Authentication に丸投げしよう
firebase ui
Guard
【PHP】新 TwitterOAuth でログイン機能を実装する
Firebase Admin SDK for PHP Authentication
Laravel のログイン認証の基本(Authentication)を完全理解する
Laravel 5.7 でプレーンなパスワード認証を行う
既存の user マイグレーションファイルを書き換えてみた
Laravel 公式
Laravel 6.0 ログイン機能を実装する
Laravel 6.0 ログイン機能を実装する
【Laravel + JavaScript】QR コードで自動ログインする機能をつくる(ダウンロード可)
Laravel + Nuxt.js + Firebase でいい感じに Twitter によるソーシャルログインを実現する
Nuxt.js と Laravel を使って Twitter ログイン機能を実装する
皆見飽きただろう Nuxt.js with Firebase 入門 認証編
Nuxt/Vuex で Firebase Authentication を使ってユーザー認証機能を作る
Firebase Auth を Nuxt + Rails の自前サービス に導入してみた
モダンな技術を全く知らない SIer5 年目が Web サービスを作ることになったため 0 から勉強する ~Laravel jwt-auth + Nuxt 編~
LaravelAPI + Nuxt で MultiAuth を実装する
vue.js (Nuxt.js)と laravelPassport を使って SPA でのログイン機能を実装してみた
Firebase と Laravel の連携検証してその先の DALI-KNX を目指す
[Laravel] Firebae の ID トークンをバックエンドで確認する
Laravel Firebase Auth *
laravel-firebase
[Laravel] 6.0.4 がリリースされました
ファサードと DI テストについて
Laradock の Nginx を SSL 化する
コピペで OK!ローカル環境に HTTPS を導入する(nginx 編)
Firebase で作った Web サービスを 3 ヶ月運用してみて、ハマったこと・知っておきたかったこと
Rails+Firebase認証のサンプルアプリ
firebase-ui web
Firebase Auth のユーザ認証機能を自前のデータベースと連携する