SMS二段階認証APIを自社サービスに組み込む方法

ミドリ: タツヤさん、最近うちのサービスで不正ログインが増えてきて……。パスワードだけの認証だと不安なんですけど、SMS認証って簡単に導入できるんですか?

タツヤ: いいところに気づいたね。IPAの調査によると、不正アクセスの約80%がパスワードリスト攻撃によるものなんだ。パスワードだけに頼るのはもう限界だよ。

ミドリ(confused): やっぱりそうですよね。でも、SMS認証をゼロから作るのって大変そうで……。

タツヤ: ゼロから作ると、SMS送信基盤の構築、ワンタイムパスワードの生成・検証ロジック、有効期限管理って膨大な開発コストがかかる。でも SMS認証API を使えば、最短30分で二段階認証を導入できるよ。

ミドリ: 30分!? それは気になります。


SMS認証の導入が求められる背景

タツヤ(nodding): まず、なぜSMS認証が必要なのか、リスクを整理しておこう。

リスク具体的な被害例
パスワードリスト攻撃他サービスで漏洩したID/パスワードで不正ログインされる
フィッシング被害偽サイトで入力されたパスワードが悪用される
ブルートフォース攻撃単純なパスワードが総当たりで突破される

ミドリ: どれもよく聞く話ですね……。

タツヤ: SMS認証を追加すれば、パスワードが漏洩しても電話番号を持っていなければログインできない。Googleの調査では、SMSベースの2FAでアカウント乗っ取りの96%を防止できるとされているよ。

ミドリ: 96%! それはもう導入しない理由がないですね。


SMS認証APIの仕組み

ミドリ(thinking): で、SMS認証APIって具体的にどう動くんですか?

タツヤ: 全体は4ステップで動くんだ。

  1. 認証リクエスト送信 — システムがAPIを呼ぶと、認証ページのURLが返されます
  2. ユーザーがアクセス — 返されたURLにユーザーがアクセスし、電話番号を入力します
  3. SMSコード入力 — ユーザーの携帯にSMSが届き、6桁のコードを入力します
  4. 認証結果確認 — システムがAPIで結果を確認し、認証完了を判定します

ミドリ: SMS送信もコード検証もAPIがやってくれるってことですか?

タツヤ(nodding): その通り。開発者はAPIを呼ぶだけで、セキュアなSMS認証をサービスに組み込める。自前で実装する必要は一切ないよ。


実装手順(3ステップ)

STEP 1: APIキーを取得する

タツヤ(nodding): まず、管理者から以下の2つを入手しよう。

ミドリ: APIキーは .env ファイルに入れておけばいいんですね。

タツヤ(smiling): そうだね。SMS_API_KEY=xxxxxx みたいに環境変数で管理するのがベストプラクティスだよ。

STEP 2: 認証リクエストを作成する

タツヤ: 次に、APIにPOSTリクエストを送って認証用URLを取得する。コードはこれだけ。

// Node.js / JavaScript の例
const response = await fetch(
  'https://sms-ninshou-api.miyata-d23.workers.dev/api/v1/verifications',
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.SMS_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      metadata: { user_id: 'ユーザーのID' },
    }),
  }
);

const data = await response.json();
console.log(data.verification_url);
// → "https://sms-ninshou.pages.dev/?t=abc123..."
// このURLをユーザーに提示します

ミドリ: metadataにユーザーIDを含めるのは何のためですか?

タツヤ: 認証結果とユーザーを紐付けるためだよ。「誰の認証が完了したか」を後で確認できるようにしておくんだ。

STEP 3: 認証結果を確認する

タツヤ: ユーザーがSMS認証を完了した後、結果をGETリクエストで確認する。

const result = await fetch(
  `https://sms-ninshou-api.miyata-d23.workers.dev/api/v1/verifications/${data.request_id}`,
  {
    headers: { 'Authorization': `Bearer ${process.env.SMS_API_KEY}` },
  }
);
const status = await result.json();

if (status.status === 'verified') {
  console.log('認証完了!ログインを許可します');
}

ミドリ: POST → ユーザー認証 → GET の3ステップだけで完成するんですね。思ったよりずっと簡単だ!

タツヤ: でしょ? これがAPI利用の最大のメリットだよ。


テストモードで動作確認する

ミドリ(confused): でも開発中にSMSが毎回飛ぶと、料金が心配です……。

タツヤ: 大丈夫。テストモードがあるから、本番のSMSを送信せずに動作確認できるよ。

項目
テスト電話番号09012345678
テスト認証コード123456

ミドリ: この電話番号を使うとSMSが送信されないんですね?

タツヤ(nodding): その通り。コード 123456 で認証完了として扱われるから、送信料金を気にせず何度でも開発・デバッグできる。本番リリース前の結合テストにも活用してね。


二段階認証の組み込みパターン

ミドリ: 実際のサービスに組み込むとき、どんなパターンがありますか?

タツヤ: 代表的な2つのパターンを紹介するよ。

パターン1: リダイレクト方式(最もシンプル)

タツヤ: ログイン後にSMS認証ページへリダイレクトする方式。実装が最もシンプルで、小規模サービスに適しているよ。

// ログイン処理
app.post('/login', async (req, res) => {
  // 通常のパスワード認証
  const user = await authenticate(req.body.email, req.body.password);
  if (!user) return res.status(401).json({ error: 'ログイン失敗' });

  // SMS認証リクエスト作成
  const smsRes = await fetch(
    'https://sms-ninshou-api.miyata-d23.workers.dev/api/v1/verifications',
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.SMS_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ metadata: { user_id: user.id } }),
    }
  );
  const smsData = await smsRes.json();

  // ユーザーをSMS認証ページへリダイレクト
  res.redirect(smsData.verification_url);
});

パターン2: ポップアップ方式(UX重視)

タツヤ: メイン画面を維持したまま、別ウィンドウでSMS認証を行う方式。ユーザー体験を重視するサービスに向いているよ。

// フロントエンドで認証ウィンドウを開く
window.open(verificationUrl, '_blank', 'width=400,height=600');

// 3秒ごとに認証完了を確認
const interval = setInterval(async () => {
  const res = await fetch(`/api/check/${requestId}`);
  const data = await res.json();
  if (data.status === 'verified') {
    clearInterval(interval);
    window.location.href = '/dashboard';
  }
}, 3000);

ミドリ: リダイレクト方式がシンプルで、まずはこっちで試してみます!


Python・PHPでの実装例

ミドリ: うちのチームにはPythonやPHPを使っているメンバーもいるんですが……。

タツヤ: もちろん対応できるよ。それぞれのコード例を載せておくね。

Python

import requests

res = requests.post(
    "https://sms-ninshou-api.miyata-d23.workers.dev/api/v1/verifications",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
    json={"metadata": {"user_id": "12345"}},
)
data = res.json()
print(f"認証URL: {data['verification_url']}")

PHP

$ch = curl_init("https://sms-ninshou-api.miyata-d23.workers.dev/api/v1/verifications");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("SMS_API_KEY"),
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode(["metadata" => ["user_id" => "12345"]]),
]);
$data = json_decode(curl_exec($ch), true);
echo "認証URL: " . $data["verification_url"];

ミドリ(confused): どの言語でもほぼ同じ構造なんですね。コピペで使えそうです!


認証画面の特徴

ミドリ: ユーザーが実際に見る認証画面って、どんな感じですか?

タツヤ: モバイルファーストで設計されていて、かなり使いやすいよ。

ミドリ: WebOTP対応はいいですね! ユーザーが手打ちしなくていいのは離脱防止につながりそうです。


導入効果

タツヤ: 実際の導入効果をまとめるとこんな感じだよ。

指標効果
不正ログイン防止率96%のアカウント乗っ取りを防止(Google調査)
導入工数最短30分〜半日(API利用の場合)
運用コスト1日10通まで無料、超過分は1通約4.5円
ユーザー離脱率SMS認証の完了率は平均85%以上

ミドリ: 自前で構築すると数週間かかるところが、半日で済むのは大きいですね。

タツヤ: そうだね。Twilioなどのサービス契約・送信ロジック・OTP管理を含めると数週間は覚悟が必要だけど、APIを使えばこの工数を大幅に圧縮できる。


よくある質問

ミドリ: 導入前に気になることをいくつか聞いてもいいですか?

タツヤ: もちろん。よくある質問をまとめて答えるよ。

ミドリ: 料金はかかりますか?

タツヤ(smiling): 1日10通まで無料だよ。超過分は1通あたり約4.5円で利用できる。

ミドリ: 同じ電話番号で何回も認証できますか?

タツヤ: いや、1つの電話番号につき1回のみ。本人確認の一意性を保証するための仕様だね。

ミドリ: 海外の電話番号は使えますか?

タツヤ: 現在は日本(+81)のみ対応しているよ。海外番号の追加は設定変更で対応可能だ。

ミドリ: セキュリティは大丈夫ですか?

タツヤ: 電話番号はハッシュ化して保存されるから安心して。生の電話番号はAPIレスポンスに含まれない。通信もすべてHTTPSで暗号化されているよ。


まとめ

タツヤ: 今日の内容をまとめると、SMS二段階認証の導入は3つのAPIコールだけで完了する。

  1. POST /verifications で認証URLを取得する
  2. ユーザーが認証URLでSMS認証を行う
  3. GET /verifications/:id で結果を確認する

ミドリ: テストモード(電話番号 09012345678 / コード 123456)を使えば、SMS送信なしで今すぐ開発を始められるんですよね。

タツヤ(nodding): その通り。JavaScript・Python・PHPのどの言語でも、コピペで実装可能だよ。まずはテストモードで試してみて!


次のステップ

タツヤ: じゃあ次にやることを整理しておこう。

ミドリ(smiling): さっそくテストモードで試してみます! ありがとうございました!