Twilio Verifyを使用したFastAPIのメールアドレス認証

May 07, 2021
執筆者
レビュー担当者
Diane Phan
Twilion

Email-Address-Verification-in-FastAPI-using-Twilio-Verify-jp

このBlogはTwilio Principal Software Engineer for Technical ContentのMiguel Grinbergこちらで公開した記事を日本語化したものです。

大半のWebアプリケーションでは、サインアッププロセス中に、ユーザーのメールアドレスを受け付けます。不正アカウントの作成を防ぐには、指定されたメールアドレスでユーザーがメールを受信できることを確認するという方法が常に有効です。

Twilio Verifyは、SMS、音声通話、メールを介して数値コードを送信しユーザー確認を行えるサービスです。このチュートリアルでは、メールを使用した検証フローをFastAPIアプリケーションに実装する方法について説明します。

fast api email verification example

チュートリアルの要件

  • Python 3.6以降 - オペレーティングシステムにPythonインタープリターがない場合は、python.orgからインストーラーをダウンロードしてください
  • Twilioアカウント - 初めてTwilioを利用する場合は、こちらをクリックして無料アカウントを作成してください。このリンクを用いてアカウントを作成すると有料アカウントへのアップグレード時に使える10ドルのクレジットを進呈します。トライアルアカウントの制限についてはこちらを確認してください
  • SendGridアカウント。こちらをクリックして無料アカウントを作成してください。このアカウントでは1日最大100通のメールを送信できます

SendGridの設定

メール認証ソリューションを設定するには、TwilioアカウントとSendGridアカウントを接続する必要があります。このセクションでは、必要な準備をSendGrid側で行います。

動的テンプレートの作成

最初のステップとしてメールテンプレートを作成します。Twilio Verifyサービスはこのテンプレートを使用しユーザーに認証コードを送信します。

SendGridダッシュボードから、左側にあるメニューの[Email API]をクリックし、続けて[Dynamic Templates](動的テンプレート)をクリックします。

SendGrid - Dynamic Template

[Create Dynamic Template](動的テンプレートの作成)ボタンをクリックし、テンプレートを新規作成します。この新しいテンプレートに任意の名前を付けます。このチュートリアルでは「email-verification」という名前を使用します。

SendGrid - Create Dynamic Template

[Create](作成)ボタンをクリックすると、[Dynamic Templates](動的テンプレート)ページに移動し、新規作成した「email-verification」テンプレートが表示されます。テンプレート名をクリックすると展開され、次のスクリーンショットのように詳細が表示されます。

Dynamic Template - Details

[Add Version](バージョンの追加)ボタンをクリックし、テンプレートの最初のバージョンを作成すると、あらかじめ用意されたテンプレートの選択肢が表示されます。このチュートリアルでは、便宜上、[Blank Template](空白のテンプレート)を選択します。ただし、HTMLに精通している場合は、このテンプレート一覧から選択しても構いません。

次のプロンプトでは、テンプレート用のエディターを選択するよう求めてきます。[Code Editor](コードエディター)を選択し、メール本文のHTMLコードに直接アクセスできるようにします。

空白のテンプレートには会員登録を解除するリンクが記載されたフッターがあるため、実際には空白ではありません。このリンクは変更せず、メール本文に文章を挿入できるように次のHTML行を追加します。追加する場所はメール本文の上、<body>要素タグのすぐ後です。

<p>{{twilio_message}}.</p>

下の図は、この段落がメールの中にどのように収まるかをコードエディターで示しています。

dynamic template - html editor

このテンプレートにおいて{{twilio_message}}はプレースホルダであり、Twilioによりメールのテキストに置き換えられます。メールには「認証コードは、xxxxxxxxです」などの内容が記述されます。

{{twilio_message}}がニーズに合わない場合は、メール本文の設計に使用できるプレースホルダがいくつかあります。詳しくは、こちらのドキュメントを参照してください。

実際のデータを入れたメールの見た目を確認したい場合は、ページ上部の[Test Data](テストデータ)タブをクリックし、twilio_message変数のサンプルの値をJSON形式で入力します。例えば次のように入力します。

{"twilio_message": "Your verification code is XXX"}

作成したテンプレートに問題がなければ、ページ左上の[Settings](設定)ボタンをクリックし、メールの件名を入力します。

dynamic template - subject

ナビゲーションバーの[Save](保存)ボタンをクリックし、ページ左上の左矢印ボタンを押して[Dynamic Templates](動的テンプレート)ページに戻ります。

新しいメールテンプレートには「テンプレートID」が割り当てられます。このIDは、後でTwilioアカウントを設定する際に必要になります。IDが表示される場所を次のスクリーンショットで確認してください。

dynamic template - id

APIキーの作成

SendGrid設定の第2のステップはAPIキーの作成です。TwilioではAPIキーを使用してユーザーに認証メールを送信できます。

ダッシュボードから[Settings](設定)、[API Keys](APIキー)の順に選択します。[API Keys](APIキー)ページで[Create API Key](APIキーの作成)ボタンをクリックします。

APIキーに名前を付けます。今回も任意の名前を付けることができますが、ここでは「email-verification」という名前を使用しています。名前の下の3つのオプションから[Full Access](フルアクセス)を選択します。

SendGrid - Create API Key

[Create & View](作成と表示)ボタンをクリックしてキーを作成すると、次のページにAPIキーが表示されます。作成したキーを確認できるのはこのときだけです。キーをコピーしてテキストとして保存しておきましょう。次のセクションで必要になるまですぐに利用できるようにしておきます。

Twilioの設定

Twilio Verifyサービスでは、SendGrid APIキーと前のセクションで設定した動的テンプレートを用いてメールを送信します。今からTwilioアカウントに移動して設定を完了し、TwilioアカウントとSendGridアカウントをリンクします。

メール連携の作成

Twilioコンソールから[All Products & Services]ボタンをクリックすると[Verify]メニューがあります。その下の[Email Integration]をクリックします。

[Create Email Integration]ボタンをクリックし、新規のメール連携を作成します。すでにメール連携が存在する場合は、[+]印をクリックしてもう1つ追加します。

Verify - Email Integration

メール連携に名前を付けるように促すプロンプトが表示されます。ここでは「email-verification」という名前にしています。

Create New Email Integration

名前を付けた後に、SendGridアカウントの詳細を入力します。次の情報を指定する必要があります。

  • SendGrid APIキー(前のセクションで作成したもの)
  • 動的テンプレートのテンプレートID(前のセクションで作成したもの)
  • 認証メールの[From]フィールドで使用するメールアドレス
  • 認証メールの[From]フィールドで使用する名前。このフィールドには自社のWebサイト名や会社名を入力

Email Integration - details

上記のフィールドに入力した後、[Save]をクリックしてこのメール連携を保存します。

Verifyサービスの作成

[Verify]メニューから[Services]を選択します。次に[Create Service Now]ボタンをクリックします。アカウントにすでにVerifyサービスが存在する場合は、[+]ボタンをクリックして新しいサービスを追加します。

Verify Service

サービスに分かりやすい名前をつけます。ここで付けた名前はユーザーに送信される認証メールで表示されます。そのため、自社のWebサイト名や会社名を入力することをお勧めします。このチュートリアルでは「My Company」という名前を付けています。

Create New Verify Service

次に、このサービスで編集可能な設定項目の画面について説明します。ページの上方に認証コードで使用する数字の桁数を設定するドロップダウンがあります。ここでは下の図のように8桁を選択しました。

digits used in verify service

下の[Email Integration]セクションまでスクロールし、さきほど作成したメール連携をドロップダウンで選択します。

integrate verify and email

一番下までスクロールすると、[Delivery Channels]セクションがあります。この例ではメールのみを使用するため、他の2つのチャネルは無効化しておくと良いでしょう。

verify channels

[Save]ボタンをクリックして変更を保存します。これでメール認証サービスの設定が完了し、FastAPIアプリケーションのコーディングを開始する準備が整いました。

プロジェクトのセットアップ

このセクションでは、新しいFastAPIプロジェクトをセットアップします。手順を整然と進められるよう、ターミナルまたはコマンドプロンプトを開き、新たなディレクトリの作成に適した場所を見つけます。ここでこれから作成するプロジェクトを実行します。

mkdir fastapi-verify-email
cd fastapi-verify-email

仮想環境の作成

Pythonのベストプラクティスに従い、ここで仮想環境を作成します。仮想環境には、このプロジェクトで必要なPythonの依存関係をインストールします。

UnixまたはmacOSシステムを使用している場合は、ターミナルを開き、次のコマンドを入力します。

python3 -m venv venv
source venv/bin/activate

Windowsでチュートリアルを実行する場合には、コマンドプロンプトウィンドウに以下のコマンドを入力します。

python -m venv venv
venv\Scripts\activate

仮想環境を有効にすると、このプロジェクトに必要なPythonの依存関係をインストールできます。

pip install fastapi python-dotenv aiofiles python-multipart uvicorn twilio

このプロジェクトで使用する6つのPythonパッケージは次のとおりです。

アプリケーション設定の定義

Twilio Verifyを用いて認証メールを送信するには、FastAPIアプリケーションでTwilioアカウントの資格情報にアクセスし、認証する必要があります。また、このアプリケーションに先ほど作成したTwilio VerifyサービスのIDを設定することも必要です。

こうした設定値を最も安全に定義する方法は設定値の環境変数を設定することであり、FastAPIアプリケーションで環境変数を管理する最も便利な方法は.envファイルを使用することです。

テキストエディターで新規ファイルを開き、「.env」(先頭に点があります)という名前を付けて次の内容を入力します。

TWILIO_ACCOUNT_SID=xxxxxxxxx
TWILIO_AUTH_TOKEN=xxxxxxxxx
TWILIO_VERIFY_SERVICE=xxxxxxxxx

すべてのxxxxxxxxxは、対応する正しい値に置き換えてください。最初の2つの変数は、Twilioの[Account SID](アカウントSID)と[Auth Token](認証トークン)です。これらの値はTwilioコンソールのダッシュボードで確認できます(下図)。

Twilio Account Sid and Auth Token

TWILIO_VERIFY_SERVICE変数は、サービスに割り当てられた[SERVICE SID]の値です。この値はVerifyサービスの設定ページで確認できます。

verify Service SID

上記3つの変数をFastAPIアプリケーションに取り込むために、config.pyという名前のファイルを作成し、次の内容を入力します。

from pydantic import BaseSettings


class Settings(BaseSettings):
    twilio_account_sid: str
    twilio_auth_token: str
    twilio_verify_service: str

    class Config:
        env_file = '.env'

FastAPIはpydanticBaseSettingsクラスを使用して設定を管理します。BaseSettingsのサブクラスは、属性として定義された変数を環境変数から自動的にインポートします。または、dotenvを用いることにより.envファイルから直接読み込むこともできます。

次のセクションでは、Settingsクラスの使い方について説明します。

FastAPIを用いたメール認証

ここでFastAPIアプリケーションのコードを記述します。

ベースとなるFastAPIアプリケーション

下の図は、FastAPIアプリケーションの最初のイテレーションです。このバージョンではメインページのみを返します。メインページにはメールアドレスを入力して検証できるWebフォームがあります。

テキストエディターまたはIDEで新規ファイルを開き、app.pyという名前を付けて次のコードを入力します。

import asyncio
from fastapi import FastAPI, Form, Cookie, status
from fastapi.responses import FileResponse, RedirectResponse
from twilio.rest import Client
import config

settings = config.Settings()

app = FastAPI()
client = Client(settings.twilio_account_sid, settings.twilio_auth_token)


@app.get('/')
async def index():
    return FileResponse('index.html')

settings変数は前のセクションで.envファイルに保存した設定変数をインポートします。app変数はFastAPIアプリケーションを表します。client変数はTwilioクライアントライブラリのインスタンスであり、Twilio APIへのリクエストの送信に使用します。Twilio Clientは設定から読み込まれる資格情報で初期化されます。資格情報はリクエスト送信時の認証で必要になります。

@app.get(‘/’)デコレーターはアプリケーションのルートURLにマッピングされているエンドポイントを定義します。エンドポイントを実装すると、index.htmlという名前の静的ファイルから読み込まれたレスポンスが返されます。

このエンドポイントを機能させるには、HTMLファイルを作成する必要があります。エディターまたはIDEで新規ファイルを開き、index.htmlという名前を付けて次のHTMLコードを入力します。

<!doctype html>
<html>
  <head>
    <title>FastAPI Email Verification Example</title>
  </head>
  <body>
    <h1>FastAPI Email Verification Example</h1>
    <form method="post">
      <label for="email">Your email:</label>
      <input name="email" id="email">
      <input type="submit" value="Submit">
    </form>
  </body>
</html>

このHTMLページでは、メールアドレスの入力を促すフィールドが1つあるフォームが作成されます。

サーバーの実行

アプリケーションはまだ完成していませんが、テスト用の機能は十分に備えています。これまでにプロジェクトディレクトリに作成したapp.pyindex.html.envの各ファイルがあることを確認し、次のコマンドでアプリケーションを起動します。

uvicorn app:app --reload

UvicornはFastAPIアプリケーションの実行で推奨されるサーバーです。--reloadにより、uvicornはソースファイルを監視し、ファイルの変更のたびにサーバーを自動的に再起動します。残りのチュートリアルを参照する間、サーバーを実行したままで問題ありません。

アプリケーションが実行中であることを確認するには、Webブラウザを開き、アドレスバーにhttp://localhost:8000と入力します。ブラウザにはアプリケーションのメインページが読み込まれ、次のように表示されます。

email address form

フォームデータの処理

フォームを送信しようとすると、FastAPIは「Method not allowed」(メソッドは許可されていません)というエラーメッセージを返します。これは、フォームの送信がまだ実装されていないためです。

index.html<form>要素では、フォームはmethod属性(postに設定)により定義され、action属性はありません。つまり、フォームはPOSTリクエストにより発信元のURLに送信されます。この例ではアプリケーションのルートURLです。

フォームの送信を処理するエンドポイントは、次のような構造になります。これをapp.pyの一番下に追加できますが、# TODOで始まる行に留意してください。この行はまだ構築されていない関数の一部を示しています。

@app.post('/')
async def handle_form(email: str = Form(...)):
    # TODO: send the verification email
    # TODO: return a response to the client

この2番目のエンドポイントでは、@app.post(‘/’)デコレーターを使用してPOSTリクエストのハンドラーを定義します。このWebフォームはemailという1つのフィールドを備えており、このフィールドが関数に渡される引数となります。FastAPIはフォームデータを解析し、クライアントから渡されたフィールドの値を抽出し、この引数の関数に送信します。

この後のセクションでこのエンドポイントを終了します。

認証コードの送信

handle_form()関数が呼び出されると、アプリケーションは検証するメールアドレスを受け取り、前に作成したTwilioのclientインスタンスを使用してユーザーにメールを送信できます。ただし、小さな問題が1つあります。

Python用のTwilio Helperライブラリが非同期アプリケーションに対応していないことです。このライブラリはTwilioサーバーへのネットワークリクエストを作成することになるため、非同期関数で直接使用した場合、asyncioループをブロックします。ループのブロックを避けるために、Twilioに関するすべての作業は、executor内で実行する関数の中でカプセル化できます。これにより、アプリケーションをスムーズに実行できます。

次のコードは、handle_form()関数をapp.pyから更新したバージョンです。executor内でsend_verification_code()関数を実行するロジックを用いています。

@app.post('/')
async def handle_form(email: str = Form(...)):
    await asyncio.get_event_loop().run_in_executor(
        None, send_verification_code, email)
    # TODO: return a response to the client

asyncioループのrun_in_executor()メソッドを使用すると、別のスレッドやプロセスでブロッキング関数を実行できるため、ループはブロックされません。最初の引数は使用したいexecutorになります。または、既定のスレッドexecutorを問題なく使用できる場合はNoneになります。残りの引数は実行する関数と、そのポジショナル引数です。

ここで、send_verification_code()関数の実装を見てみましょう。なお、これは標準的な同期コードであるため、この関数はasyncキーワードで定義されていません。この関数をapp.pyに追加します。

def send_verification_code(email):
    verification = client.verify.services(
        settings.twilio_verify_service).verifications.create(
            to=email, channel='email')
    assert verification.status == 'pending'

この関数はTwilio Clientのインスタンスを使用します。このインスタンスはグローバルスコープで作成されており、認証リソースを作成します。新たに作成された認証リソースは「pending」のステータスになります。そこで関数はassert文を使用し、オブジェクトが予期された状態であることを確認します。

この段階で、TwilioはSendGrid APIキーとテンプレートを使用して指定されたアドレスにメールを送信します。メールにはランダムに生成された数値コードが記載されています。

レスポンスの送信

前のセクションで、handle_form()関数は不完全な状態で残されていました。executorにより認証コードが送信された後、サーバーはクライアントのWebブラウザにレスポンスを返す必要があります。

フォーム送信を処理する際に最も一般的な処理は、リダイレクトを伴う応答です。これにより、例えばフォームの二重送信や、ブラウザからユーザーに提示される分かりにくい警告など、潜在的な多くの問題を回避できます。これは、PRGパターン(Post/Redirect/Getの略)として知られています。

このアプリケーションの場合、ここでブラウザは2つ目のフォームを表示する必要があります。このフォームには、ユーザーがメールで受信した認証コードを入力します。2番目のフォームは、/verifyエンドポイントの下に実装されます。そのため、ここでリダイレクトが発生する必要があります。handle_form()関数を完全に実装すると次のようになります。app.pyのバージョンを更新してください。

@app.post('/')
async def handle_form(email: str = Form(...)):
    await asyncio.get_event_loop().run_in_executor(
        None, send_verification_code, email)
    response = RedirectResponse('/verify',
                                status_code=status.HTTP_303_SEE_OTHER)
    response.set_cookie('email', email)
    return response

レスポンスにより、ブラウザは/verifyのURLにすぐにリダイレクトするよう指示されます。このアプリケーションの興味深い点は、ユーザーから提供されるメールアドレスが、コードの検証時にもう1度必要になるということです。アドレスが喪失しないよう、関数はセッションcookieを追加し、レスポンスを返す前にアドレスを保管します。

コードの受付

ユーザーが数値コードの書かれたメールを受信しました。ここでブラウザは2つ目のWebフォームを表示する必要があります。このフォームでコードを入力し、メールアドレスを確認します。

/verifyエンドポイントへのGETリクエストにより、フォームを備えたHTMLページが返されます。このエンドポイントをapp.pyに追加します。

@app.get('/verify')
async def verify():
    return FileResponse('verify.html')

このエンドポイントはverify.htmlファイルを参照します。この参照ファイルをプロジェクトディレクトリに作成し、次の内容を入力します。

<!doctype html>
<html>
  <head>
    <title>FastAPI Email Verification Example</title>
  </head>
  <body>
    <h1>FastAPI Email Verification Example</h1>
    <form method="post">
      <label for="code">Verification code:</label>
      <input name="code" id="code">
      <input type="submit" value="Submit">
    </form>
  </body>
</html>

コードの検証

コードを検証するロジックでは、Twilio APIにもう1度コールする必要があります。そのためexecutor内で実行する補足的な同期関数がもう1度必要になります。次の関数をapp.pyに追加します。

def check_verification_code(email, code):
    verification = client.verify.services(
        settings.twilio_verify_service).verification_checks.create(
            to=email, code=code)
    return verification.status == 'approved'

check_verification_code()関数がメールアドレスと数値コードを受け取り、これらをTwilio Verifyサービスに送信して確認します。確認に成功すると、返された認証チェックリソースのステータスはapprovedになります。コードが正しくない場合、返されるリソースのステータスはpendingになります。関数はステータスを確認し、入力されたコードが正しい場合はTrueを返し、正しくない場合はFalseを返します。

ユーザーが確認のためにコードを送信すると、ブラウザでは/verifyエンドポイントへのPOSTリクエストが発行されます。次の図は、このリクエストで使用できるハンドラーです。この関数をapp.pyに追加します。

@app.post('/verify')
async def verify_code(email: str = Cookie(None), code: str = Form(...)):
    verified = await asyncio.get_event_loop().run_in_executor(
        None, check_verification_code, email, code)
    if verified:
        return RedirectResponse('/success',
                                status_code=status.HTTP_303_SEE_OTHER)
    else:
        return RedirectResponse('/verify',
                                status_code=status.HTTP_303_SEE_OTHER)

このエンドポイントは2つの引数を受け付けます。email引数は、さきほど設定したcookieから取得しています。一方、code引数は送信されたものです。これらの引数はcheck_verification_code()関数に渡されます。この関数はasyncループをブロックしないようにexecutor内で実行する必要があります。

コードの確認に成功した場合、このエンドポイントからのレスポンスは/success URLにリダイレクトされます。コードを確認できない場合は、/verifyエンドポイントへのリダイレクトが発生します。これはコードを受け付けるフォームを表示するエンドポイントであり、ユーザーはこのフォームに新しいコードを入力できます。

次の図は/successエンドポイントの実装です。このハンドラーはapp.pyの最後に動作します。

@app.get('/success')
async def success():
    return FileResponse('success.html')

これはHTMLページを表示する短いハンドラーです。success.htmlファイルを作成し、次の内容をコピーしてください。

<!doctype html>
<html>
  <head>
    <title>FastAPI Email Verification Example</title>
  </head>
  <body>
    <h1>FastAPI Email Verification Example</h1>
    <p>You have been verified!</p>
    <p><a href="/">Verify another email?</a></p>
  </body>
</html>

このページはユーザーに認証が成功したことを通知します。他のメールアドレスの検証を行う場合に備えて、通知にはメインページへのリンクが記載されています。

アプリケーションのテスト

いよいよお待ちかねの瞬間です。アプリケーションが完成し、テストの準備ができました。app.pyが更新され、ここまでに紹介したすべての関数があることを確認してください。config.py.envindex.htmlverify.htmlsuccess.htmlの各ファイルがプロジェクトディレクトリ内にあることも確認します。

チュートリアルの開始時からuvicornを実行し続けている場合、ソースファイルに変更が加えられるたびにサーバーが自動的に再起動しているはずであるため、そのまま続行できます。現在サーバーを実行中でない場合は、次のコマンドにより起動できます。

uvicorn app:app --reload

Webブラウザを開いてhttp://localhost:8000に移動してください。フォームにメールアドレスを入力します。送信ボタンをクリックすると、認証コードを含むメールがすぐに送信されます。コードを入力するとメールアドレスが検証されます。

project completed

まとめ

これで、FastAPIとTwilio Verifyを使用してメールアドレスを検証する方法の説明を終わります。この記事で学んだテクニックは他のフレームワークにも適用できます。特に適しているのは、同様にasyncioをベースにしているQuartSanicTornadoなどです。非同期アプリケーションでTwilioを使用する方法について詳しくは、こちらのブログの「Using the Twilio Python Helper Library in your Async Applications(非同期アプリケーションでTwilio Python Helperライブラリを使用する方法)」をご覧ください。FastAPI向けのTwilio活用のヒントについて詳しくは、こちらのSMSの送信方法に関する記事をご覧ください。

TwilioとFastAPIを使用して何を構築されるか、とても楽しみです。

Miguel Grinbergは、Twilioのテクニカルコンテンツ担当プリンシパルソフトウェアエンジニアです。このブログでは皆様のプロジェクトを紹介します。ぜひ、mgrinberg [at] twilio [dot] comまでご連絡ください。