33
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Laravel6.0 の認証を Firebase のTwitter Oauth認証で行ったメモ

Last updated at Posted at 2019-10-13

概要

モチベーションは以下。

  • 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

認証の流れ

以下のイメージ。
image.png

事前準備

ツイッターで認証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 の作成

image.png

『i』のついているところが必須項目。
image.png

アクセス権限を Read-only にする。

image.png

firebase の準備

firebaseプロジェクトの準備

image.png
image.png
image.png
image.png
image.png
image.png

基本設定

リソースロケーションのリージョンは東京にしておく。

image.png
image.png

Authentifactionの有効化

image.png

『ログイン方法』のタブから、 Twitter を選択。

image.png

ツイッターの開発アカウントでアプリ連携を確認して入力する

image.png

ツイッターのアプリ連携の『Callback URLs』に、Firebaseの画面に書かれているコールバック URL を記入する
image.png

『ログイン方法』のタブの下のほうにある、承認済ドメインにファイルを配置するサーバのドメインを追加。ローカル開発の場合は、IP番号を追加でもいい。
image.png

秘密鍵のダウンロード

image.png

jsonファイルはLaravelのディレクトリに置いておく。
ここではsecret.jsonの名前で保存したとしておく。

Laravelでの作業

Laravel + Nuxt.js + Firebase でいい感じにTwitterによるソーシャルログインを実現するを参考にして行った。

Firebase用のライブラリの導入

composer require kreait/laravel-firebase

Firebaseの秘密鍵の設定

秘密鍵のダウンロードで保存したjsonのパスを環境変数に記入する。

.env
FIREBASE_CREDENTIALS=secret.json

firebaseのconfigファイルを追加する。

config/firebase.php
<?php

return [
    'credentials' => [
        'file' => base_path(env('FIREBASE_CREDENTIALS')),
        'auto_discovery' => true,
    ],
];

サービスプロバイダの追加

追加したライブラリをサービスプロバイダに追加する。

config/app.config
        /*
         * 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
databasae/migrations/2019_10_12_100647_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->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');
    }
}

あわせてユーザのモデルも修正。

app/User.php
<?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',
    ];
}

環境変数にデータベースの情報を追加する。

.env
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=<laradockで決めたもの>
DB_USERNAME=<laradockで決めたもの>
DB_PASSWORD=<laradockで決めたもの>

テーブルの作成

php artisan migration

ログイン画面の作成

resources/views/login.blade.php
<!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のソーシャルログイン用のボタンを表示するようにしている。

ログイン後画面の作成

resources/views/home.blade.php
<!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>

ルーティング設定

routes/web.php
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
app/Http/Controllers/MyAuth/LoginControler.php
<?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');
        }
    }
}

パスワード検証の削除

app/Auth/MyEloquentUserProvider.php
<?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());
    }
}
app/Providers/AuthServiceProvider.php
<?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']);
        });
    }
}

不要な設定の削除

パスワードを使用しないので、パスワード関連の設定を削除

config/auth.php
<?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 のユーザ認証機能を自前のデータベースと連携する

33
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?