[Twilio+Python] 認証コードを読み上げる音声通話認証の実装

[Twilio+Python] 認証コードを読み上げる音声通話認証の実装

SMS が使えない環境や固定電話ユーザー向けに、Twilio Programmable Voice を使って 6 桁の認証コードを自動音声で読み上げる電話認証機能を実装します。TwiML を直接 API に渡すだけで完結する最小構成です。

対象読者

  • SMS が使えない環境での認証手段を探している人
  • Twilio の Programmable Voice を活用したい開発者
  • 高齢者や視覚障害者向けの UI/UX を検討している Web サービス提供者
  • 簡単な通話通知・読み上げ機能を API 経由で実装したい人
検証環境

バージョン

python: 3.13.2
Flask: 3.1.0
python-dotenv: 1.1.0
twilio: 9.5.2

概要

この記事では、Twilio Programmable Voice を活用し、Web アプリからのリクエストをトリガーにして音声通話を発信し、 6 桁の認証コードを自動音声で読み上げる仕組みを実装する方法を紹介します。SMS 認証が使えない環境や、音声による案内が適している高齢者ユーザー、固定電話利用者などにとって、音声通話を用いた認証は有効な代替手段です。

今回は、Twilio Voice API の /calls エンドポイントに直接 TwiML を渡して完結させる最小構成での実装にフォーカスします。TwiML について詳しくは こちら

システム構成の流れ

  1. ユーザーが Web アプリで電話番号を入力して「認証コードを送信」ボタンを押す
  2. サーバー側で 6 桁の認証コードを生成し、DB またはメモリに保存
  3. Twilio の /calls API を呼び出し、twiml パラメータで読み上げ内容を直接指定
  4. ユーザーの電話に Twilio から着信し、自動音声でコードが読み上げられる

1

実装

環境構築

今回の実装は、Python(Flask)を用いて Web サーバーを立ち上げ、Twilio の Voice API を呼び出す構成です。Twilio の /calls API に直接 TWiML を渡して完結させます。

  1. プロジェクトの作成

任意の作業ディレクトリを作成し、初期化します。

mkdir twilio-voice-auth-python
cd twilio-voice-auth-python
python -m venv venv
source venv/bin/activate  # Windows の場合は venv\Scripts\activate
  1. 必要なパッケージのインストール
pip install Flask twilio python-dotenv
  1. .env ファイルの作成

ルートディレクトリに .env ファイルを作成し、Twilio のアカウント情報を記述します。

TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TWILIO_AUTH_TOKEN=your_auth_token
TWILIO_CALLER_ID=+81XXXXXXXXXX
  • TWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKEN は Twilio コンソールで確認できます。
  • TWILIO_CALLER_ID には Twilio で取得した発信元の電話番号(E.164 形式)を設定してください。
  1. app.py の作成

以降で紹介する /start-call および /verify-code のコードを app.py に実装します。

認証コードを生成し音声通話で読み上げる

ユーザーが「電話番号を入力 → 認証コード送信ボタンを押す」と、サーバー側で 6 桁の認証コードが自動生成され、Twilio の /calls API によってユーザーの電話へ音声通話が発信されます。このとき、コードは1桁ずつ読み上げられるよう、工夫して TwiML を作成しています。

まずは、認証コードの生成と音声通話の発信を行う /start-call API のコードです。

# app.py
from flask import Flask, request, Response
from twilio.rest import Client
from dotenv import load_dotenv
import os
import random
import threading
import json

load_dotenv()

app = Flask(__name__)
client = Client(os.getenv("TWILIO_ACCOUNT_SID"), os.getenv("TWILIO_AUTH_TOKEN"))

# メモリ上にコードを保持(本番では Redis 推奨)
code_storage = {}

def delete_code(phone):
    code_storage.pop(phone, None)

@app.route("/start-call", methods=["POST"])
def start_call():
    phone = request.json.get("phoneNumber")
    code = str(random.randint(100000, 999999))
    code_storage[phone] = code

    # 5分後に自動削除
    threading.Timer(300, delete_code, [phone]).start()

    spaced = ", ".join(code)
    twiml = f"""
    <Response>
      <Say language="ja-JP" voice="alice">こんにちは。あなたの認証コードは、</Say>
      <Pause length="1"/>
      <Say language="ja-JP" voice="alice">{spaced}</Say>
      <Say language="ja-JP" voice="alice">です。</Say>
    </Response>
    """

    try:
        call = client.calls.create(
            to=phone,
            from_=os.getenv("TWILIO_CALLER_ID"),
            twiml=twiml
        )
        result = {
            "success": True,
            "message": "通話を発信しました。",
            "callSid": call.sid
            # "code": code  # ← デバッグ時は表示してOK
        }
        return Response(json.dumps(result, ensure_ascii=False), mimetype="application/json")
    except Exception as e:
        result = {
            "success": False,
            "message": "通話の発信に失敗しました。",
            "error": str(e)
        }
        return Response(json.dumps(result, ensure_ascii=False), mimetype="application/json"), 500

認証コードを照合する

通話後、ユーザーが受け取った認証コードを Web アプリ上のフォームに入力した場合、そのコードとサーバー側に保存されたコードを照合する API を用意できます。

@app.route("/verify-code", methods=["POST"])
def verify_code():
    phone = request.json.get("phoneNumber")
    input_code = request.json.get("code")
    expected = code_storage.get(phone)

    if not expected:
        result = {"success": False, "message": "コードが期限切れまたは存在しません。"}
        return Response(json.dumps(result, ensure_ascii=False), mimetype="application/json"), 400

    if input_code == expected:
        code_storage.pop(phone, None)
        result = {"success": True, "message": "認証成功"}
        return Response(json.dumps(result, ensure_ascii=False), mimetype="application/json")
    else:
        result = {"success": False, "message": "認証コードが一致しません。"}
        return Response(json.dumps(result, ensure_ascii=False), mimetype="application/json"), 401
in-memory の注意点

この例では dict を使って簡易的に認証コードを保存していますが、以下のような制限があります。

注意点 内容
スケールしない 複数プロセス間でコードの共有ができない
プロセスが落ちると消える プロセスが落ちた時点で認証コードも失われる
本番運用には不向き Redis や RDB への保存が推奨される(TTL付き)

サーバーの起動

最後に、 Flask サーバーを起動するための行を追加します。

if __name__ == "__main__":
    app.run(port=3000)

動作確認

この節では、今回紹介した認証コードの音声通話発信機能を実際に動かして確認する手順を紹介します。

  1. サーバーを起動

    python app.py
    
  2. 別のターミナルや Postman、curl を使って /start-call にリクエストを送信

    curl -X POST http://localhost:3000/start-call \
      -H "Content-Type: application/json" \
      -d '{"phoneNumber": "+81XXXXXXXXXX"}'
    
  3. 数秒後、指定した電話番号に Twilio から着信があり、6桁のコードが自動音声で読み上げられます。

  4. 読み上げられたコードを使って照合します。

curl -X POST http://localhost:3000/verify-code \
  -H "Content-Type: application/json" \
  -d '{"phoneNumber": "+81XXXXXXXXXX", "code": "読み上げられた6桁"}'
  1. 成功すると次のようなレスポンスが返されます
{ "success": true, "message": "認証成功" }
失敗時

例えば次のようなレスポンスが返されます。

{ "success": false, "message": "認証コードが一致しません。" }

おわりに

本記事では、Twilio の Voice API を使って 音声による認証コード読み上げを最小構成で実装する方法を紹介しました。この方式は、以下のような用途に応用可能です:

  • 高齢者向けの電話による本人確認
  • SMS が使えない固定電話環境での通知
  • ユーザー体験の向上を目的とした音声インターフェースの導入

今後は、音声認識や DTMF 入力を使った双方向通話への展開、再生音声のカスタマイズ、スケーラブルな構成への移行なども検討の余地があります。ぜひこの例を出発点に、より豊かなユーザー体験を設計してみてください。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.