【Laravel + JavaScript】QRコードで自動ログインする機能をつくる(ダウンロード可)

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、嬉しいことにこのブログを始めてからご連絡をいただいた訪問ユーザー様と直接お話をさせていただくことも増えてきたのですが、やはりそういった場合には「そうか、じゃあこんなこともできるかも!」という新しいアイデアに気づかせてもらうことあったりします。

そして、今回の話題の出発点になったのが、Laravel + JSでバーコードを読み取る(ダウンロード可)という記事なんですが、これは商品には必ずといっていいほどついている「バーコード」をJavaScriptで読み取るという内容になってます。

ただ、スマートフォンが普及しだしたころからもう1つ存在感を出してきた「読み取りコード」がありますよね。

そうです。

QRコード

ですね。

※ ↓↓↓こういうやつです。

QRコードは縦と横方向に情報をもたせることができるので、情報量がバーコードより多くすることができる、いわば「発展系バーコード」ですが、今回はこのQRコードを読み取ることで自動ログインができたら面白いなと思いサンプルを作ってみることにしました。

(例えば、社員さんがQRコードを配布されていてPCやスマホのカメラに見せるだけでログインできるようなシステムを想定しています)

ぜひ皆さんのお役に立てると嬉しいです😊✨
最後に今回のソースコード一式をダウンロードすることができます。

開発環境: Laravel 5.8、Vue 2.6、Google Chrome 75

前提として

この記事の前提として、Laravelのログイン機能がインストール済みであるものとします。もしまだの方は以下の記事を参考にして準備しておいてください。

【Laravel5.6】インストール直後にやること3点

※ 注意:Google Chromeでテストする場合、ローカルであってもウェブカメラにアクセスするにはHTTPS環境である必要があります。もしまだHTTPSを導入していない方は以下を参考にしてください。

コピペでOK!ローカル環境にHTTPSを導入する(nginx編)

自動ログインの手順

今回、自動ログインを実現する手順は次のとおりになってます。

  1. JavaScriptでQRコードを読み取る
  2. 読み取ったデータ(UUID)を持ったユーザーを探す
  3. 見つかればログインする

こうすることでめんどうなログインフォームへの入力をショートカットできますし、月一回強制的にUUID(他とかぶらないID番号)を変更してQRコードを新しく発行するようにすれば、セキュリティも向上すると思います。

DB内の準備

usersテーブルにUUIDを保存するフィールドを追加する

では、QRコードで読み取る文字列(UUID)を保存しておくフィールドをusersテーブルに追加しておきましょう。

以下のコマンドでマイグレーションを作成します。

php artisan make:migration add_uuid_to_users_table

すると、database/migrations/****_**_**_******_add_uuid_to_users_table.phpというファイルが作成されますので、このファイルを開いて以下のように変更します。

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddUuidToUsersTable extends Migration
{
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('uuid')
                ->nullable()
                ->after('remember_token');
        });
    }

    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('uuid');
        });
    }
}

変更したらマイグレーションを実行。

php artisan migrate

実行するとテーブルは次のようになります。

テストユーザーを作成する

後で実際にログインのテストをしますのでSeederを使ってテストデータを作成します。以下のコマンドを実行してください。

php artisan make:seed UsersTableSeeder

database/seeds/UsersTableSeeder.phpが作成されるので、run()内に以下のコードを追加します。

public function run()
{
    $names = [
        'taro' => '太郎',
        'jiro' => '次郎',
        'saburo' => '三郎',
        'shiro' => '四郎',
        'goro' => '五郎',
        'rokuro' => '六郎',
        'shichiro' => '七郎',
        'hachiro' => '八郎',
        'kuro' => '九郎'
    ];

    foreach ($names as $name_en => $name_jp) {

        \App\User::create([
            'name' => $name_jp,
            'email' => $name_en .'@example.com',
            'password' => bcrypt('xxxxxxxx'),
            'uuid' => (string) \Str::uuid()  // ここがUUID
        ]);

    }
}

変更したらUsersTableSeederが有効になるようにdatabase/seeds/DatabaseSeeder.phprun()内に登録します。(コメントアウトを外すだけでOKです)

public function run()
{
     $this->call(UsersTableSeeder::class);
}

ではSeederでテストユーザーを登録しましょう。(今回はテーブルを削除して一気に実行します)

php artisan migrate:fresh --seed

実行が完了すると、以下のようにusersテーブルの各ユーザーにUUIDが登録されます。

自動ログイン機能をつくる

ではここからが実際にコードを書いていく作業になります。

QRコードを読み取る部分をつくる

ログインフォームの代わりとして、QRコードを読み取るページをつくっていきましょう。

ルートをつくる

はじめにroute/web.phpにルートを追加します。

Route::get('auth/qr_login', 'Auth\\QrLoginController@showQrReader');   // ログインフォーム
Route::post('auth/qr_login', 'Auth\\QrLoginController@login');          // Ajax通信

コントローラーをつくる

続いて、ルートで指定したコントローラーを以下のコマンドで作成します。

php artisan make:controller Auth\\QrLoginController

app/Http/Controllers/Auth/QrLoginController.phpが作成されるので以下のようにメソッドを追加します。

<?php

namespace App\Http\Controllers\Auth;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class QrLoginController extends Controller
{
    public function showQrReader() {

        return view('auth.qr_login');   // ログインフォームの代わり

    }
}

ビューをつくる

では、ビューがまだないのでresources/views/auth/qr_login.blade.phpにファイルを作成し、中身を以下のようにして保存してください。

<html>
<head>
    <style>

        canvas {
            padding-left: 0;
            padding-right: 0;
            margin-left: auto;
            margin-right: auto;
            display: block;
            width: 50%;
        }

    </style>
</head>
<body>
<div id="app">
    <div style="text-align:center;font-size:35px;">QRコードを読みとって自動ログインできます</div>
    <br>
    <canvas ref="canvas"></canvas>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script>
<script>

    new Vue({
        el: '#app',
        data: {
            video: null,
            canvas: null,
            context: null,
            uuid: '',
            completed: false,
            componentWidth: -1,
        },
        computed: {
            hasUuid() {

                return (this.uuid !== '');

            }
        },
        methods: {
            renderFrame() {

                if(!this.hasUuid && !this.completed) { // まだQRコードが読み込まれていない場合

                    const video = this.video;
                    const canvas = this.canvas;
                    const context = this.context;

                    if(video.readyState === video.HAVE_ENOUGH_DATA) {

                        context.drawImage(video, 0, 0, canvas.width, canvas.height);
                        const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
                        const code = jsQR(imageData.data, imageData.width, imageData.height);

                        if(code) {

                            this.uuid = code.data;
                            axios.post('/auth/qr_login', { uuid: this.uuid })
                                .then((response) => {

                                    const result = response.data.result;
                                    const user = response.data.user;

                                    if(result) {

                                        this.completed = true;
                                        alert('「'+ user.name +'」さん、おはようございます!');
                                        // location.href = '/user'; // ここでリダイレクト

                                    } else {

                                        console.log('ログイン失敗..');

                                    }

                                })
                                .catch(error => {

                                    console.log(error);

                                })
                                .finally(() => {

                                    this.uuid = '';

                                });

                        }

                    }

                }

                requestAnimationFrame(this.renderFrame);

            },
            initializeVideo(videoParams) {

                return navigator.mediaDevices.getUserMedia(videoParams)

            },
            initializeVideoThen(stream) {

                this.video.srcObject = stream;
                this.video.play();

            }
        },
        mounted() {

            this.video = document.createElement('video');
            this.video.addEventListener('loadedmetadata', () => {

                this.canvas.width = this.componentWidth;
                this.canvas.height = Number(this.canvas.width * this.video.videoHeight / this.video.videoWidth);
                this.renderFrame();

            });
            this.video.setAttribute('autoplay', '');
            this.video.setAttribute('muted', '');
            this.video.setAttribute('playsinline', '');
            this.canvas = this.$refs.canvas;
            this.context = this.canvas.getContext('2d');
            this.componentWidth = this.canvas.offsetWidth;

            const videoParams = {
                audio: false,
                video: {
                    facingMode: {
                        exact: 'environment'
                    },
                    width: { ideal: 1080 },
                    height: { ideal: 720 }
                }
            };

            this.initializeVideo(videoParams)
                .then(this.initializeVideoThen)
                .catch(() => {

                    this.initializeVideo({ video: true })
                        .then(this.initializeVideoThen)

                });

        }
    });

</script>
</body>
</html>

【追記:2019.07.30】バグがあることがわかりましたので変更しました。すみません。
【追記:2022.7.4】負荷を軽減するようコードを修正しました。
【追記:2022.7.12】iPhone でうまく動かないためコードを大幅に修正しました。(iOS 15.5 の Safari で動作を確認)

なお、QRコードの読み取りには jsQR というJSパッケージを使っているのですが、このパッケージはcdnが存在していないので、今回はpublic/jsgit cloneで設置して使います。(本来はnpmでインストールしてビルドすべきですが、説明が複雑になってしまうので今回は割愛します😅)

【追記:2022.7.12】cdn が公開されてましたので変更しました。

コマンドラインでpublic/jsに移動して以下のコマンドを実行してください。(もしくはシンプルにダウンロードして設置するだけでもOKです)

git clone https://github.com/cozmo/jsQR.git

設置が完了するとこのようになります。

なお、このビューの中でやっていることは以下のとおりです。

  1. mounted()内でvideoやcanvasを準備し、ウェブカメラにアクセスする
  2. ウェブカメラにアクセスができたらrenderFrame()で繰り返し、内容を描画する
  3. もし描画中にQRコードが読み込まれたらaxiosを使ってAjax送信
  4. 送信されてきたUUIDでログインができたらアラートを表示
  5. (ログインに失敗したら)2〜4を繰り返す

そして、実行したものがこちらです(ウェブカメラにはシールを貼ってある状態です)

ログイン部分をつくる

残るは、Ajax通信でUUIDが送信されてきたときにログインを実行する部分です。

先ほど作成したQrLoginControllerを開いてlogin()を追加してください。

public function login(Request $request) {

    $result = false;
    $user = \App\User::where('uuid', $request->uuid)->first();

    if(!is_null($user)) {

        \Auth::login($user);    // ユーザーをログインさせる
        $result = true;

    }

    return [
        'result' => $result,
        'user' => $user
    ];

}

中身としてはシンプルで、送信されてきたUUIDを使ってユーザーをDBから探し出し、もしユーザーが存在していたらAuth::login()で強制的にログインさせるという流れになっています。

テストしてみる

では、お待たせしました。
ここまでで開発してきたコードで自動ログインを実行してみたいと思います。

なお、読み取るQRコードはテストユーザーの「太郎さん」UUIDで、QRコードの画像は、いつも使わせてもらっているQRのススメさんでスマホをつかって作成することにします。

・・・・・・ということで、実行結果はこうなりました!

 

↓↓

 

↓↓

 

↓↓

 

はい!
うまくログインすることができました😊✨

実際はログインできたらユーザー専用ページへリダイレクトする形になると思いますが、今回はテストなのでここで終了にしたいと思います。

お疲れ様でした!

ソースコードをダウンロードする

今回実際に開発したソースコード一式を以下からダウンロードすることができます。

※ ただし、jsQRのインストールやDBテーブルの準備はご自身で実行していただく必要があります。

【Laravel + JavaScript】QRコードで自動ログインする機能

 

【追記:2019.07.30】バグがあることがわかりましたので変更しました。すみません。

開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

今回、QRコードの読み取りをJavaScriptのみで実行してみましたが、感想としては「えっ、こんなに反応早いの!?」でした。

正直なところ、ブラウザだとどうしても動作がモサッとしてしまって、ユーザビリティとしてはあんまり・・・なんてことになるのかと思いきや、あまりにQRコードの読み取りが早くてテスト画像は何回も取り直ししました😂(というのもQRコードは一部分がかけていてもデータを読み取れるので、QRコードが画面の真ん中に来ないのに自動ログインが完了してしまったんです)

ということで今回はQRコードを読みとって自動ログインするサンプルを作ってみました。

このテクニックを使えば、出勤チェックシステムも使えるでしょうし、Raspberry Piと連携させて入室管理システムなんていうのもつくれるんじゃないでしょうか。

テクノロジーの進歩ってすごいですね。

ではでは〜!

このエントリーをはてなブックマークに追加       follow us in feedly