この記事は VonageコミュニケーションAPIを使ってみた、Vonageのことなら何でも共有しよう! by Vonage Advent Calendar 2022 に参加しています。
はじめに
私は昨年、Vonageさんのアドベントカレンダーを見かけて ↓ のような記事で参加させてもらいました。GoのSDKが見当たらなかったので作ってみる!という試みでしたが、終盤で「あれ、まだ大々的に紹介されてないけどGoのSDK開発中ですね!」と気づくという、ちょっと残念な結果に
今回はそのリベンジの位置付けで、VonageさんのVerify API
をSDKを活用して使用する方法を、シンプルなWebアプリケーション例を通じて試してみました。
この記事の内容
- おさらい:Vonageから提供されているサービス
- 電話番号を使った多要素認証の流れ
- GoによるWebアプリケーションでの電話番号認証サンプル
こんな人におすすめの記事です
- CPaaSを使ってみたい方
- 電話番号を使った多要素認証の流れを理解したい方
- なんでもGo言語で書きたい方
おさらい:Vonageから提供されているサービス
VonageはCPaaS(Communication Platform as a Service)の1つで、企業と顧客のコミュニケーションだったり、従業員同士のコミュニケーションを支える様々なサービスを提供しています。
CPaaSって何?Vonageではどのようなことができるの?については昨年のアドベントカレンダー記事が大変分かりやすいです。
上記の記事から年も跨いだことなので、一応公式サイトのトップページから、現在提供されているサービス一覧も確認してみましょう。
今回はこの中から コミュニケーションAPI に焦点を当ててみます。(個人で触ってみるレベルのサービスはコミュニケーションAPIぐらいしか無いと思いましたので。。。)
コミュニケーションAPIはさらにVoice API, Video API, Message APIの3つに分けられています。昨年に続き、Message APIの中から、 簡単に多要素認証を導入可能なVerify API
を使ってみたいと思います。
電話番号を使った多要素認証の流れ
上記ドキュメントにも記載されていますが、ベーシックな流れは次のとおりです。
上図における「電話番号認証の開始」や「認証リクエストIDとPINを送信」など、WebサービスがVonageに対して行うリクエストの例は ↓ に多様な例が紹介されています。助かりますね。
ただ実際には、リクエストIDをどうやってWebサービス側で保持しておくのか、など、少し考えてしまうこともあるのではないでしょうか?その疑問に対しては次の例が大変参考になります。Node.jsの場合について、認証開始から完了までの一連の流れをサンプルアプリケーションを交えて解説してくれています。
さて、しかしながら私はGo言語で書きたいところです(ただの趣味)。そこで活躍するのがVonageから提供されているGo SDKですね。
ただ、最近はあまり活発に開発が行われていないのでしょうか?今回サンプルアプリケーションを作る中でSDKに対してバグ修正のプルリクを出したのですがなかなかマージされません。お願いしますね!
コマンドラインで使う簡易なアプリ例は上記リポジトリに掲載されています。これとNode.jsの例を参考に電話番号認証をする簡単なWebアプリケーションサンプルを構築してみたいと思います。
GoによるWebアプリケーションでの電話番号認証サンプル
成果物イメージ
作成するWebアプリケーションの概略は次のとおりです。
- ユーザーがWebアプリにアクセスしたとき、電話番号の認証が済んでいない場合には、電話番号入力フォームが表示されます。
- 認証を開始するとユーザーにはPINコード入力画面が表示されます。このとき、WebアプリはVonageに認証リクエストを送信しており、ユーザーにはVonageからSMSを通じてPINコードが送信されます。
- ユーザーがPINコードを用いて認証を行うとトップページにリダイレクトされます。電話番号認証が済んでいることをWebアプリ内の状態として保持しているため、最初とは異なるメッセージが表示されます。
実際のWebアプリでは、認証済み情報をそのまま使うというより、DBに「あるユーザーが認証済みであるかどうか」を記録すると思いますが、今回は簡単のために上記仕様としています。
実装
事前準備
- Vonageにユーザ登録し、Verify APIのAPI KeyとAPI Secretを取得する
-
取得した情報を
VONAGE_API_KEY
,VONAGE_API_SECRET
という名前で環境変数に登録する
環境・SW関連
- Go1.19.2
- Webフレームワーク: echo v4.9.1
- echo middleware: echo-contrib(session) v0.13.0
詳細
コード全体は以下のリポジトリに保存しました。
あくまでサンプルアプリケーション向けのため、入力のバリデーションや、アクセス制限など、プロダクションを想定した時に必要な設定は不足しています。
本節ではポイントを絞って解説していきます。
Sessionを使ったWebアプリ内の状態保持
ユーザーが電話番号をWebアプリに送信した後、WebアプリからVonageに認証プロセスの開始をリクエストします。リクエストが成功すると、この後のPINコード確認に必要なrequestID
がVonageからWebアプリに返されるため、これをWebアプリ内で保持しておく必要があります。
Node.jsの例でもサーバサイドでの状態保持にsessionを利用しています。Goでは標準モジュールでセッションをサポートしていないため、自作するか公開モジュールを利用しますが、今回はWebフレームワークのechoの中でsessionを使っていきます。(なお内部的にはgithub.com/gorilla/sessions
が使われています)
func verify(c echo.Context) error {
tel := c.FormValue("tel")
if !isValidTel(tel) {
return c.HTML(
http.StatusBadRequest,
`<html><p>invalid telephone number</p><a href="/">go to home</a></html>`,
)
}
// start verify process using vonage api
reqID, err := requestVerify(c) //この関数の中身は後ほど紹介します
if err != nil && !errors.Is(err, errConcurrentRequest) {
return c.String(http.StatusInternalServerError, err.Error())
}
// save requestID into session
sess, err := session.Get(sessionName, c)
if err != nil {
return c.String(http.StatusInternalServerError, fmt.Sprintf("fail to call session: %s", err))
}
sess.Options = &sessions.Options{
Path: "/",
MaxAge: sessionMaxAge,
HttpOnly: true,
Secure: true,
}
sess.Values["requestID"] = reqID
sess.Save(c.Request(), c.Response())
// show form to send pin code
return c.Render(http.StatusOK, "pinform", nil)
}
- この例では簡単のため、UXを損ねるかもしれませんが、Vonageへの認証リクエスト、応答待ち、PIN入力フォーム表示を順番に実施しています。
- ただ試してみて興味深かったのですが、Vonageへの認証リクエストから応答が返ってくるまでのラグはほとんど気になりませんでした。高速に応答してくれるのはとても嬉しいですね。
- sessionへのデータ保存はechoのサンプルが大変分かりやすいです。
- なお、上記のコードはアプリケーションの状態遷移としては ↓ の部分に相当します。
sessionはPINコードのチェックでも同様に活用しますが、そちらはリポジトリ上のコードを参照下さい。
Vonage SDKを使った認証
電話番号をVonage Verify APIを使って認証するには、認証プロセスごとに割り当てられたrequestID
と、ユーザーにSMSで送信されるPINコード(pin
)の2つが必要です。
APIをhttp.POST
などで直接叩いても良いですが、さまざまなオプションもあるためSDKを使うとより気軽に利用することが可能です。
まず認証プロセスを開始し、RequestIDを取得する処理を説明します。
func requestVerify(c echo.Context) (reqID string, _ error) {
auth := vonage.CreateAuthFromKeySecret(apiKey, apiSecret)
verifyClient := vonage.NewVerifyClient(auth)
resp, respErr, err := verifyClient.Request(c.FormValue("tel"), brandName, vonage.VerifyOpts{
// https://developer.vonage.com/api/verify
PinExpiry: sessionMaxAge,
Lg: "ja-jp",
WorkflowID: 6, //only sms (one time)
})
// some error handling...
return resp.RequestId, nil
}
- 処理はとてもシンプルで、事前に取り込んでおいた
apiKey
とapiSecret
を用いてクライアントを生成し、それに認証する電話番号(c.FormValue("tel")
)を渡しています。- このとき、オプションをいくつか設定しています。
-
PinExpiry
はPINコード確認を終えるまでの有効期間を秒単位で指定します -
Lg
はユーザーに送信するSMSの言語設定に使われます。 -
WorkflowID
はユーザーに送るメッセージの組み合わせを変えます。デフォルトではSMS+SMS+TELと、3回PINコードが送信されます。 - Optionの全体は公式ドキュメントから確認することができます。 https://developer.vonage.com/api/verify?theme=dark#verifyRequest
-
- このとき、オプションをいくつか設定しています。
- スペースの都合上エラー処理を割愛して紹介していますが、 ある電話番号に対する認証が期限切れになる前に、連続して認証開始リクエストを送信してもVonageからエラーを返してくれます。また進行中の認証の
requestID
も返却してくれます。Webアプリ側で配慮することがいろいろと減らせて嬉しいですね。
なお、OptionでPinExpiry
を渡せるのですが、SDK上で未実装になっており設定が反映されません プルリク出したのでお願いしますね!
次にPINコードの確認を行う処理を紹介します。
func requestCheck(c echo.Context) (verified bool, _ error) {
auth := vonage.CreateAuthFromKeySecret(apiKey, apiSecret)
verifyClient := vonage.NewVerifyClient(auth)
// sessionに保持してあるrequestIDを読み出す
sess, err := session.Get(sessionName, c)
if err != nil {
return false, fmt.Errorf("fail to call session: %w", err)
}
r := sess.Values["requestID"]
reqID, ok := r.(string)
if !ok {
return false, errors.New("invalid requestID in this session")
}
if reqID == "" {
return false, errors.New("invalid requestID in this session")
}
// ユーザがフォームで入力したPINコードを読み出す
pin := c.FormValue("pin")
if !isValidPin(pin) {
return false, errors.New("invalid pin code")
}
_, respErr, err := verifyClient.Check(reqID, pin)
// some error handling...
// 何もエラーがなければ認証OKとして返す
return true, nil
}
- こちらもシンプルに、sessionに保存しておいたrequestIDと、ユーザーがフォームで入力したPINコードをそれぞれ取り出して、VonageのVerify APIに渡しています。
シンプルに電話番号認証が実装できましたね!
おわりに
去年よりも一歩踏み込んでVonageさんのサービスに触れることができたように思います。簡単に電話番号認証ができたので、これで私も2FA使いになれますかね…。こういった試みも何かきっかけがないと始めづらいので、去年に続き今年もアドベントカレンダーを企画してくださったVonageさんに感謝です