https://a.storyblok.com/f/270183/149817/bc3ed65a05/2fa-laravel.png

LaravelとNexmoによる2FAログイン

最終更新日 May 18, 2021

所要時間:3 分

この記事はもともと michaelheap.com掲載前 マイケルに参加する前に ネクスモ チーム!

先日 ユーザー認証によるLaravelのブートストラップについて書きました。これは、アプリケーションのための素晴らしい出発点を提供してくれます。 この投稿を偶然見つけ、Nexmo Verifyについて考え始めました。 Nexmo Verify.

私は最近 をチャットボットに統合しました。に問題なく組み込んだので、Laravelのログインフローに組み込めば便利だと思いました。

ユーザーの電話番号の収集

ユーザーの電話番号を収集する必要があります - これがないと認証メールを送ることができません。ユーザーが登録した後に収集することもできますが、代わりに登録時に収集することにしました。

最初にやるべきことは、usersテーブルを変更して、ユーザーの電話番号を格納するフィールドを用意することです。これを行うために、新しいマイグレーションを作成して usersテーブルを変更します:

php artisan make:migration add_users_phone_number

これは database/migrationsという名前のファイルが作成される。 <current time>_add_users_phone_number.php.そのファイルを開き、中身を以下のように置き換える:

<?php

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

class AddUsersPhoneNumber extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('phone_number');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('phone_number');
        });
    }
}

このマイグレーションは、実行時に phone_numberという名前のカラムを追加し、ロールバックするとそのカラムを削除します。ターミナルで php artisan migrateを実行してください。

次に、登録フォームにユーザーが電話番号を入力するためのテキスト入力を追加する必要があります。編集する resources/views/auth/register.blade.phpを編集し、フォームの一番下の送信ボタンの直前に以下を追加します:

<div class="form-group{{ $errors->has('phone_number') ? ' has-error' : '' }}">
    <label for="name" class="col-md-4 control-label">Phone Number</label>
    <div class="col-md-6">
        <input id="name" type="tel" class="form-control" name="phone_number" value="{{ old('phone_number') }}" required autofocus>

     @if ($errors->has('phone_number'))
        <span class="help-block">
            <strong>{{ $errors->first('phone_number') }}</strong>
        </span>
    @endif
    </div>
</div>

今 http://localhost:8000/register にアクセスすると、登録フォームの一番下に電話番号フィールドが表示されているはずです。もう少しですが、まだ1つ重要な部分が欠けています - ユーザーが提供した電話番号を実際にデータベースの新しいフィールドに保存していないのです。

Laravelはユーザーを登録するためのロジックをすべて app/Http/Controllers/Auth/RegisterController.phpファイルに保持されています。ファイルを開いて見てください。 validatorメソッドと createメソッドがあるはずです。ユーザーの電話番号を保存するために、この両方を変更する必要があります。

まずは validatorメソッドから始めましょう。提供された番号が有効であることを確認するために phone_numberに新しいエントリーを追加して、提供された番号が有効であることを確認する必要があります。私のバリデーション・ルールはかなり厳密で、12文字ぴったりで、すべてのユーザーで一意であることを要求しています。バリデーションルールを追加したら validatorメソッドは以下のようになるはずです:

return Validator::make($data, [
    'name' => 'required|max:255',
    'email' => 'required|email|max:255|unique:users',
    'password' => 'required|min:6|confirmed',
    'phone_number' =>; 'required|size:12|unique:users',
]);

データが指定したバリデーション・ルールに合格したら、それをデータベースに保存する必要がある。そのために createメソッドを編集して、電話番号を保存する行を追加します。すべての受信リクエストデータは $data変数に格納されているので、1行追加するだけです:

return User::create([
    'name' => $data['name'],
    'email' => $data['email'],
    'password' => bcrypt($data['password']),
    'phone_number' =>; $data['phone_number']
]);

今、ユーザーを追加しようとすると、期待通りに動作しません。これは、クラスへのプロパティの大量割り当てを防ぐLaravelの安全機能によるものです。私たちは Userクラスに phone_numberが有効なフィールドであることをクラスに通知していないため、保存のリクエストを拒否してしまうのです。この問題を解決するには app/User.phpを編集して phone_number$fillable配列に追加します:

protected $fillable = [
    'name', 'email', 'password', 'phone_number'
];

この変更を行った後、以下のページからアカウントを登録してください。 登録ページにログインしてください。

Nexmo Verifyの追加

ユーザーの電話番号が分かったので、Verifyロジックの実装を開始します。Laravelはユーザーのログインリクエストを app/Http/Controllers/Auth/LoginController.phpを実行して、提供された認証情報が有効かどうかを調べます。認証情報が有効な場合、Laravelは次に authenticatedメソッドを探します。 LoginController.メソッドが存在すれば、そこでロジックを実行します。ここに二要素認証のロジックを追加します。

開く app/Http/Controllers/Auth/LoginController.phpを開き、他の宣言の隣に以下を追加する。 useを追加する:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Contracts\Auth\Authenticatable;

この3つの useメソッドのヒントを入力できるようにするためです。 authenticatedメソッドを追加する必要がある。次に追加する LoginControllerクラスに追加する:

public function authenticated(Request $request, Authenticatable $user)
{
    Auth::logout();
    $request->session()->put('verify:user:id', $user->id);
    // @TODO: Send the Verify SMS here
    return redirect('verify');
}

このコードはユーザーを再度ログアウトさせ、どのユーザーとしてログインしようとしたかを知るために、セッションにユーザーIDを保存します。Verifyリクエストが完了すると、このIDを使って自動的に再ログインします。

Verify リクエストのトリガー

そこに @TODOが追加されていることにお気づきでしょう。今のところNexmo経由でSMSを送信する方法はまだないので、次にそれをやってみましょう。ありがたいことに、Nexmoには Laravelパッケージを用意してくれています。プロジェクトのREADMEに従って、ComposerでNexmoクライアントとLaravelサービスプロバイダーの両方をインストールします:

composer require nexmo/client @beta composer require nexmo/laravel 1.0.0-beta3

インストールが完了したら、Laravelにクライアントの存在を知らせる必要があります。の2つのセクションを編集する必要があります。 config/app.phpこのために providersそして aliases.

以下を追加 providers:

Nexmo\Laravel\NexmoServiceProvider::class

以下を追加 aliases:

'Nexmo' => \Nexmo\Laravel\Facade\Nexmo::class

最後に php artisan vendor:publishを実行してNexmo設定ファイルを生成する。このコマンドを実行したら、次のように編集します。 config/nexmo.phpを編集し、API認証情報を api_keyそして api_secret.ここで直接入力するか、データベース設定ファイルと同じように .envを使うこともできる。ここでは .envファイルを使うことにします。 config/nexmo.phpを以下のように変更します:

'api_key' => env('NEXMO_KEY', ''),
'api_secret' => env('NEXMO_SECRET', ''),

そして .envファイルの一番下に2つのエントリーを追加しました。 NEXMO_KEYNEXMO_SECRET:

NEXMO_KEY=mykey NEXMO_SECRET=mysecret

Nexmoクライアントが設定できたので、次に app/Http/Controllers/Auth/LoginController.phpに戻り、通知システムを実装します。先ほどの @TODOのコメントを次のように置き換える:

$verification = Nexmo::verify()->start([
    'number' => $user->phone_number,
    'brand'  => 'Laravel Demo'
]);
$request->session()->put('verify:request_id', $verification->getRequestId());

これにより、そのユーザーの電話番号にNexmo経由でVerifyリクエストが送信されます。また、ファサードが利用できるように、ファイルの先頭に use Nexmo;を追加する必要があります。それができたら、ログインしてVerifyリクエストをトリガーできるようになります!ユーザが検証コードを提供する方法がないので、身元を確認することができません。

リクエストの検証

の最後に LoginController::authenticatedの最後で、ユーザーを /verifyurlにリダイレクトします。そのルートをLaravelに登録し、実装を書きましょう。

開く routes/web.phpを開き、その下に以下を追加する:

Route::get('/verify', 'VerifyController@show')->name('verify');
Route::post('/verify', 'VerifyController@verify')->name('verify');

これは、2つのルート(a GETPOSTを登録する。 /verify)を登録します。Laravelに showverifyメソッドを呼び出すように VerifyControllerを使用してコントローラを生成します。 artisan:

php artisan make:controller VerifyController

にファイルが作成されます。 app/Http/Controllers/VerifyController.php- にファイルが作成されるので、その内容を以下のように置き換えてください:

<?php

namespace App\Http\Controllers;

use Auth;
use Nexmo;
use Illuminate\Http\Request;

class VerifyController extends Controller
{
    public function show(Request $request) {
        return view('verify');
    }

    public function verify(Request $request) {
        return 'Not Implemented';
    }
}

誰かが/verifyにGETリクエストしたときにverifyビューを表示するには、これで十分です。もう一度言いますが、このファイルはまだ存在しないので、resources/views/verify.blade.phpに以下の内容で作成してみましょう:

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Verify</div>

                <div class="panel-body">
                    <form class="form-horizontal" role="form" method="POST" action="{{ route('verify') }}">
                        {{ csrf_field() }}

                        <div class="form-group{{ $errors->has('code') ? ' has-error' : '' }}">
                            <label for="code" class="col-md-4 control-label">Code</label>

                            <div class="col-md-6">
                                <input id="code" type="number" class="form-control" name="code" value="{{ old('code') }}" required autofocus>

                                @if ($errors->has('code'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('code') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>
                           <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <button type="submit" class="btn btn-primary">
                                    Verify Account
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

そこにはたくさんのHTMLがあるが、やっていることは送信ボタン付きのフォーム入力がひとつ表示されるだけだ。あなたは Verifyページをご覧ください。

Verifyコードを入力するページができたので、あとはNexmoで提供されたコードを検証するだけです。あなたの verifyメソッドを VerifyControllerを次のコードに置き換えてください。このメソッドは、入力されたデータが4文字であることを検証し(Nexmoの検証コードは4文字または6文字です。認証されなかった場合は例外が発生し、ユーザーにエラーを返します。そうでない場合は、セッションからユーザIDを取得し、ユーザをログインさせ、ホームコントローラにリダイレクトします。

public function verify(Request $request) {
    $this->validate($request, [
        'code' => 'size:4',
    ]);

    try {
        Nexmo::verify()->check(
            $request->session()->get('verify:request_id'),
            $request->code
        );
        Auth::loginUsingId($request->session()->pull('verify:user:id'));
        return redirect('/home');
    } catch (Nexmo\Client\Exception\Request $e) {
        return redirect()->back()->withErrors([
            'code' => $e->getMessage()
        ]);

    }
}

この時点で、私たちの統合はエンドツーエンドで機能しているはずです。すべての変更を保存してログインを試みると、次のページにリダイレクトされ、認証コードが記載されたテキストメッセージが届きます。 verify ページにリダイレクトされ、認証コードが記載されたテキストメッセージが届きます。コードを入力すると、期待通りにログインできます。

おめでとうございます!Nexmo Verifyによる二要素認証をLaravelアプリケーションに統合しました。

荒削りな部分を整理する

機能する一方で、まだ解決すべき荒削りな部分もある。例えば、ユーザーは最初のVerifyリクエストを確認することなく2回目のログインができる。また /verifyページに到達することもできます。最後に、登録後、ユーザがログアウトし、再度ログインしようとしたときのみ、Verifyを行います。

この記事ではこれらの問題を解決するつもりはない!

シェア:

https://a.storyblok.com/f/270183/384x384/1c8825919c/mheap.png
Michael Heapヴォネージの卒業生

マイケルはポリグロット・ソフトウェア・エンジニアであり、システムの複雑性を軽減し、より予測可能なものにすることに尽力している。さまざまな言語やツールを使いこなし、ユーザーグループやカンファレンスで世界中の聴衆と技術的な専門知識を共有している。日々、マイケルはVonageの元開発者支持者であり、あらゆるテクノロジーについて学び、教え、書くことに時間を費やしている。