Authy と Python を使った簡単な電話認証アプリを作る




※ 米国Twilio社が 2018/2/1 に掲載したブログ記事を日本語で紹介します。

セキュリティの確保はどのようなサービスであっても最重要課題です。認証を行う手段として「電話を使うこと」は最もシンプルな手法の一つといえます。ユーザーの電話番号を使うことで、不正利用を抑止するとともに、信頼性の高い通知先を得ることになります。さっそく、Authy Phone Verification API と Pyhton, Flask を使って電話番号を認証する仕組みを一緒に作ってみましょう。

必要なもの

ここから紹介する内容では、以下のものが必要になります。

・Twilioアカウント 1つ
・Autyアプリケーション 1つ (Twilioコンソールから作成可能)
・Python (記事では 3.6.4 を利用していますがおそらく 2.x でも動きます)
Pip (依存性の解決のためにインストール)
Virtualenv

セットアップ

Twilioコンソール(管理画面)にログインし、Authy の Setting にあるメニューから、Authy App API Key を取得します。



新しいプロジェクトのフォルダで、この例では phone_verification という名前を付け、config.py という名前で設定ファイルを作成し、以下のように API Key を加えます。

AUTHY_API_KEY = 'asdf........................'

そして requirements.txt というファイルを作ります。このファイルは、Pythonプロジェクトにおける依存性の管理に役立ちます。このプロジェクトでは 2つの依存性がありますので、以下のように requirement.txt に2行を書きます。

authy
flask

ターミナルから、virtualenv を以下のように立ち上げます。

virtualenv env
source env/bin/activate
pip install -r requirements.txt

これでアプリケーションのセットアップができました。

Authyを使って電話認証を簡単にする

電話番号認証を行うために、この例では以下のステップを踏みます。

・ユーザは次の3つを入力し、アプリケーションに渡す。
 「電話番号」「国番号」「認証を音声で行うかSMSで行うか」
・アプリケーションが Authy APIにリクエストを投げ、認証を開始する
・ユーザーの電話機へ、電話着信 または SMS で 4桁のコードを送る
・ユーザーがアプリケーションにコードを入力する
・電話番号と国番号と共にこのコードを Authy APIへ送り、認証する
・正しければこの番号を認証したとみなす

たった2回の APIコールと 2つのWebページで、ユーザーの電話番号を認証することができるのです。
では、アプリを作っていきましょう。

Authyを使って電話認証を簡単にする

前述のステップのデモンストレーションとなるように、簡単な入力フォームから作り始めます。
verify.py という名前で以下のコードを作成してください。

from authy.api import AuthyApiClient
from flask import Flask

app = Flask(__name__)
app.config.from_object('config')

api = AuthyApiClient(app.config['AUTHY_API_KEY'])

@app.route("/phone_verification", methods=["GET", "POST"])
def phone_verification():
pass

if __name__ == '__main__':
app.run(debug=True)

このコードは、コントローラーのようなもので、リクエストを正しい場所へルーティングする役割をします。まずは、phone_verification のルートをアップデートします。

認証を行うためには、ユーザーの電話番号と国番号を入力させる必要があります。templates という名前のフォルダを作成し、phone_verification.html という名前のファイルを作ります。有効な国番号だけを受け付けるようにしたいのですが、素晴らしいことに Authy はすでに お手軽フォームヘルパー を作ってくれています。では、phone_verification.html に以下のコードを書いてください。これは、ユーザーから電話番号・国番号・認証手段(音声かSMSか)を入力させるHTMLフォームになります。

<html>
<head>
<title>Phone Verification</title>
<link rel="stylesheet" media="all"
href="https://cdnjs.cloudflare.com/ajax/libs/authy-form-helpers/2.3/form.authy.min.css"
data-turbolinks-track="reload" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/authy-form-helpers/2.3/form.authy.min.js"
data-turbolinks-track="reload"></script>
</head>
<body>
<h1>Verify your phone number</h1>
<form action="/phone_verification" accept-charset="UTF-8" method="post">
<div>
<label for="authy-countries">Country code:</label>
<select name="country_code" id="authy-countries" data-show-as="number"></select>
</div>
<div>
<label for="phone_number">Phone number:</label>
<input type="tel" name="phone_number" id="phone_number" />
</div>
<div>
<p>Verification method</p>
<label for="method_sms">SMS: </label>
<input type="radio" name="method" id="method_sms" value="sms" />
<label for="method_call">Call: </label>
<input type="radio" name="method" id="method_call" value="call" />
</div>
<button name="button" type="submit">Verify</button>
</form>
</body>
</html>

verify.py コントローラーへ戻り、テンプレートをルートへ紐付けます。

from flask import Flask, render_template

@app.route("/phone_verification", methods=["GET", "POST"])
def phone_verification():
return render_template("phone_verification.html")

ターミナルから python verify.py を実行してサーバーを起動したら、
ブラウザから http://localhost:5000/phone_verification にアクセスしてみます。
以下のようなページが現れたでしょうか?

まだ Verify を押しても何も起こりません。まだ続きがあるのです。このフォームは phone_verification に POSTリクエストを送信します。このリクエストを処理する部分を verify.py に加えます。

from flask import Flask, render_template, request

@app.route("/phone_verification", methods=["GET", "POST"])
def phone_verification():
if request.method == "POST":
country_code = request.form.get("country_code")
phone_number = request.form.get("phone_number")
method = request.form.get("method")

return render_template("phone_verification.html")

これでフォームに入力されたものを受取り、電話認証に必要な情報が集められました。
そして、以下の api.phones で始まる1行が、APIコールを発生させる部分です。

@app.route("/phone_verification", methods=["GET", "POST"])
def phone_verification():
if request.method == "POST":
country_code = request.form.get("country_code")
phone_number = request.form.get("phone_number")
method = request.form.get("method")

api.phones.verification_start(phone_number, country_code, via=method)

return render_template("phone_verification.html")


Tokenのバリデーション

ユーザーへ電話をかけるかSMSを送るかして認証コード(Token)が送られました。次は、このTokenを入力する部分を作ります。verify.py/phone_verification ルートの下へ新しいルートを加えます。

@app.route("/verify", methods=["GET", "POST"])
def verify():
return render_template("verify.html")

新しいテンプデートを templateフォルダの下に verify.html という名前で作成し、
中身を以下のようにします。

<html>
<head>
<title>Phone Verification</title>
</head>
<body>
<h1>Challenge</h1>
<form action="/verify" accept-charset="UTF-8" method="post">
<div>
<label>Enter the code you were sent:</label>
<input name="token" id="token">
</div>
<button name="button" type="submit">Verify</button>
</form>
</body>
</html>

そして、このページへリダイレクトしてユーザーがTokenを認証できるようにします。
verify.py を更新します。

from flask import Flask, render_template, request, redirect, url_for

@app.route("/phone_verification", methods=["GET", "POST"])
def phone_verification():
if request.method == "POST":
country_code = request.form.get("country_code")
phone_number = request.form.get("phone_number")
method = request.form.get("method")

api.phones.verification_start(phone_number, country_code, via=method)

return redirect(url_for("verify"))
return render_template("phone_verification.html")

次は、Tokenのチェックです。Authy API を呼ぶ /verify のエンドポイントへPOSTを送信します。認証には、電話番号・国番号・Token が必須ですが、ユーザーにまた電話番号を聞きたくありません。そこで、Flask sessions を使い、この情報をストアします。

最初に、秘密鍵(secret_key)をセットし、セッションを初期化できるようにします。ここはランダムな文字列でも何でも構いません。

app = Flask(__name__)
app.config.from_object('config')
app.secret_key = 'super-secret'

/phone_verification ルートをアップデートし、電話番号と国番号をこのユーザーのセッションに保存します。

from flask import Flask, render_template, request, redirect, url_for, session

@app.route("/phone_verification", methods=["GET", "POST"])
def phone_verification():
if request.method == "POST":
country_code = request.form.get("country_code")
phone_number = request.form.get("phone_number")
method = request.form.get("method")

session['country_code'] = country_code
session['phone_number'] = phone_number

api.phones.verification_start(phone_number, country_code, via=method)

return redirect(url_for("verify"))

return render_template("phone_verification.html")

これらの情報は、入力された Token を使って /verify エンドポイントから取得できます。verify エンドポイントを以下のコードでアップデートします。

from flask import Flask, render_template, request, redirect, url_for, session, Response

@app.route("/verify", methods=["GET", "POST"])
def verify():
if request.method == "POST":
token = request.form.get("token")

phone_number = session.get("phone_number")
country_code = session.get("country_code")

return render_template("verify.html")

最後に、SDKコールを加え、トークンの認証を実現します。

@app.route("/verify", methods=["GET", "POST"])
def verify():
if request.method == "POST":
token = request.form.get("token")

phone_number = session.get("phone_number")
country_code = session.get("country_code")

verification = api.phones.verification_check(phone_number,
                  country_code,token)

if verification.ok():
return Response("<h1>Success!</h1>")

return render_template("verify.html")

では、このアプリを動かしてみましょう。ターミナルから python verify.py を実行し、
ブラウザから http://localhost:5000/phone_verification を開きます。

電話番号認証を活用しましょう

たった2つのAPIコールと2つのWebページだけで、そのユーザー自身がアクセス可能であり、かつ、実在する電話番号を持っているかを確認することができるようになりました。このような電話番号認証は、不正なアカウントを大量に作る行為を抑止し、みなさんのアプリケーションから音声やテキストで通知を行う際の到達性に信頼が持てるようになります。

ユーザーを認証したあとは、確認が取れた電話番号にフラグを立てて保存したい、といった要望が出てくると思います。→こちらのGithub にフルコードを掲載していますので参考にしてください。

この記事をシェア


最新記事

すべての記事へ