Amazon Pinpointでワンタイムパスワード(OTP)の発行と検証が出来るようになりました

2021.11.28

いわさです。

Amazon Pinpointへ何と、ワンタイムパスワード(OTP)管理機能が登場しました。

PinpointのAPIとして以下が提供されています。

  • OTPの発行
  • OTPの検証

従来はPinpointやSNS、あるいは別のサービスを使ってSMSを送信しつつ、OTPの管理を行って自前で実装していたと思いますが、新たに提供されるPinpointのAPI を使うだけで自前での管理が不要になります。これはすごい。

本日はOTPの発行・検証と有効・無効の条件などを動かしながら少し確認してみました。

OTP送信と検証

OTP送信、検証はマネージメントコンソールでは提供されておらず、AWS CLIを使った実装が必要です。
また、サンドボックスSMSアカウントでは利用できず、本番環境へ移行済みである必要もあります。

いつものように、新機能ですのでAWS CLIのバージョンアップが必要です。(v1.22.14+/v2.4.2+)
また、実行時のパラメータとして、プロジェクトIDと送信元電話番号が必要なので事前に作成・取得をしておいてください。

送信

送信にはsend-otp-messageを使用します。

本日時点の冒頭のドキュメントではコマンドやパラメータが誤っているようなのでCLIのリファレンス側を見て頂いたほうが良いです。

iwasa.takahito@hoge ~ % aws pinpoint send-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --send-otp-message-request-parameters '{
   "Channel": "SMS",
   "BrandName": "Hoge iwasa",
   "CodeLength": 5,
   "ValidityPeriod": 20,
   "AllowedAttempts": 5,
   "OriginationIdentity": "+1xxxyyyzzzz",
   "DestinationIdentity": "+81080xxxxxxxx",
   "ReferenceId": "iwasa-ref1"
}' --profile hoge
{
    "MessageResponse": {
        "ApplicationId": "08ab58337502488fa2aa7993b8227d21",
        "RequestId": "560bc36b-45c1-4b91-be73-367b55b44b94",
        "Result": {
            "+81080xxxxxxxx": {
                "DeliveryStatus": "SUCCESSFUL",
                "MessageId": "avmnuf34es9i066dansehhi7e5h10ifv9k798cg0",
                "StatusCode": 200,
                "StatusMessage": "MessageId: avmnuf34es9i066dansehhi7e5h10ifv9k798cg0"
            }
        }
    }
}

ReferenceIdに何を設定すれば良いか悩むと思います。
ReferenceIdは毎度ランダムで生成するか、一つの電話番号で同じ参照IDを生成するのが良いかなと思います。

生成したIDはOPT検証時に必要で、検証サーバー側で保持する必要があります。
前者の場合だと生成したIDを保存する必要がありますが、検証ロジックに外部から注入される恐れがなければ後者の場合だとハッシュ生成などで対応出来るのでデータベースなどでの管理がいらないかなと思います。
詳細はこの後の検証結果を見て頂くとわかるのですが、同一電話番号で複数発行されたワンタイムパスワードは自動で過去のものが無効になるためです。

私が確認した際には毎回数秒で配送されました。

届きましたね。

検証

検証には、verify-otp-messageを使用します。

まずは間違ったOTPを使ってみましょう。

iwasa.takahito@hoge ~ % aws pinpoint verify-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --verify-otp-message-request-parameters '{
   "DestinationIdentity": "+81080xxxxxxxx",
   "ReferenceId": "iwasa-ref1",
   "Otp": "70919"
}' --profile hoge
{
    "VerificationResponse": {
        "Valid": false
    }
}

結果がfalseになりましたね。

次に、正しいOTPで検証してみます。

iwasa.takahito@hoge ~ % aws pinpoint verify-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --verify-otp-message-request-parameters '{
   "DestinationIdentity": "+81080xxxxxxxx",
   "ReferenceId": "iwasa-ref1",
   "Otp": "70918"
}' --profile hoge
{
    "VerificationResponse": {
        "Valid": true
    }
}

検証結果がtrueになりました。

一度検証通したあとに同じOTPが使えるのかを確認してみましょう。

iwasa.takahito@hoge ~ % aws pinpoint verify-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --verify-otp-message-request-parameters '{
   "DestinationIdentity": "+81080xxxxxxxx",
   "ReferenceId": "iwasa-ref1",
   "Otp": "70918"
}' --profile hoge
{
    "VerificationResponse": {
        "Valid": false
    }
}

一度検証に使われて通過したOTPは再利用出来ないですね。
とても良いです。

提供されているのはこのように送信と検証のみです。
この結果を以て、既存ユーザーや送信先がOTP検証済みかどうかは実装側が責任をもつ必要があります。

ReferenceIdについて

どういう値を設定したら良いのか最初悩んだのでいくつかパターンを試してみました。
見解は先程記述しているとおりですが、どういう挙動をするのか把握しておくと、どのように参照IDを生成したら良いか検討しやすいかと思います。

一度発行したReferenceIdは使えるのか?

先程使った、既に検証済みのReferenceIdを使いまわしてみましょう。

iwasa.takahito@hoge ~ % aws pinpoint send-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --send-otp-message-request-parameters '{
   ...
   "DestinationIdentity": "+81080xxxxxxxx",
   "ReferenceId": "iwasa-ref1"
}' --profile hoge
{
    "MessageResponse": {
        "ApplicationId": "08ab58337502488fa2aa7993b8227d21",
        "RequestId": "c1de88eb-811c-496b-9f1c-a1e789e90798",
        "Result": {
            "+81080xxxxxxxx": {
                "DeliveryStatus": "SUCCESSFUL",
                "MessageId": "pgh6devmivnejkef4prnaftuh29genshq690hl80",
                "StatusCode": 200,
                "StatusMessage": "MessageId: pgh6devmivnejkef4prnaftuh29genshq690hl80"
            }
        }
    }
}

発行出来ました。先程とは別のOTPが発行されていますね。

ReferenceIdは使い回すことが出来ます。
Pinpoint側ではReferenceIdを永続的に管理しているわけではなさそうですね。
重複によるエラーなどは心配しなくても良さそうです。

同じ電話番号へ同一ReferenceIdを使って連続で送った場合に、どれも有効になるのか?

先程、検証したあとに別のOTPが発行されていることが確認出来ましたが、検証前に同一の電話番号へ同一ReferenceIdで発行した場合、OTPはどうなるでしょうか。

iwasa.takahito@hoge ~ % aws pinpoint send-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --send-otp-message-request-parameters '{
   ...
   "DestinationIdentity": "+81080xxxxxxxx",
   "ReferenceId": "iwasa-ref1"

iwasa.takahito@hoge ~ % aws pinpoint send-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --send-otp-message-request-parameters '{
   ...
   "DestinationIdentity": "+81080xxxxxxxx",
   "ReferenceId": "iwasa-ref1"
   ...

別々のOTPが発行されていますね。
検証してみましょう。

iwasa.takahito@hoge ~ % aws pinpoint verify-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --verify-otp-message-request-parameters '{
"DestinationIdentity": "+81080xxxxxxxx",
"ReferenceId": "iwasa-ref1",
"Otp": "48922"
}' --profile hoge
{
    "VerificationResponse": {
        "Valid": false
    }
}
iwasa.takahito@hoge ~ % aws pinpoint verify-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --verify-otp-message-request-parameters '{
"DestinationIdentity": "+81080xxxxxxxx",
"ReferenceId": "iwasa-ref1",
"Otp": "28410"
}' --profile hoge
{
    "VerificationResponse": {
        "Valid": true
    }
}

どうやら先に送ったものは無効になるようです。
最後に送ったものだけが常に有効になるので、過去に送ったタイミングや有効期間を管理する必要はないです。これもとても良いですね。

同じ電話番号へ別ReferenceIdで連続で送った場合に、どれも有効になるのか?

先程は同一のReferenceIdを使いましたが、別のReferenceIdだとどういう挙動になるでしょうか。
どちらのOTPも有効になるのでしょうか。

iwasa.takahito@hoge ~ % aws pinpoint send-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --send-otp-message-request-parameters '{
   ...
   "DestinationIdentity": "+81080xxxxxxxx",
   "ReferenceId": "iwasa-ref1"
   ...
iwasa.takahito@hoge ~ % aws pinpoint send-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --send-otp-message-request-parameters '{
   ...
   "DestinationIdentity": "+81080xxxxxxxx",
   "ReferenceId": "iwasa-ref2"
   ...

検証してみます。

aws pinpoint verify-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --verify-otp-message-request-parameters '{
"DestinationIdentity": "+81080xxxxxxxx",
"ReferenceId": "iwasa-ref1",
"Otp": "96558"
}' --profile hoge

aws pinpoint verify-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --verify-otp-message-request-parameters '{
"DestinationIdentity": "+81080xxxxxxxx",
"ReferenceId": "iwasa-ref2",
"Otp": "38966"
}' --profile hoge

先程と同じ結果になりました。
この結果から、1つの送信先電話番号につき有効なOTPは1つと考えることが出来ます。
もし参照IDをランダムに生成してデータベースで管理するような仕組みを取る場合でも管理する参照IDはひとつで良いですね。後勝ちです。

別々の電話番号に同一ReferenceIdを送付するとどうなる?

先程の結果からすると電話番号のみで一意としているようにも見えますが、ReferenceIdのみでも一意としているのでしょうか。
ここでは同一のReferenceIdを使って別々の電話番号へOTPを発行し、検証してみます。

iwasa.takahito@hoge ~ % aws pinpoint send-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --send-otp-message-request-parameters '{
   ...
   "DestinationIdentity": "+81090zzzzzzzz",
   "ReferenceId": "iwasa-ref-2"
   ...
iwasa.takahito@hoge ~ % aws pinpoint send-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --send-otp-message-request-parameters '{
   ...
   "DestinationIdentity": "+81080xxxxxxxx",
   "ReferenceId": "iwasa-ref-2"
   ...

検証してみましょう。

iwasa.takahito@hoge ~ % aws pinpoint verify-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --verify-otp-message-request-parameters '{
"DestinationIdentity": "+81090zzzzzzzz",
"ReferenceId": "iwasa-ref-2",
"Otp": "358047"
}' --profile hoge
{
    "VerificationResponse": {
        "Valid": true
    }
}
iwasa.takahito@hoge ~ % aws pinpoint verify-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --verify-otp-message-request-parameters '{
"DestinationIdentity": "+81080xxxxxxxx",
"ReferenceId": "iwasa-ref-2",
"Otp": "953999"
}' --profile hoge
{
    "VerificationResponse": {
        "Valid": true
    }
}

なんと、どちらのOTPも有効でした。
この結果からすると、参照IDは一意に特定するための情報ではなく、送信先電話番号で一意にして付加情報として使用するパスワードのようなものに近いかもしれません。

多言語化

さて、ここまで検証を繰り返してきましたがメッセージは英語でしたね。
このAPIではメッセージの言語を指定するオプションがありますので試してみましょう。

iwasa.takahito@hoge ~ % aws pinpoint send-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --send-otp-message-request-parameters '{
   "Channel": "SMS",
   "BrandName": "ほげいわさ",
   "CodeLength": 6,
   "ValidityPeriod": 20,
   "AllowedAttempts": 5,
   "OriginationIdentity": "+1xxxyyyzzzz",
   "DestinationIdentity": "+81080xxxxxxxx",
   "ReferenceId": "iwasa-ref-2",
   "Language": "ja-JP"
}' --profile hoge

日本語、ドイツ語、韓国語で指定してみました。

それぞれの言語に変換されています。
なお、パラメータで指定するBrandNameは言語に関係なくどういった文字列でも指定可能です。

試行回数の確認

最後に試行回数を確認してみます。
AllowedAttemptsで指定出来ることはわかっているのですが、境界値で失敗するのか、そこまで許容されるのか、自信がなかったので確認したいと思ったためです。

AllowedAttemptsに 2 を指定し、2回失敗後に成功させてみます。

iwasa.takahito@hoge ~ % aws pinpoint send-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --send-otp-message-request-parameters '{
   "Channel": "SMS",
   "BrandName": "ほげいわさ",
   "CodeLength": 6,
   "ValidityPeriod": 20,
   "AllowedAttempts": 2,
   "OriginationIdentity": "+1xxxyyyzzzz",
   "DestinationIdentity": "+81080xxxxxxxx",
   "ReferenceId": "iwasa-ref-2",
   "Language": "ja-JP"
}' --profile hoge
{
    "MessageResponse": {
        "ApplicationId": "08ab58337502488fa2aa7993b8227d21",
        "RequestId": "883187eb-55fe-48dc-9e04-cfb5edab7749",
        "Result": {
            "+81080xxxxxxxx": {
                "DeliveryStatus": "SUCCESSFUL",
                "MessageId": "bo8j53tt7je2b2dev8cpfchj3bnse19l8g3prno0",
                "StatusCode": 200,
                "StatusMessage": "MessageId: bo8j53tt7je2b2dev8cpfchj3bnse19l8g3prno0"
            }
        }
    }
}
iwasa.takahito@hoge ~ % aws pinpoint verify-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --verify-otp-message-request-parameters '{
"DestinationIdentity": "+81080xxxxxxxx",
"ReferenceId": "iwasa-ref-2",
"Otp": "858675"
}' --profile hoge
{
    "VerificationResponse": {
        "Valid": false
    }
}
iwasa.takahito@hoge ~ % aws pinpoint verify-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --verify-otp-message-request-parameters '{
"DestinationIdentity": "+81080xxxxxxxx",
"ReferenceId": "iwasa-ref-2",
"Otp": "858675"
}' --profile hoge
{
    "VerificationResponse": {
        "Valid": false
    }
}
iwasa.takahito@hoge ~ % aws pinpoint verify-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --verify-otp-message-request-parameters '{
"DestinationIdentity": "+81080xxxxxxxx",
"ReferenceId": "iwasa-ref-2",
"Otp": "858676"
}' --profile hoge
{
    "VerificationResponse": {
        "Valid": false
    }
}

AllowedAttemptsまで失敗するとOTPが無効になることがわかりました。
AllowedAttemptsの境界値で成功した場合に検証がOKとなるか念の為確認してみます。

iwasa.takahito@hoge ~ % aws pinpoint send-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --send-otp-message-request-parameters '{
   "Channel": "SMS",
   "BrandName": "ほげいわさ",
   "CodeLength": 6,
   "ValidityPeriod": 20,
   "AllowedAttempts": 2,
   "OriginationIdentity": "+1xxxyyyzzzz",
   "DestinationIdentity": "+81080xxxxxxxx",
   "ReferenceId": "iwasa-ref-2",
   "Language": "ja-JP"
}' --profile hoge
{
    "MessageResponse": {
        "ApplicationId": "08ab58337502488fa2aa7993b8227d21",
        "RequestId": "35fb7626-32f1-4eb7-9fcd-a8e579e9e3de",
        "Result": {
            "+81080xxxxxxxx": {
                "DeliveryStatus": "SUCCESSFUL",
                "MessageId": "cpopjujm71amhma3pn7s5maoe6468bepoi3hqi80",
                "StatusCode": 200,
                "StatusMessage": "MessageId: cpopjujm71amhma3pn7s5maoe6468bepoi3hqi80"
            }
        }
    }
}
iwasa.takahito@hoge ~ % aws pinpoint verify-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --verify-otp-message-request-parameters '{
"DestinationIdentity": "+81080xxxxxxxx",
"ReferenceId": "iwasa-ref-2",
"Otp": "877255"
}' --profile hoge
{
    "VerificationResponse": {
        "Valid": false
    }
}
iwasa.takahito@hoge ~ % aws pinpoint verify-otp-message --application-id 08ab58337502488fa2aa7993b8227d21 --verify-otp-message-request-parameters '{
"DestinationIdentity": "+81080xxxxxxxx",
"ReferenceId": "iwasa-ref-2",
"Otp": "877256"
}' --profile hoge
{
    "VerificationResponse": {
        "Valid": true
    }
}

期待どおり、試行回数以内だとOTPは有効でした。

注意点

送信先がインドの場合は特殊な要件があるので注意してください。
事前に必要な手続きを行った上で、API呼び出し時に追加のパラメータが必要になるようです。
詳細は以下を参照ください。

まとめ

本日はAmazon Pinpointを使ってOPTの発行、検証機能を試してみました。
今まで独自実装する必要があった部分がマネージドな機能で賄えるようになったのはとても大きいと思います。

気になった点としては、OPTに限った話ではないですが検証中何度かSMSが到達しないことがありました。
必ず到達しないわけではなく、時間を置いたり、別の送信元電話番号を使うと到達したりしました。
導入を検討される方はそのあたりを気にしてよく検証をして頂いたほうが良いかと思います。

サービスごとの到達率もあるとは思いますが、やはりSMSを使ったシステムは到達しない場合の選択肢を与えるなど、到達しないこともあり得る前提での機能設計が必要だと感じました。