https://a.storyblok.com/f/270183/46227/1450fb847e/blog_symfony_verify_1200x600.png

symfonyでユーザー登録を検証する

最終更新日 April 19, 2021

所要時間:16 分

虚偽の情報で登録するユーザは害虫になる可能性があり、特に連絡可能であると予想される電話番号で登録する場合はそうです。 VonageのVerify APIは、電話番号が正しく、ユーザーが所有していることを確認できるようにすることで、この解決策を提供します。APIは電話番号を取得し、その電話番号にピンコードを送信し、それが正しいソースを通してリレーバックされることを期待します。

このチュートリアルでは、既存の基本的なユーザー認証システムを拡張します。 チュートリアルではを使って多要素認証を実装します。 Vonage Verify API を使って多要素認証を実装します。(以前の NexmoVerify API) を使って多要素認証を実装します。

完成したコードは end-tutorialブランチにあります。 GitHub リポジトリ.

前提条件

はじめに

リポジトリのクローン

このコマンドをターミナルにコピーして既存のリポジトリをクローンし、プロジェクトのディレクトリを変更します:

git clone git@github.com:nexmo-community/verify-user-registrations-with-symfony.git cd verify-user-registrations-with-symfony

データベース認証情報

その symfony/ディレクトリ内に .env.localファイルを作成し、リポジトリにコミットしたくないローカル環境変数を保存します。たとえば、データベースに接続するためのメソッドと認証情報をコードで知っておく必要があります。次の行を .env.localファイルにコピーします:

DATABASE_URL=postgresql://user:password@postgres:5432/test?serverVersion=11&charset=utf8

上記の例には、データベースに接続するために必要な情報がいくつか含まれている。

  • postgresqlは接続に使われるプロトコルである。

  • userそして passwordはデータベースへの認証に使用される認証情報のセットです。

  • postgresはドメインのアドレスである。

  • 5432はデータベースに接続するポートです。

  • testはデータベース名である。

サードパーティライブラリのインストール

このプロジェクトですでに定義されているいくつかのサードパーティ・ライブラリを、Composerとyarnパッケージの両方を使ってインストールする必要がある。

以下の3つのコマンドを実行する:

# Install libraries such as Symfony framework bundle and Doctrine Orm bundles (for manipulating the database). composer install # Install Symfony Webpack encore for integrating bootstrap and front end technologies into the Symfony application. yarn install # Compile front end files ready for development use. yarn run dev

Dockerの実行

このチュートリアルでは、サーバーの要件が誰でも同じであることを保証するために、あらかじめ定義された構成のコンテナを使用するようにDocker configを設定しました。

ディレクトリ内で docker/ディレクトリを実行する:

docker-compose up -d

コマンドが終了すると docker-composeコマンドが終了すると、3つのコンテナが実行されていることが以下のように確認できるはずだ:

Image showing Terminal output of Docker containers successfully running

データベース・マイグレーションの実行

ターミナルで、以下のコマンドを実行して PHP Docker コンテナに接続する:

docker-compose exec php bash

データベースのテーブルを作成し、symfony/src/migrations/で見つかるすべてのファイルを実行するには、次のコマンドを実行します:

php bin/console doctrine:migrations:migrate

このコマンドは、関連するカラムを持つユーザー・データベース・テーブルを作成する。

登録のテスト実行

にアクセスしてください: http://localhost:8081/registerにアクセスすると、下の画像のような登録ページが表示されます:

Initial template render of registration page with form.

テスト用の電話番号とパスワードを入力してください。フォームを送信すると、プロフィールページが表示されます!

注意してください:ここで電話番号を使用すると、新しいユーザーが作成されますので、ユーザーデータベース・テーブルからその登録を削除する準備をしておいてください。

ここまでくれば、このチュートリアルの準備は万端だ。

Nexmo PHP SDKのインストール

注意:Composer コマンドは、PHP docker コンテナ内から実行する必要がある。コンテナから データベースの移行を実行するチュートリアルのステップで、PHP docker コンテナのターミナルにリモートアクセスしました。コンテナのターミナルセッションから抜けた場合は、コンテナ内で docker-compose exec php bashディレクトリから docker/ディレクトリから実行します。

このチュートリアルでは Vonage Verify API を使用します。PHP でこれを使用する最も簡単な方法は、PHP SDK をインストールすることです。

これをインストールするには

composer require nexmo/client

お客様の Vonage開発者ダッシュボードに、"Your API credentials "がありますので、これをメモしておいてください。

ディレクトリ symfony/ディレクトリ内で、次の2行を .env.localファイルを追加する(api_keyとapi_secretをあなたのkeyとsecretに置き換える):

VONAGE_API_KEY= VONAGE_API_SECRET=

という新しいディレクトリを作成する。 Utilという新しいディレクトリを作成する。 symfony/src/という名前の新しいディレクトリを作成し、その中に VonageUtil.php.

このユーティリティクラスは、Nexmo PHP SDK を使用するすべてのコードを処理します。以下の例では、NexmoClient オブジェクトを作成する以外に何も行いません。 .env.local.以下の例を新しく作成した VonageUtil.php:

<?php

// symfony/src/Util/VonageUtil.php

namespace App\Util;

use App\Entity\User;
use Nexmo\Client as NexmoClient;
use Nexmo\Client\Credentials\Basic;
use Nexmo\Verify\Verification;

class VonageUtil
{
    /** @var NexmoClient */
    protected $client;

    public function __construct()
    {
        $this->client = new NexmoClient(
            new Basic(
                $_ENV['VONAGE_API_KEY'],
                $_ENV['VONAGE_API_SECRET']
            )
        );     
    }
}

検証ページの作成

新しいカラムの検証

新しいプロパティは Userエンティティの内部に新しいプロパティが必要である。

Dockerターミナルで

php bin/console make:entity

新しいプロパティを3つ作成します。上記のコマンドの手順に従って、以下のように値を入力してください:

エンティティ・タイプの入力を求める箇所 User

- New property name: countryCode
- Type: string
- Length: 2
- Is Nullable: false
- New property name: verificationRequestId
- Type: string
- Length: 255
- Is Nullable: true
- New property name: verified
- Type: boolean
- Is Nullable: false

が必要である。 countryCodeは、Verify APIが正常に電話をかけるために、電話番号がどの国のものかを判断するために必要です。

この verificationRequestIdは、Verify API が最初にサーバに返す ID で、この ID と検証コードとの組み合わせでユーザを検証します。

この verifiedプロパティは、ユーザが認証済みかどうかをシステムが判断できるようにします。

データベースを変更した新しいマイグレーションファイルを生成するには、以下を実行する必要があります。

php bin/console make:migration

上記のコマンドは、プロジェクトの Entityファイルに加えられた変更を検出します。そして、これらの変更をSQLクエリに変換し、マイグレーションとして実行すると、変更をデータベースに保持します。

生成された移行ファイルは symfony/src/Migrationsにあります。

これらの変更に問題がなければ、以下のコマンドを実行してデータベースに永続化する。

php bin/console doctrine:migrations:migrate

ユーザー登録に国コードを含める

クラスを RegistrationFormTypeクラスを symfony/src/Form/RegistrationFormType.php.ファイルの一番上にChoiceTypeクラスのフォーム・タイプ用の新しいインクルードを追加します:

use Symfony\Component\Form\Extension\Core\Type\ChoiceType;

同じファイルで以下の変更を加える:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
+      ->add(
+          'countryCode',
+          ChoiceType::class,
+          [
+              'label' => false,
+              'attr' => [
+                  'class' => 'form-control form-control-lg'
+              ],
+              'choices' => [
+                  "United Kingdom" => "GB",
+                  "United States" => "US"
+              ]
+          ]
+      )

このデモで選択できるのは、GBとUSの2つのみ。しかし、他の国もサポートされています。ISOの国名リストと、それに付随する国コードはこちらでご覧いただけます: ISO.org.配列の値が、選択した国の2文字のISO標準であることを確認してください。

その中に symfony/templates/registration/register.html.twigのフォーム行がある。 phoneNumber.この上に countryCodeを追加します:

+ {{ form_row(registrationForm.countryCode) }}
  {{ form_row(registrationForm.phoneNumber) }}

Dockerを起動している場合は、以下の登録ページで確認できる。 http://localhost:8081/registerで確認でき、以下のようなページが表示される:

Registration page with country code for use with the phone number

登録フォームの使用は歓迎しますが、マイナンバーを使用しないか、ユーザーデータベースのテーブルからその登録を削除する準備をしておいてください。

電話番号の有効性を確認する

認証コードを提供するために番号に電話する際、システムはブランド名を要求する。そこで symfony/ディレクトリで .env.localを開き、新しい行を追加します。次のように置き換えます。 VerifyWithVonageを、あなたが認証のために代表している会社/ブランドに置き換えてください:

VONAGE_BRAND_NAME=VerifyWithVonage

番号が有効な電話番号であることをVerifyするには、電話番号が正しい形式であり、提供した地域(国コード)で有効であることを確認する必要があります。そのためには Giggsey による Google libphonenumber の PHP 移植版.

以下のコマンドを実行して、このライブラリをインストールする。

composer require giggsey/libphonenumber-for-php

あなたの VonageUtilファイルを開きます。 symfony/src/Util.このクラスの中に、電話番号と国番号を検証するメソッドを追加する必要があります。このメソッドは、電話番号がその地域にマッチするかどうかもチェックします。もし一致すれば、このメソッドは国際化されたフォーマットで電話番号を返します。このクラスに以下をコピーします:

private function getInternationalizedNumber(User $user): ?string
{
    $phoneNumberUtil = \libphonenumber\PhoneNumberUtil::getInstance();

    $phoneNumberObject = $phoneNumberUtil->parse(
        $user->getPhoneNumber(),
        $user->getCountryCode()
    );

    if (!$phoneNumberUtil->isValidNumberForRegion(
        $phoneNumberObject,
        $user->getCountryCode())
    ) {
        return null;
    }

    return $phoneNumberUtil->format(
        $phoneNumberObject,
        \libphonenumber\PhoneNumberFormat::INTERNATIONAL
    );
}

このメソッドは $userオブジェクトを使用して次のことを行う:

  • ライブラリを使って電話番号と国コードを解析する。 libphonenumberライブラリを使用します、

  • は、この番号が提供された地域で有効な番号であることを確認します(国コードによる)。

番号と地域が有効であれば、電話番号を国際化されたものにフォーマットする。

登録時の確認コールの送信

このプライベート・メソッドを使用するメソッドを VonageUtilメソッドを作成します。この新しい public メソッドは、ユーザーの入力が有効であることを確認し、Verify API を使用して検証プロセスを開始します。

public function sendVerification(User $user)
{
    // Retrieves the internationalized number using the previous util method created.
    $internationalizedNumber = $this->getInternationalizedNumber($user);

    // If the number is not valid or valid for the country code provided, then return null
    if (!$internationalizedNumber) {
        return null;
    }

    // Initialize the verification process with Vonage
    $verification = new Verification(
        $internationalizedNumber,
        $_ENV['VONAGE_BRAND_NAME'],
        ['workflow_id' => 3]
    );

    return $this->client->verify()->start($verification);
}

異なるファイル間のジャンプを保存するために、このユーティリティ・クラスに別のメソッドを追加します。この新しいメソッドによって request_idオブジェクトの中で返される Verificationオブジェクトの中で返されます。

public function getRequestId(Verification $verification): ?string
{
    $responseData = $verification->getResponseData();

    if (empty($responseData)) {
        return null;
    }

    return $responseData['request_id'];
}

クラスの中に新しく作ったメソッドは VonageUtilクラスの内部に作成したこれらの新しいメソッドは、今は何もしません。これらのメソッドが役に立つようにするには、このメソッドの内部から機能を呼び出す必要があります。 RegistrationControllerの中から機能を呼び出す必要があります。 symfony/src/Controller/.

まず VonageUtilRegistrationControllerに注入する必要がある:

+use App\Util\VonageUtil;

class RegistrationController extends AbstractController
{
+    /** @var VonageUtil */
+    protected $vonageUtil;
+
+    public function __construct(VonageUtil $vonageUtil)
+    {
+        $this->vonageUtil = $vonageUtil;
+    }

あなたの registerメソッド内で $entityManager行を見つけ setVerified()をfalseとして追加する:

+$user->setVerified(false);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();

以下 $entityManager->flush()検証プロセスを開始するリクエストを作成する。そこで、以下の2行を追加します。 VonageUtilメソッドを呼び出して検証リクエストを送信します。 requestIdを変数として保存します:

$verification = $this->vonageUtil->sendVerification($user);
$requestId = $this->vonageUtil->getRequestId($verification);

クラスには次のようなコードがある:

return $guardHandler->authenticateUserAndHandleSuccess(
    $user,
    $request,
    $authenticator,
    'main' // firewall name in security.yaml
);

を設定したかどうかを最初にチェックする機能に置き換える。 requestIdを保存します。 requestIdを保存し、ユーザーを認証します:

if ($requestId) {
    $user->setVerificationRequestId($requestId);
    $entityManager->flush();

    return $guardHandler->authenticateUserAndHandleSuccess(
        $user,
        $request,
        $authenticator,
        'main' // firewall name in security.yaml
    );
}

Verifyフォーム

Dockerターミナルで以下のコマンドを実行し、指示に従ってリストされた値を入力します:

php bin/console make:form
- Name: VerifyFormType
- Entity name: User

Creating a verify form in Symfony

これを提出することで symfony/src/Form/という VerifyFormType.php.このフォームが期待通りに動作するためには、いくつかの変更が必要です:

以下の行を置き換える:

->add('phoneNumber')
->add('roles')
->add('password')
->add('countryCode')
->add('verificationRequestId')
->add('verified')

と:

->add('verificationCode', TextType::class, [
    'mapped' => false,
    'attr' => [
        'class' => 'form-control form-control-lg'
    ],
    'constraints' => [
        new NotBlank([
            'message' => 'Please enter a verification code',
        ]),
        new Length([
            'min' => 4,
            'max' => 4,
            'minMessage' => 'The verification code is a 4 digit number.',
        ]),
    ],
])

更新されるべきでないフォームフィールドを削除し、新しいマッピングされていないフィールド(データベースのテーブルにマッピングされていないフィールド)である verificationCode.を追加しました。 verificationCodeは電話番号をVerifyするためにAPIに送られます。

ファイルの先頭に、さらに3つのincludeを追加する。これらは、上のコード例で使用したクラスの完全修飾クラス名です。

use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;

コードの検証

あなたの VonageUtilクラスでは、Verify API を呼び出して、ユーザから提供されたコードが有効であることを検証する新しいメソッドが必要です。以下の例を VonageUtilクラスに入れてください:

public function verify(string $requestId, string $verificationCode)
{
    $verification = new Verification($requestId);

    return $this->client->verify()->check($verification, $verificationCode);
}

の中に新しいテンプレート・ファイルを作成する。 symfony/templates/registrationという verify.html.twig

{% extends 'base.html.twig' %}

{% block title %}Verify{% endblock %}

{% block body %}
    <div class="row justify-content-center align-items-center h-100">
        <div class="col col-sm-6 col-md-6 col-lg-4 col-xl-3">
            <h1 class="h3 mb-3 font-weight-normal">Verify</h1>

            {{ form_start(verificationForm) }}
                <div class="form-group">
                    {{ form_row(verificationForm.verificationCode) }}
                </div>

                <button class="btn btn-info btn-lg btn-block" type="submit">Verify</button>
            {{ form_end(verificationForm) }}
        </div>
    <div>
{% endblock %}

あなたの RegistrationControllerの中に、上記のテンプレートを表示し、フォーム送信を処理する新しいメソッドが必要です。

まず VerifyFormTypeとVonageクラス Verificationを追加します:

use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
+use App\Form\VerifyFormType;
+use Nexmo\Verify\Verification;

次に、新しいメソッドを作成する:

/**
 * @Route("/register/verify", name="app_register_verify")
 */
public function verify(Request $request): Response
{
    $user = $this->getUser();
    $form = $this->createForm(VerifyFormType::class, $user);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $verify = $this->vonageUtil->verify(
            $user->getVerificationRequestId(),
            $form->get('verificationCode')->getData()
        );

        if ($verify instanceof Verification) {
            $user->setVerificationRequestId(null);
            $user->setVerified(true);

            $entityManager = $this->getDoctrine()->getManager();
            $entityManager->flush();

            return $this->redirectToRoute('profile');
        }
    }

    return $this->render('registration/verify.html.twig', [
        'verificationForm' => $form->createView(),
    ]);
}

このチュートリアルの時点では、登録のプロセスは以下のようになっている:

  • 電話番号 /register電話番号とパスワードを入力する

  • 4桁の番号で電話がかかってくる。

  • にリダイレクトされた。 /profile

現在、ユーザーが認証されているかどうかのチェックは行われていない。

検証の実施

このステップでは、セキュリティで保護されたページへのアクセスを許可する前に、ユーザが検証済みかどうかをチェックするイベントサブスクライバを実装します。ユーザが検証済みでない場合、検証コードを入力するために検証フォームにリダイレクトされます。

Dockerターミナルで、新しいイベント・サブスクライバーを作成するコマンドを入力し、画面の指示に従って以下の値を入力してください:

php bin/console make:subscriber
- Class name: `VerifiedUserSubscriber`
- Event to subscribe to: `kernel.controller`

下の画像は、コマンドを完了するために入力される内容の例である:

Verified user event subscriber

オープン VerifiedUserSubscriberこれは symfony/src/EventSubscriber/.

onKernelControllerメソッドにチェックと制限を追加する。

まず、ユーザーがプロフィールのURLにアクセスしようとしているかどうかをチェックします。もしそうでなければ、戻って目的のページに進めるようにします:

if (!preg_match('/^\/profile/i', $event->getRequest()->getPathInfo())) {
    return;
}

ここで、ユーザーが認証されたかどうかをチェックしたい。これを行うには、イベント・サブスクライバに tokenStorageサービスをイベント・サブスクライバに注入します。このとき、時間を節約するために、ユーザ・チェックの後に routerサービスを注入します。

ファイルの一番上に、他のクラス・インクルージョンと一緒に以下を追加する:

use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;

次に、クラスのコンストラクタを使って2つのサービスをインジェクトし、これらのサービスをクラス内のプロパティとして設定する:

/** @var RouterInterface */
protected $router;

/** @var TokenStorageInterface */
private $tokenStorage;

/**
 * @param RouterInterface $router
 * @param TokenStorageInterface $tokenStorage
 */
public function __construct(
    RouterInterface $router,
    TokenStorageInterface $tokenStorage
) {
    $this->router = $router;
    $this->tokenStorage = $tokenStorage;
}

関数内の onKernelController関数内に戻り、ユーザーがプロファイルにアクセスしているかどうかのチェックの下に、ユーザーが認証済みかどうか、そして認証されているかどうかをチェックする以下を追加します:

if (null === $user = $this->tokenStorage->getToken()->getUser()) {
    return;
}

// Check whether the user is verified, if they are, allow them to continue to their destination.
if ($user->getVerified()) {
    return;
}

最後に、この時点でユーザーがログインしていて、プロフィールセクションにアクセスしようとしていて、Verifyされていない場合です。そこで、先に進む前にアカウントを確認する必要があることを確認するために、Verifyルートにリダイレクトします。

$route = $this->router->generate('app_register_verify');
$event->setController(function () use ($route) {
    return new RedirectResponse($route);
});

テストしてみよう!

このチュートリアルをテストする完全な方法は、新規アカウントを 登録を有効な電話番号で登録することです。すると Verifyこのフォームを送信すると

その電話番号に4桁のコードが記載された電話がかかってくるので、それをVerifyページのフォームに入力する。これで プロフィールにアクセスできる。

これで、Vonage Verify API を使用して、2 段階の登録プロセスを Symfony アプリケーションに統合できました。提供された例は、Verify API を使用する多くの方法の一つに過ぎません。ログイン中の多要素認証であれ、Verify であれ、ユーザーの電話番号が有効であることを確認することで、確実に連絡を取ることができます。

このチュートリアルで Verify API に興味を持たれたかもしれませんが、PHP がお好みの言語でない場合は、Vonage ブログで様々な言語やサービスのチュートリアルをご覧いただけます:

このチュートリアルの完成したコードは GitHub リポジトリend-tutorialブランチにあります。

シェア:

https://a.storyblok.com/f/270183/250x250/b052219541/greg-holmes.png
Greg Holmesヴォネージの卒業生

元Vonage開発者エデュケーター。PHPのバックグラウンドを持つが、一つの言語に縛られることはない。熱心なゲーマーでRaspberry pi愛好家。屋内クライミング施設でボルダリングをしていることが多い。