Vonage API + React(SPA) + Expressで電話番号認証とSMS送信アプリを作る

# 概要
本記事では、VonageAPIの**Verify API**と**SMS API**を用いて、電話番号認証をして認証済み電話番号に対してSMSでメッセージを送信するWebアプリケーションを作成するハンズオン記事になっています。

MFA（多要素認証）は今やアプリケーションのセキュリティを向上させる上で重要な役割を担っています。各個人でMFAのための認証基盤を作成するのはコスト的にも技術的にも大変だと思います。VoyageAPIを使用することで認証方法の1つである電話番号認証が簡単に実装できることがわかりました。
本記事では、まずVonageが提供している Verify APIを用いて SPAによる電話番号認証を作成していきます。

https://developer.vonage.com/verify/overview

（おまけ） セクションで、この電話番号認証をより堅牢にするために **Number Insight API** を用いて電話番号の有効性チェックの導入も行います。

https://developer.vonage.com/number-insight/overview

そして、認証後の電話番号とSMS APIを用いて認証済みの電話番号端末に対して任意のメッセージでSMSを送信機能まで作成します。

https://developer.vonage.com/messaging/sms/overview


以下のような画面構成を持つSPAのWebアプリケーションを作成していきます。

| ホーム | 認証コード発行 | 認証 |
|:--:|:--:|:--:|
| ![スクリーンショット 2022-12-20 0.21.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154845/9faa8d80-0ea3-721e-bae9-51258fa897c6.png) | ![スクリーンショット 2022-12-20 0.44.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154845/3265a16f-0a93-c88c-419b-dad07ed5e0c8.png) | ![スクリーンショット 2022-12-20 0.44.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154845/eec5f337-758d-f34c-2f8a-53b5e3438954.png) |

作成するアプリケーションの全体像です。 緑の四角がSPAのページを示していて、赤の四角がバックエンドサーバーの機能を示しています。直線がSPA内でのページの遷移、破線がそれぞれのAPIへのリクエストの流れを示しています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154845/49edd062-5842-2781-d110-68a7676133b4.png" width=500 />


作成したコードはこちらに置いてありますので参考にしてください。

https://github.com/ta1m1kam/vonage-verify-app

それではアプリケーションを作成していきましょう。

# アプリケーションを作成する
## Vonageアカウントの作成する
Vonageアカウントの作成をしておきます。

https://dashboard.nexmo.com/?utm_source=qiita-advent-calendar-2022

## プロジェクトを作成する
### Reactプロジェクトの作成（フロントエンド）

```sh
$ npx create-react-app vonage-app
```

### 必要なパッケージインストール
アプリケーションを完成させるために必要なパッケージをインストールしていきます。

```sh
$ npm install express express-session body-parser dotenv @vonage/server-sdk
```

| パッケージ名 | 内容 |
|:--|:--|
| express | Webアプリケーションフレームワーク |
| express-session | ユーザーのログイン状態を管理する |
| body-parser | POSTリクエストをパースする |
| dotenv | VonageAPI KEY, SECRET, BRAND NAME（アプリケーション名）を環境変数管理 |
| @vonage/server-sdk | VonageAPIを扱うためのSDK |


### server.jsのフレームを作成（バックエンド）

```js
require('dotenv').config();

const path = require('path')
const express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');
const app = express();
const { Vonage } = require('@vonage/server-sdk');

const VONAGE_API_KEY = process.env.VONAGE_API_KEY;
const VONAGE_API_SECRET = process.env.VONAGE_API_SECRET;
const VONAGE_BRAND_NAME = process.env.VONAGE_BRAND_NAME;

let verifyRequestId = null;
let verifyRequestNumber = null;

// Location of the application's CSS files
app.use(express.static('build'));

// The session object we will use to manage the user's login state
app.use(session({
    secret: 'loadsofrandomstuff',
    resave: false,
    saveUninitialized: true
}));

app.use(express.json());
app.use(bodyParser.urlencoded({ extended: true }));

// Define your routes here

// Run the web server
const server = app.listen(3000, () => {
    console.log(`Server running on port ${server.address().port}`);
});

```

dotenvで`.env`ファイルによって、環境変数が使用できるようになっているので`.env`ファイルにVONAGE_API_KEY、VONAGE_API_SECRETをVonageAPIDeveloperページから取得して環境変数に追加します。VONAGE_BRAND_NAMEは任意で適当な値を設定できます。

![スクリーンショット 2022-12-17 22.59.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154845/b56b0200-470b-298b-4795-5dbad2e636f3.png)

```.env
VONAGE_API_KEY=YOUR_API_KEY
VONAGE_API_SECRET=YOUR_API_SECRET
VONAGE_BRAND_NAME=<アプリケーション名>
```

package.jsonにexpressサーバー起動ようのスクリプトを追加していきます。
```package.json
// ...
 "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "server": "node server.js" // ← 追加
  },
// ...
```
### アプリを確認する
ここまで来たところで、現在のアプリケーションを確認してみましょう

以下のコマンドを実行して、Reactアプリケーションをビルドし、expressサーバーを立ち上げてブラウザで確認します。
```
$ npm run build // ← Reactアプリケーションのビルド
$ npm run server // ← Expressサーバー起動
> vonage-app@0.1.0 server
> node server.js

Server running on port 3000
```

![スクリーンショット 2022-12-17 23.17.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154845/2e866932-ad68-0446-c6e3-969bda803514.png)

## 認証コードを取得する
### 画面UIを作成する（フロントエンド）
必要なページを作成していくためにReactRouter、UIを作成するためにMUIをインストールします。

```sh
$ npm install --save react-router-dom
$ npm install --save @mui/material @emotion/react @emotion/styled
```

### 各ページを作成する

以下それぞれのファイルのコードを記載しています。


```App.jsx
import './App.css';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Home from './pages/Home';
import Authenticate from './pages/Authenticate';
import { Container } from '@mui/system';

function App() {
  return (
    <div className="App">
      <Container maxWidth="sm">
        <BrowserRouter>
          <Routes>
            <Route path={"/"} element={<Home />} />
            <Route path={"/authenticate"} element={<Authenticate />} />
          </Routes>
        </BrowserRouter>
      </Container>
    </div>
  );
}
export default App;
```


```pages/Home.jsx
import { Button } from "@mui/material";
import { Link } from "react-router-dom";

const Home = () => {
  return (
    <>
      <h1>Account Management!</h1>
        <p>
          You have verified your identity using the phone number TODO
          and are now permitted to make changes to your account.
        </p>
        <Link to={"/authenticate"}>
          <Button variant="contained">
            Verify me
          </Button>
        </Link>
    </>
  );
};

export default Home;
```


```pages/Authenticate.jsx
import { Button, FormControl, TextField } from "@mui/material";
import { useState } from "react";

const Authenticate = () => {
  const [mobileNumber, setMobileNumber] = useState("");

  const handleChangeMobileNumber = (e) => {
    setMobileNumber(e.target.value);
  }

  const requestVerificationCode = async () => {
    const formatMobileNumber = "81" + mobileNumber.slice(1);
    const response = await fetch("/verify", {
      method: "POST",
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ number: formatMobileNumber })
    })

    console.log(response.json());
  }

  return (
    <>
      <h1>Authenticate</h1>
      <FormControl fullWidth sx={{ m: 1 }}>
        <TextField
          id="outlined-name"
          label="Mobile number"
          inputProps={{ inputMode: "numeric", pattern: '[0-9]*' }}
          onChange={handleChangeMobileNumber}
        />
      </FormControl>
      <FormControl fullWidth sx={{ m: 1 }}>
        <Button 
          variant="contained"
          onClick={requestVerificationCode}
        >
            Get Verification Code
        </Button>
      </FormControl>
      <h2>{mobileNumber}</h2>
    </>
  );
};

export default Authenticate;
```


ここまで完成すると以下のような画面が作成できました。

| ホーム画面 | 認証コードリクエスト画面 |
|:--:|:--:|
| ![スクリーンショット 2022-12-18 0.52.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154845/74c41fca-7dff-643e-dce0-c92192e80cdc.png) | ![スクリーンショット 2022-12-18 0.53.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154845/089d40cc-5380-cabd-bb2c-a2e43405dcb1.png) |


### 認証コード発行をVonageにリクエストするエンドポイントを作成（バックエンド）

`/verify`というPOSTリクエストのパスを作成し、リクエストで取得した携帯電話番号をvonageインスタンスにセットして認証コードをリクエストします。詰まってしまう箇所として、リクエストする時の電話番号は**国際電話番号の形式にする必要がある**ことに注意です。
`09012345678` であれば、先頭のゼロを取り除き、日本を表す「81」を付けた `819012345678`となります。
```server.js
// Define your routes here
app.post('/verify', (req, res) => {
  verifyRequestNumber = req.body.number;
  vonage.verify.start({
    number: verifyRequestNumber,
    brand: VONAGE_BRAND_NAME
  }).then((resp) => {
    console.log(resp)
    verifyRequestId = resp.request_id;
    console.log(`request_id: ${verifyRequestId}`);
    res.status(201).send("success");
  }).catch(err => console.error(err));
})
```

以下のコードは画面側で `/verify`へリクエストする際のイベント関数ですが、リクエストの前に電話番号を国際電話番号形式に変換するロジックを入れています。（※ 画面UIを作成する(フロントエンド)章の `pages/Authenticate.jsx`に含まれるコードの一部です。）
```Authenticate.jsx
// ...
  const requestVerificationCode = async () => {
    const formatMobileNumber = "81" + mobileNumber.slice(1);
    const response = await fetch("/verify", {
      method: "POST",
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ number: formatMobileNumber })
    })

    console.log(response.json());
  }
// ...
```

### 認証情報が携帯電話に送信されるかを確認する

認証コードリクエスト画面で、実際に自身の携帯電話番号を入力して、`GET VERIFICATION CODE`ボタンを押下してみましょう。

するとExpressサーバー側のログにはrequest_idが表示され、正常にリクエストできていることが確認できます。

```sh
{ request_id: 'cec9cb43b5434d12862b3b867cfa4da7', status: '0' }
request_id: cec9cb43b5434d12862b3b867cfa4da7
```

実際に携帯電話にもSMS通知で認証番号が届いていることがわかります。

| 認証コード |
|:--:|
| ![IMG_3565.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154845/788e03aa-c15a-3683-7025-73c77925a9d3.png) |


## 認証コードを認証する
### 画面UIを作成する（フロントエンド）
新規で認証コードを入力するためのページを作成します。
ページに必要なUIとしては、携帯電話に送信された認証番号を入力するテキストフィールドとリクエストを送信するためのボタンなのでほとんど`Authenticate`ページと変わらないです。

以下が`VerifyCode`ページの全体のソースコードです。

```pages/VerifyCode.jsx
import { Button, FormControl, TextField } from "@mui/material";
import { useState } from "react";

const VerifyCode = () => {
  const [code, setCode] = useState("");

  const handleChangeCode = (e) => {
    setCode(e.target.value);
  }

  const requestVerificationCode = async () => {
    const response = await fetch("/check-code", {
      method: "POST",
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ code })
    })

    console.log(response.json());
  }

  return (
    <>
      <h1>VerifyCode</h1>
      <FormControl fullWidth sx={{ m: 1 }}>
        <TextField
          id="outlined-name"
          label="Code"
          inputProps={{ inputMode: "numeric", pattern: '[0-9]*' }}
          onChange={handleChangeCode}
        />
      </FormControl>
      <FormControl fullWidth sx={{ m: 1 }}>
        <Button 
          variant="contained"
          onClick={requestVerificationCode}
        >
          Verify me!
        </Button>
      </FormControl>
    </>
  );
};

export default VerifyCode;
```


| 認証ページ |
|:--:|
| ![スクリーンショット 2022-12-18 1.52.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154845/8b41764f-3e5e-0311-9b1c-452f564dc605.png) |

新規ページに遷移できるようにするために、`App.jsx`を修正します。

```pages/App.jsx
import './App.css';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Home from './pages/Home';
import Authenticate from './pages/Authenticate';
import { Container } from '@mui/system';
import VerifyCode from './pages/VerifyCode';

function App() {
  return (
    <div className="App">
      <Container maxWidth="sm">
        <BrowserRouter>
          <Routes>
            <Route path={"/"} element={<Home />} />
            <Route path={"/authenticate"} element={<Authenticate />} />
            <Route path={"/verifycode"} element={<VerifyCode />} />
          </Routes>
        </BrowserRouter>
      </Container>
    </div>
  );
}

export default App;
```

また、`Authenticate`ページで認証コード発行のリクエスト後にページ遷移させるために、ReactRouterの`useNavigate()`を利用して`VerifyCode`ページに遷移するように修正します。


```pages/Authenticate.jsx
import { Button, FormControl, TextField } from "@mui/material";
import { useState } from "react";
import { useNavigate } from "react-router-dom";

const Authenticate = () => {
  const navigate = useNavigate();
  const [mobileNumber, setMobileNumber] = useState("");

  const handleChangeMobileNumber = (e) => {
    setMobileNumber(e.target.value);
  }

  const requestVerificationCode = async () => {
    const formatMobileNumber = "81" + mobileNumber.slice(1);
    await fetch("/verify", {
      method: "POST",
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ number: formatMobileNumber })
    });

    navigate("/verifycode")
  }

  return (
    <>
      <h1>Authenticate</h1>
      <FormControl fullWidth sx={{ m: 1 }}>
        <TextField
          id="outlined-name"
          label="Mobile number"
          inputProps={{ inputMode: "numeric", pattern: '[0-9]*' }}
          onChange={handleChangeMobileNumber}
        />
      </FormControl>
      <FormControl fullWidth sx={{ m: 1 }}>
        <Button 
          variant="contained"
          onClick={requestVerificationCode}
        >
            Get Verification Code
        </Button>
      </FormControl>
      <h2>{mobileNumber}</h2>
    </>
  );
};

export default Authenticate;
```



### 認証コード認証をVonageにリクエストするエンドポイント作成（バックエンド）

`/verify`の下に新しく`/check-code`のエンドポインを立て、認証をリクエストする処理を書いて行きます。発行のコードと似ていて、vonageインスタンスに発行の際に取得した`verifyRequestId`と画面側からのリクエストで取得する携帯電話に送信された認証番号である`code`を引数にいれて認証をリクエストします。

```sever.js
app.post('/check-code', (req, res) => {
  vonage.verify.check({
    request_id: verifyRequestId,
    code: req.body.code
  }).then((resp) => {
    console.log(resp)
    if (resp.status === 0) {
      req.session.user = {
        number: verifyRequestNumber
      };
    }
    res.status(201).send("success");
  }).catch(err => console.error(err));
});
```

### 認証の確認
Expressサーバーで認証コードが正常に受理されると以下のようなレスポンスが返却され console.logによってターミナルに表示されます。
`status: '0'`が成功のステータスレスポンスです。
```
{ code: '3904' }
{
  request_id: 'c753cc99fb714c80ad3656ac364e1579',
  status: '0',
  event_id: '330000005912A562',
  price: '0.05000000',
  currency: 'EUR',
  estimated_price_messages_sent: '0.07000000'
}
```

## 認証状態を表示する
### ホーム画面を修正する（フロントエンド）

`Home.jsx`に認証状態を取得する処理を追加していきます。画面レンダリングされたと同時に取得したいので`useEffect`でAPIコールを行います。
また、認証状態である場合には認証済みの電話番号を表示させたいので、表示させるUIデザインを少し可愛くしていきます。そのために`@mui/icons-material`をインストールします。

```
$ npm install --save @mui/icons-material
```

以下がHome.jsxの最新の全体コードです。

```pages/Home.jsx
import { Button, Chip } from "@mui/material";
import { useEffect, useState } from "react";
import { Link, useLocation } from "react-router-dom";
import MdPhone from '@mui/icons-material/Phone';
import { Box } from "@mui/system";

const Home = () => {
  const [verifyNumber, setVerifyNumber] = useState(null);
  const [fromVerifyCode, setFromVerifyCode] = useState(null);
  const search = useLocation().search;
  const query = new URLSearchParams(search);
  if (query.verify) {
    setFromVerifyCode(query.verify);
  }

  useEffect(() => {
    const getVerify = async () => {
      const response = await fetch("/verify-number", {
        method: "GET",
        headers: {
          'Content-Type': 'application/json'
        },
      })

      const json = await response.json();
      setVerifyNumber(json.verifyNumber);
    }
    getVerify();
  }, [fromVerifyCode]);

  const phoneLabelMessage = verifyNumber ? `+${verifyNumber}` : "You are not verified";

  return (
    <>
      <h1>Account Management!</h1>
      <Box sx={{ m: 1 }}>
        <Chip icon={<MdPhone />} label={phoneLabelMessage}/>
      </Box>
      <Link to={"/authenticate"}>
        <Button variant="contained">
          Verify me
        </Button>
      </Link>
    </>
  );
};

export default Home;
```


### 認証状態を取得するエンドポイントを作成（バックエンド）

`/check-code`のエンドポイントの下に新しく、認証状態を取得するGETリクエスト用のエンドポイント `/verify-number`を作成します。ここでは、認証済みであれば `verifyNumber`に認証された電話番号が入り、未認証であれば、`null`が入るようになっています。
```server.js
app.get('/verify-number', (req, res) => {
  console.log(req.session.user)
  res.status(200).send({
    verifyNumber: req?.session?.user?.number,
  })
});
```

### 認証状態の確認

| 未認証 | 認証済み |
|:--:|:--:|
| ![スクリーンショット 2022-12-18 2.48.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154845/de6b687b-0245-bc6f-6f78-166db056a148.png) | ![スクリーンショット 2022-12-18 2.48.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154845/4564c14d-5342-6f7a-5e19-f06afaa5b1b0.png) |

これでSPAに電話番号認証を導入することができます。VonageAPIを使用するとめちゃくちゃ簡単に作成できますね！


## 認証した電話番号にSMSを送信する機能

最後に、認証した電話番号を用いてSMSを送信する機能を作成していきます。

### ホーム画面を修正する（フロントエンド）

`Home.jsx`に認証済みの電話番号があれば、テキストボックスとSMSの送信ボタンを表示させるようにJSX部分に追加していきます。
また、SMSで送信したい内容用のテキストボックスのchangeイベントに対応する `handleChangeText`関数とSMSを送信するための`handleSubmitSms`関数を定義して、それぞれのイベントに設定します。SMSの送信のためのエンドポイントのAPIは後述のバックエンド側の処理で書いていきますが、`send-sms`にPOSTでリクエストするようにします。

```pages/Home.jsx
import { Button, Chip, FormControl, TextField } from "@mui/material";
import { useEffect, useState } from "react";
import { Link, useLocation } from "react-router-dom";
import MdPhone from '@mui/icons-material/Phone';
import { Box } from "@mui/system";

const Home = () => {
  const [verifyNumber, setVerifyNumber] = useState(null);
  const [fromVerifyCode, setFromVerifyCode] = useState(null);
  const [text, setText] = useState("");
  const search = useLocation().search;
  const query = new URLSearchParams(search);
  if (query.verify) {
    setFromVerifyCode(query.verify);
  }

  useEffect(() => {
    const getVerify = async () => {
      const response = await fetch("/verify-number", {
        method: "GET",
        headers: {
          'Content-Type': 'application/json'
        },
      })

      const json = await response.json();
      setVerifyNumber(json.verifyNumber);
    }
    getVerify();
  }, [fromVerifyCode]);

  const phoneLabelMessage = verifyNumber ? `+${verifyNumber}` : "You are not verified";

  const handleChangeText = (e) => {
    setText(e.target.value);
  }

  const handleSubmitSms = async () => {
    const data = { number: verifyNumber, text };
    await fetch("/send-sms", {
      method: "POST",
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    });
  }

  return (
    <>
      <h1>Account Management!</h1>
      <Box sx={{ m: 1 }}>
        <Chip icon={<MdPhone />} label={phoneLabelMessage}/>
      </Box>
      <Link to={"/authenticate"}>
        <Button variant="contained">
          Verify me
        </Button>
      </Link>

      { verifyNumber && 
        (<Box sx={{ m: 2 }}>
          <FormControl fullWidth sx={{ m: 1 }}>
            <TextField
              id="outlined-name"
              label="sms text"
              onChange={handleChangeText}
            />
          </FormControl>
          <FormControl fullWidth sx={{ m: 1 }}>
            <Button 
              variant="contained"
              onClick={handleSubmitSms}
            >
              Send SMS
            </Button>
          </FormControl>
        </Box>)
      }
    </>
  );
};

export default Home;
```

### SMSを送信するためのエンドポイントを作成（バックエンド）
`/verify-number` のエンドポイントの下に`/send-sms`の新規エンドポイントを作成します。ここでは、VonageSDKのSMS APIを使用していきますが、to、from、textさえ指定すれば簡単に任意のメッセージをSMSで送信することができます。
任意のメッセージは画面からリクエストされて送られてくる`req.body.text`を設定します。

```server.js
app.post('/send-sms', async (req, res) => {
  const to = req.body.number;
  const text = req.body.text;
  await vonage.sms.send({ to, from: VONAGE_BRAND_NAME, text })
    .then(resp => {
      console.log('Message sent successfully');
      console.log(resp);
    })
    .catch(err => console.error(err));
  res.status(201).send("success");
});
```

### SMS送信の確認

以下のコマンドで再度フロントエンドをビルドして、サーバーを立ち上げます。
```sh
$ npm run build
$ npm run server
```

立ち上げたWebアプリケーションで電話番号認証をすると、以下のように新規で追加したテキストボックスと`SEND SMS`ボタンが表示されます。ここに任意のメッセージを入れ、送信！ 
そうすると左のSMSメッセージ画像のようにアプリブランド名とメッセージがSMSで送信されました！

| 電話番号認証済みHOME画面 | SMSメッセージ |
|:--:|:--:|
| ![スクリーンショット 2022-12-19 23.13.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154845/c068525f-0c16-a575-3b70-1d106c9b55c4.png) | ![IMG_3566.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154845/ce988823-b587-f622-bd90-7dcfce1aec9b.png) |

バックエンドのログも確認しておきましょう。
console.logに仕込んでおいた `Message sent successfully` が表示されて、VonageAPIからのレスポンスが表示されていますね。

```bash
Message sent successfully
{
  messageCount: 1,
  'message-count': '1',
  messages: [
    {
      to: 'XXXXXXXXXX', ← 電話番号なのでマスク
      'message-id': 'e9cfde87-6e96-476c-a37b-9c0cfbe90eb1',
      status: '0',
      'remaining-balance': '10.59800000',
      'message-price': '0.07000000',
      network: '44007',
      messageId: 'e9cfde87-6e96-476c-a37b-9c0cfbe90eb1',
      remainingBalance: '10.59800000',
      messagePrice: '0.07000000'
    }
  ]
}
```
SMS送信も驚くほど簡単に実装できました。


## （おまけ） 電話番号認証を少し堅牢にする
VonageAPIにはNumber Insight APIという電話番号の有効性や到達性をチェックするためのAPIがあります。このAPIを用いて、電話番号認証のロジックを少し改良します。

https://developer.vonage.com/number-insight/overview

Expressサーバーの `/verify` エンドポイントで電話番号に対して認証コードを送信する前に、その電話番号が有効化どうかをNumber Insight APIによってチェックします。
statusが`0`であれば、正常な電話番号として処理されているので、それ以外であればhttp statusコード400を返すようにします。

```server.js
app.post('/verify', async (req, res) => {
  verifyRequestNumber = req.body.number;

+  const resp = await vonage.numberInsights.basicLookup(req.body.number);
+  console.log(resp);
+  if (resp.status !== 0) {
+    return res.status(400).send("invalid number");
+  }

  vonage.verify.start({
    number: verifyRequestNumber,
    brand: VONAGE_BRAND_NAME
  }).then((resp) => {
    console.log(resp)
    verifyRequestId = resp.request_id;
    console.log(`request_id: ${verifyRequestId}`);
    res.status(201).send("success");
  }).catch(err => console.error(err));
});
```

これで有効性チェックも導入できました。

ちなみに、Number InsightAPIのレスポンスは以下のような中身でした。国や国際電話番号プレフィックスもわかるので認証APIを国際化もできそうですね。

```sh
{
  status: 0,
  status_message: 'Success',
  request_id: 'e1dfd728-7ce0-44ba-ad40-e3f177892492',
  international_format_number: 'XXXXXXXX', ← マスク
  national_format_number: 'XXX-XXXX-XXXX', ← マスク
  country_code: 'JP',
  country_code_iso3: 'JPN',
  country_name: 'Japan',
  country_prefix: '81'
}
```


## トラブルシューティング
もしAPIエラー等で悩まされる場合はAPIエラーがまとめられたドキュメントがあるので参照してみるとヒントが得られるかもしれないです。

筆者はデバッグしている際によく `status: 10` が返却されて、セッション切れを待つことがありました笑。（他にもverifyすると解消されます。）

| Code | Text | Definition |
|:--:|:--:|:--:|
| 10 | Concurrent verifications to the same number are not allowed | Two simultaneous requests to the same number are not allowed. The customer can perform a verification request to the same number only after the previous request to that number has been marked as SUCCESSFUL, FAILED, or EXPIRED. |

https://api.support.vonage.com/hc/en-us/articles/360025561931-What-do-the-Verify-API-response-codes-mean-

https://developer.vonage.com/api-errors


### API Reference
また今回利用したAPIはOpenAPIのような形式でRESTful APIのインターフェースが定義されている仕様書まであるので、ぜひ参照して見てください。

- https://developer.vonage.com/api/verify
- https://developer.vonage.com/api/sms
- https://developer.vonage.com/api/number-insight


# VonageAPIでの開発に欲しかった機能
開発時点では、ダミーの電話番号や認証コードが1234などで通るようなsandbox機能があるといいなぁと思いました。しかし、Dashboardも見やすく各APIの実装例も豊富なので困らずに実装できました。


# 最後に
今回初めてVonageAPIを触ってみましたが、ドキュメントが充実しておりとても使いやすいサービスでした。SDKの更新に対応できていないドキュメントなどもありましたが、Dashboardから見れる説明はほぼ新しい情報だったので特に困らず実装をすることができました。
また、カレンダーに参加するにあたって無料のテストクレジットや追加のクレジットクーポンも提供してくださっていて大変助かりました。他のVoyageAPIも興味深いものが多いのでこの機会を活用して色々試して見ようと思いました。ありがとうございました。


# 参考

https://github.com/Vonage/vonage-node-sdk

https://developer.vonage.com/verify/overview

https://developer.vonage.com/messaging/sms/overview

https://developer.vonage.com/number-insight/overview

https://developer.vonage.com/verify/tutorials/step-up-authentication/introduction/node