Amplifyの電話番号認証とSNS/SMS上限申請 - 2025年最新版で見る設定と運用の全体像
SMS認証を取り巻く環境の変化と現状
AWS Amplifyで電話番号認証を実装する際、多くの開発者が最初に驚くのが「SMS送信の月額上限が1ドル」という制約です。この制約自体は以前から存在していましたが、2024年7月にAmazon PinpointのメッセージングサービスがAWS End User Messagingに改称されたことで、SMS送信の仕組みと管理方法に大きな変化がありました。
現在のアーキテクチャでは、Cognitoが送信するSMSは「SNSのPublish API」を経由し、実際の配信と課金は「End User Messaging SMS」が担当するという構造になっています。この変更により、SMS送信に関する設定や監視の方法も更新されており、既存のドキュメントや記事の情報が古くなっているケースが散見されます。
さらに重要な変更として、SNSにSMSサンドボックスという概念が導入されました。これは開発環境と本番環境を明確に分離するための仕組みで、本番利用を開始する前に必ず「サンドボックスの終了申請」を行う必要があります。この手続きを知らずに本番リリースを迎えると、認証SMSが送信できないという致命的な問題に直面することになります。
認証フロー選択における重要な変更点
OAuth認証フローの選択においても、セキュリティの観点から推奨事項が大きく変わりました。従来推奨されていたImplicit Grantフローは現在では非推奨となり、Authorization Code + PKCEフローが標準推奨となっています。
この変更は単なる推奨事項の変更ではなく、セキュリティ強化の観点から必須の対応と考えるべきです。特にSPAやモバイルアプリケーションを開発している場合、アクセストークンの扱い方が根本的に変わるため、既存実装の見直しが必要になる可能性があります。
Amplifyでの電話番号認証実装手順
Amplify Gen 2による実装アプローチ
Amplify Gen 2(コードファーストアプローチ)では、設定ファイルベースで認証機能を定義できるようになりました。Gen 1のような対話形式のCLIセットアップも引き続き利用可能ですが、インフラストラクチャをコードとして管理する観点から、Gen 2のアプローチが推奨されています。
Gen 2での電話番号認証の基本設定は、amplify/backend/auth/resource.ts
に以下のような形で定義します。
import { defineAuth } from '@aws-amplify/backend';
export const auth = defineAuth({
loginWith: {
phone: true,
email: false
},
multifactor: {
mode: 'OPTIONAL',
sms: true
},
userAttributes: {
phone_number: {
required: true,
mutable: false
}
}
});
この設定により、電話番号をプライマリの認証方法として使用し、SMSベースのMFAをオプションとして有効化できます。Gen 1と比較して、設定の可視性が向上し、バージョン管理システムでの変更追跡が容易になったことは大きなメリットです。
UIコンポーネントの実装
フロントエンド実装では、Amplify UIライブラリの「Authenticator」コンポーネントを使用することで、電話番号認証のUIを簡単に実装できます。React環境での実装例を以下に示します。
import { Authenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
interface AppProps {
children: React.ReactNode;
}
export const App: React.FC<AppProps> = ({ children }) => {
return (
<Authenticator
loginMechanisms={['phone_number']}
signUpAttributes={['phone_number']}
formFields={{
signUp: {
phone_number: {
label: '電話番号',
placeholder: '+81 90-1234-5678',
isRequired: true,
order: 1
}
}
}}
>
{({ signOut, user }) => (
<main>
<h1>ようこそ {user?.attributes?.phone_number}</h1>
<button onClick={signOut}>サインアウト</button>
{children}
</main>
)}
</Authenticator>
);
};
このコンポーネントベースのアプローチにより、電話番号入力、SMS検証コード送信、コード入力という一連のフローを自動的に処理できます。カスタマイズも容易で、デザインシステムに合わせた見た目の調整も可能です。
Cognito側の必須設定項目
オリジネーション・アイデンティティの準備
SMS送信を行うためには、送信元を識別する「オリジネーション・アイデンティティ」の設定が必要です。これは国や地域によって要件が異なり、日本向けの送信では「Sender ID」または「専用番号」を使用することになります。
日本市場向けのSMS送信において、必須の登録制度は現時点で存在しませんが、専用ショートコードを取得する場合は初期費用1,750 USD、月額1,150 USD、プロビジョニング期間約12週間という投資が必要になります。ショートコードの利点は到達率の向上と送信速度の向上ですが、コスト対効果を慎重に検討する必要があります。
IAMロールの設定
CognitoがSMSを送信するためには、適切な権限を持つIAMロールの作成が必要です。以下のポリシーを持つロールを作成します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sns:Publish"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"sns:MessageType": "Transactional"
}
}
}
]
}
このロールの信頼関係には、Cognitoサービスがアクセスできるよう以下の設定を行います。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "cognito-idp.amazonaws.com"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "your-external-id"
}
}
}
]
}
External IDの設定は、混同副攻撃(Confused Deputy Attack)を防ぐための重要なセキュリティ対策です。ランダムな文字列を生成して使用することを推奨します。
SNS側の本番化と上限設定
SMSサンドボックスの終了申請
開発環境から本番環境への移行で最も重要なステップが、SMSサンドボックスの終了申請です。サンドボックス環境では、事前に検証済みの電話番号にのみSMSを送信できる制限があります。
サンドボックス終了の申請手順は以下の通りです。
- SNSコンソールにアクセスし、「テキストメッセージング (SMS)」セクションを開く
- アカウント情報タブで「Exit SMS sandbox」ボタンをクリック
- 自動的にサポートケースが起票される画面に遷移
- 使用目的、送信先国、予想送信量などの情報を入力
- 申請を送信し、承認を待つ(通常1-3営業日)
申請時には、具体的なユースケースと送信量の見積もりを明確に記載することが承認率を上げるポイントです。例えば「ユーザー認証用のワンタイムパスワード送信、月間1000件程度」といった具体的な情報を提供します。
月額使用クォータの増枠申請
SNSのSMS送信には月額使用クォータが設定されており、デフォルトでは1.00 USDとなっています。この制限を緩和するには、以下の二段階の手続きが必要です。
まず、AWSサポートセンターで増枠申請を行います。申請時の記載例を以下に示します。
引用:実際の申請文例 サービスの本番運用に向けて、SNSのSMS送信月額上限を500 USDに増額を希望します。 用途:ユーザー認証用OTP送信(1件あたり約0.07 USD × 月間7,000件想定) 送信先:日本国内の携帯電話番号 メッセージタイプ:Transactional(認証用途)
申請が承認された後、重要な追加ステップとして、SNSコンソールの「テキストメッセージングの優先設定」で実際の上限額を設定する必要があります。この設定を忘れると、承認されても実際の上限は変わらないという落とし穴があります。
メッセージタイプの設定
SMS送信において、メッセージタイプを「Transactional」に設定することは重要です。これにより、認証コードのような重要なメッセージが確実に配信されるよう優先度が上がります。SNSコンソールから以下の設定を行います。
表 メッセージタイプによる配信特性の違い
タイプ | 用途 | 配信優先度 | コスト | 到達率 |
---|---|---|---|---|
Transactional | OTP、アラート等 | 高 | やや高い | 高い |
Promotional | マーケティング等 | 低 | 安い | 通常 |
Transactionalタイプは配信の信頼性を重視する設定で、認証用途では必須の選択と言えます。
国・地域別の要件と対策
米国向けSMS送信の特別要件
米国向けのSMS送信には、A2P 10DLCの登録が必須となっています。これは、Application-to-Person(A2P)メッセージングの規制強化によるもので、未登録の場合は配信が拒否される可能性があります。
A2P 10DLC登録には以下の手順が必要です。
- ブランド登録(企業情報の登録)
- キャンペーン登録(送信内容の登録)
- 番号の関連付け
- 審査完了を待つ(約2-4週間)
登録費用は、ブランド登録が4 USD(一回限り)、キャンペーン登録が10 USD/月となっています。米国市場をターゲットとする場合は、この登録作業を開発初期段階で開始することが重要です。
日本市場向けの最適化
日本向けのSMS送信では、現時点で米国のような必須登録制度はありませんが、いくつかの考慮事項があります。まず、日本の携帯電話番号は国際電話番号形式(+81)で管理する必要があり、ユーザー入力時の変換処理を実装することが推奨されます。
const formatJapanesePhoneNumber = (phoneNumber: string): string => {
// 先頭の0を削除して+81を付与
const cleaned = phoneNumber.replace(/\\\\D/g, '');
if (cleaned.startsWith('0')) {
return `+81${cleaned.substring(1)}`;
}
return phoneNumber;
};
// 使用例
const formattedNumber = formatJapanesePhoneNumber('090-1234-5678');
// 結果: +819012345678
また、日本語でのSMS送信時は文字数制限に注意が必要です。日本語を含むSMSは1通あたり70文字が上限となり、それを超えると複数のメッセージに分割されてコストが増加します。認証コードのメッセージは簡潔にまとめることが重要です。
監視と運用の設計
CloudWatchによるメトリクス監視
CloudWatchを活用したSMS送信の監視は、安定運用のために不可欠です。特に重要なメトリクスとアラーム設定を以下に示します。
表 SMS監視で重要なCloudWatchメトリクス
メトリクス名 | 説明 | アラーム閾値の例 | 対応アクション |
---|---|---|---|
SMSMonthToDateSpentUSD | 今月の累積SMS費用 | 月額上限の80% | 上限増額申請の検討 |
NumberOfMessagesSent | 送信成功メッセージ数 | - | トレンド監視 |
NumberOfMessagesFailed | 送信失敗メッセージ数 | 5分間で10件以上 | 即座に原因調査 |
SMSSuccessRate | 送信成功率 | 95%未満 | 配信品質の確認 |
これらのメトリクスを基に、以下のようなアラームを設定することを推奨します。
import { Alarm, Metric, ComparisonOperator } from 'aws-cdk-lib/aws-cloudwatch';
import { SnsAction } from 'aws-cdk-lib/aws-cloudwatch-actions';
import { Topic } from 'aws-cdk-lib/aws-sns';
const alertTopic = new Topic(this, 'SMSAlertTopic');
new Alarm(this, 'SMSSpendingAlarm', {
metric: new Metric({
namespace: 'AWS/SNS',
metricName: 'SMSMonthToDateSpentUSD',
statistic: 'Maximum'
}),
threshold: 400, // 月額上限500USDの80%
evaluationPeriods: 1,
comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
alarmDescription: 'SMS月額使用量が上限の80%に到達',
}).addAlarmAction(new SnsAction(alertTopic));
配信ログの活用
SMS配信の詳細ログをCloudWatch Logsに出力することで、配信失敗の原因を詳細に分析できます。SNSコンソールから配信ステータスログを有効化し、以下のような情報を取得できます。
配信ログに含まれる主要な情報は以下の通りです。
- メッセージID
- 配信ステータス(SUCCESS/FAILURE)
- 送信先電話番号(マスキング済み)
- 配信プロバイダー
- 料金情報
- エラーコード(失敗時)
これらのログを定期的に分析することで、特定のキャリアや地域での配信問題を早期に発見できます。
実装時の落とし穴と対策
レート制限への対応
SMS送信にはレート制限が存在し、短時間に大量のメッセージを送信しようとすると制限にかかる可能性があります。特に、ユーザー登録が集中する時間帯やキャンペーン実施時には注意が必要です。
実装上の対策として、エクスポネンシャルバックオフを実装することを推奨します。
const sendSMSWithRetry = async (
phoneNumber: string,
message: string,
maxRetries: number = 3
): Promise<void> => {
let retryCount = 0;
let delay = 1000; // 初期遅延1秒
while (retryCount < maxRetries) {
try {
await sns.publish({
PhoneNumber: phoneNumber,
Message: message,
MessageAttributes: {
'AWS.SNS.SMS.SMSType': {
DataType: 'String',
StringValue: 'Transactional'
}
}
}).promise();
return; // 成功したら終了
} catch (error: any) {
if (error.code === 'Throttling' && retryCount < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2; // エクスポネンシャルバックオフ
retryCount++;
} else {
throw error;
}
}
}
};
コスト最適化の戦略
SMS送信コストは積み重なると大きな金額になる可能性があります。コスト最適化の観点から、以下の戦略を検討することが重要です。
まず、認証方法の多様化を検討します。メールアドレス認証やソーシャルログインなど、SMS以外の認証方法を併用することで、SMS送信量を削減できます。また、MFAをオプション化し、セキュリティ要件の高いユーザーのみに適用することも有効です。
次に、SMS再送信の制限を実装します。短時間での再送信要求を制限することで、不正利用や誤操作によるコスト増加を防げます。
interface ResendLimiter {
canResend(userId: string): boolean;
recordResend(userId: string): void;
}
class SMSResendLimiter implements ResendLimiter {
private resendRecords = new Map<string, number[]>();
private readonly windowMs = 60000; // 1分間
private readonly maxResends = 3;
canResend(userId: string): boolean {
const now = Date.now();
const records = this.resendRecords.get(userId) || [];
// 時間窓内の送信回数をカウント
const recentRecords = records.filter(
timestamp => now - timestamp < this.windowMs
);
return recentRecords.length < this.maxResends;
}
recordResend(userId: string): void {
const now = Date.now();
const records = this.resendRecords.get(userId) || [];
records.push(now);
// 古いレコードを削除
const validRecords = records.filter(
timestamp => now - timestamp < this.windowMs
);
this.resendRecords.set(userId, validRecords);
}
}
セキュリティ考慮事項
SMS認証の脆弱性と対策
SMS認証は便利な反面、「SIMスワップ攻撃」や「SS7プロトコルの脆弱性」といったセキュリティリスクが存在します。これらのリスクを完全に排除することは困難ですが、以下の対策により影響を軽減できます。
リスクベース認証の実装が有効な対策の一つです。ログイン時のIPアドレス、デバイス情報、行動パターンなどを分析し、異常を検知した場合は追加の認証を要求する仕組みを構築します。
interface RiskAssessment {
score: number;
factors: string[];
requiresAdditionalAuth: boolean;
}
const assessLoginRisk = (context: LoginContext): RiskAssessment => {
let score = 0;
const factors: string[] = [];
// 新しいデバイスからのログイン
if (!context.isKnownDevice) {
score += 30;
factors.push('unknown_device');
}
// 地理的に離れた場所からのログイン
if (context.geoDistance > 1000) {
score += 40;
factors.push('unusual_location');
}
// 深夜のログイン
const hour = new Date().getHours();
if (hour >= 2 && hour <= 5) {
score += 20;
factors.push('unusual_time');
}
return {
score,
factors,
requiresAdditionalAuth: score >= 50
};
};
認証コードの設計
認証コードの生成と管理においても、セキュリティを考慮した設計が必要です。単純な数字の羅列ではなく、予測困難性と使いやすさのバランスを取ることが重要です。
認証コードの推奨設計は以下の通りです。
- 6桁以上の数字(できれば英数字混在)
- 有効期限は5分以内
- 使用済みコードの再利用防止
- 試行回数の制限(3-5回)
これらの要件を満たす実装例を示します。
import crypto from 'crypto';
class AuthCodeManager {
private codes = new Map<string, AuthCode>();
generateCode(userId: string): string {
// 暗号学的に安全な乱数生成
const code = crypto.randomInt(100000, 999999).toString();
this.codes.set(userId, {
code,
createdAt: Date.now(),
attempts: 0,
used: false
});
// 有効期限切れコードの自動削除
setTimeout(() => {
this.codes.delete(userId);
}, 5 * 60 * 1000); // 5分
return code;
}
verifyCode(userId: string, inputCode: string): boolean {
const authCode = this.codes.get(userId);
if (!authCode || authCode.used) {
return false;
}
// 試行回数チェック
authCode.attempts++;
if (authCode.attempts > 3) {
this.codes.delete(userId);
return false;
}
// 有効期限チェック
const now = Date.now();
if (now - authCode.createdAt > 5 * 60 * 1000) {
this.codes.delete(userId);
return false;
}
// コード検証
if (authCode.code === inputCode) {
authCode.used = true;
return true;
}
return false;
}
}
interface AuthCode {
code: string;
createdAt: number;
attempts: number;
used: boolean;
}
今後の展望と準備すべきこと
パスキー認証への移行準備
SMS認証は現在も広く使われていますが、「パスキー(Passkeys)」という新しい認証技術が急速に普及しています。AppleやGoogleがプッシュしているこの技術は、生体認証とデバイス認証を組み合わせた、よりセキュアで使いやすい認証方法です。
現時点でSMS認証を実装する場合でも、将来的なパスキー移行を見据えた設計が重要です。認証方法を抽象化し、新しい認証メソッドを追加しやすい構造にしておくことを推奨します。
規制強化への対応
世界各国でSMS送信に関する規制が強化される傾向にあります。米国のA2P 10DLCはその一例ですが、今後他の国でも同様の規制が導入される可能性があります。
規制対応のために準備しておくべき事項は以下の通りです。
- 送信元の明確化(企業情報の整備)
- オプトイン/オプトアウトの仕組み
- 送信ログの長期保管
- プライバシーポリシーの整備
これらの準備を事前に行っておくことで、新たな規制が導入された際にも迅速に対応できます。
まとめ
AWS AmplifyとCognito、SNSを使用した電話番号認証の実装は、2024年以降の変更により以前よりも複雑になりました。特に「End User Messaging」への改称、「SMSサンドボックス」の導入、OAuth認証フローの推奨変更など、押さえるべきポイントが増えています。
しかし、これらの変更は全てセキュリティと運用性の向上を目的としたものです。本記事で紹介した手順と考慮事項を踏まえて実装することで、セキュアで安定した認証基盤を構築できます。
実装時のチェックリストをもう一度確認しましょう。
- Amplify Gen 2での電話番号認証設定の実装
- CognitoのIAMロールと送信元アイデンティティの準備
- SNS SMSサンドボックスの終了申請
- 月額使用クォータの増枠申請(二段階の手続き)
- メッセージタイプのTransactional設定
- 米国向けの場合はA2P 10DLC登録
- CloudWatchによる監視とアラーム設定
これらの設定を適切に行うことで、本番環境での安定した電話番号認証を実現できます。特に上限緩和申請は時間がかかる可能性があるため、開発初期段階から準備を進めることが成功の鍵となります。