ステップ6:セキュリティ強化 - PKCEによる認証セキュリティの向上
このステップは現在作成中です
本番環境を想定したセキュリティ強化の実装手順を準備中です。 完成次第、公開いたします。
このステップの概要
このステップでは、ステップ2で構築したユーザー認証システムをさらにセキュアにするため、PKCE(Proof Key for Code Exchange) を使用した認証フローに移行します。
現在の実装では、学習とプロトタイピングを容易にするため「USER_PASSWORD_AUTH」フローを使用していますが、本番環境のモバイルアプリケーションでは、より高度なセキュリティが求められます。特に、認可コードの傍受攻撃を防ぐために、PKCEの実装が業界標準となっています。
なぜPKCEが必要なのか
モバイルアプリの特性とセキュリティリスク
モバイルアプリケーションは、以下の理由から特別なセキュリティ対策が必要です:
1. クライアントシークレットを安全に保管できない
従来のWebアプリケーションでは、サーバー側でクライアントシークレット(秘密鍵)を安全に保管できますが、モバイルアプリではアプリのコードがユーザーのデバイス上に存在するため、以下のリスクがあります:
- 逆コンパイルによる解析:AndroidやiOSアプリは、適切なツールを使えば逆コンパイルしてソースコードを読み取ることができます
- メモリダンプ攻撃:アプリの実行中にメモリを解析して、埋め込まれた秘密情報を抽出できます
- ネットワーク傍受:中間者攻撃により、アプリとサーバー間の通信を傍受される可能性があります
例えば、アプリのコード内に以下のようなクライアントシークレットを埋め込んでいた場合:
// ❌ セキュリティリスク:アプリに埋め込まれたシークレット
const CLIENT_SECRET = "abc123xyz789secret";悪意のある攻撃者がアプリを逆コンパイルすれば、この秘密情報を簡単に取得できてしまいます。
2. 認可コード傍受攻撃のリスク
OAuth 2.0の標準的な認可コードフローでは、以下のような攻撃シナリオが存在します:
攻撃の流れ:
- 正規ユーザーがモバイルアプリで認証を開始
- ユーザーがCognitoのログイン画面で認証を完了
- Cognitoが認可コード(
code=xyz123)をアプリにリダイレクト - 攻撃者がこの認可コードを傍受(例:悪意のあるアプリが同じカスタムURLスキームを登録)
- 攻撃者が傍受した認可コードを使って、自分のアプリでトークンを取得
- 攻撃者がユーザーになりすましてAPIにアクセス
このような攻撃を防ぐために、PKCEが開発されました。
PKCEによる解決策
PKCE(Proof Key for Code Exchange)は、RFC 7636で標準化されたOAuth 2.0の拡張機能で、認可コード傍受攻撃を防ぎます。
リダイレクトURI(redirect_uri)の選択
PKCEを実装する際、認証が完了した後にユーザーをどこにリダイレクトするかを決める必要があります。モバイルアプリでは、主に2つのアプローチがあり、それぞれ異なる特徴を持っています。
カスタムURLスキームを使う方法
最もシンプルなアプローチは、com.example.myapp://callbackのようなカスタムURLスキームを使用する方法です。これは、アプリ固有のURL形式を定義して、そのURLが呼び出されたときにアプリを起動する仕組みです。
この方法の大きな利点は、実装が非常にシンプルで、学習段階やプロトタイピングの際に素早く動作確認できることです。また、iOS・Android両方で同じ形式のURLを使えるため、コードの共通化もしやすくなります。
ただし、URLスキームの命名には注意が必要です。例えばmyapp://callbackのような一般的な名前を使うと、他の開発者が同じ名前を使っている可能性があり、特にAndroid 6.0以前では複数のアプリが同じURLスキームを登録できてしまうため、意図しないアプリが起動してしまうリスクがあります。
そのため、カスタムURLスキームを使う場合は、リバースドメイン形式(例:com.yourcompany.yourapp://callback)を使用することが強く推奨されます。これにより、あなたの組織のドメインを基にした一意な識別子となり、他のアプリとの衝突を防ぐことができます。
Universal Links / App Linksを使う方法
本番環境で高いセキュリティを求める場合は、https://yourapp.com/auth/callbackのような通常のHTTPS URLを使う方法があります。これはiOSでは「Universal Links」、Androidでは「App Links」と呼ばれる仕組みです。
この方法の最大の特徴は、あなたが実際にドメインを所有していることを証明する必要がある点です。具体的には、iOSの場合はhttps://yourapp.com/.well-known/apple-app-site-associationに特殊なファイルを配置し、Androidの場合はhttps://yourapp.com/.well-known/assetlinks.jsonにJSONファイルを配置します。OSがこれらのファイルを確認することで、「このアプリは本当にこのドメインの所有者が作ったものだ」と検証できるのです。
この仕組みにより、カスタムURLスキームの衝突攻撃を完全に防ぐことができます。さらに、ユーザーがアプリをまだインストールしていない場合は、自動的にWebページにフォールバックされるため、ユーザーエクスペリエンスも向上します。Apple・Googleもこの方式を推奨しており、本番環境のアプリには最適な選択です。
一方で、初期設定がやや複雑になることと、実際のドメインを所有している必要があることがハードルとなります。
どちらを選ぶべきか
学習段階や開発初期では、リバースドメイン形式のカスタムURLスキーム(com.example.myapp://callback)を使って、まずはPKCEの仕組みを理解し、動作を確認することをお勧めします。実装がシンプルで、すぐに動かせるからです。
そして、アプリがある程度完成し、App StoreやGoogle Play Storeへの公開を視野に入れる段階になったら、Universal Links / App Linksへの移行を検討しましょう。特に、企業向けアプリや金融・医療などのセンシティブな情報を扱うアプリでは、この移行は必須と考えるべきです。
PKCEの仕組み
1. アプリが「Code Verifier」(ランダムな秘密文字列)を生成
【生成要件(RFC 7636準拠)】
- 長さ:43〜128文字
- 文字セット:[A-Z] [a-z] [0-9] - . _ ~
例: code_verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
2. Code VerifierのSHA256ハッシュをBase64 URL-encodeして「Code Challenge」を生成
例: code_challenge = BASE64URL(SHA256(code_verifier))
= "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
※SHA256は32バイトのバイナリデータを生成し、それをBase64 URL-encodingします
3. 認可リクエスト時に「Code Challenge」と「state」を送信(Verifierは送らない)
GET https://your-domain.auth.ap-northeast-1.amazoncognito.com/oauth2/authorize?
response_type=code&
client_id=YOUR_APP_CLIENT_ID&
redirect_uri=com.example.myapp://callback&
scope=openid+email+profile&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
code_challenge_method=S256&
state=RANDOM_STATE_VALUE
※ state:CSRF攻撃を防ぐためのランダムな文字列(必須推奨)
4. ユーザー認証後、認可コードとstateを受け取る
リダイレクト例:
com.example.myapp://callback?code=xyz123&state=RANDOM_STATE_VALUE
※アプリはstateの値を検証し、一致しない場合はリクエストを拒否
5. トークン交換時に元の「Code Verifier」を送信
POST https://your-domain.auth.ap-northeast-1.amazoncognito.com/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=xyz123&
client_id=YOUR_APP_CLIENT_ID&
redirect_uri=com.example.myapp://callback&
code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
6. Cognitoが検証:BASE64URL(SHA256(code_verifier)) == code_challenge ?
✅ 一致すれば正当なアプリ → アクセストークン、IDトークン、リフレッシュトークンを返す
❌ 不一致なら攻撃者の可能性 → エラーを返すなぜ攻撃を防げるのか:
- 攻撃者が認可コードを傍受しても、元の「Code Verifier」を知らないため、トークンと交換できません
- Code Verifierはアプリのメモリ内でのみ保持され、ネットワーク上を流れるのはそのハッシュ値(Code Challenge)のみ
- SHA256ハッシュは一方向関数のため、Code ChallengeからCode Verifierを逆算することは計算量的に不可能
- Base64 URL-encodingは可逆的な変換ですが、元のハッシュ値は32バイトのランダムなバイナリデータであり、そこからCode Verifierを逆算することはできません
セキュリティのポイント:
- Code Verifierは認可リクエスト時には送信せず、トークン交換時のみ送信
- stateパラメータにより、CSRF(Cross-Site Request Forgery)攻撃も防御
- redirect_uriの厳密な検証により、不正なリダイレクトを防止
USER_PASSWORD_AUTH との比較
| 項目 | USER_PASSWORD_AUTH (現在の実装) | Authorization Code + PKCE (推奨実装) |
|---|---|---|
| セキュリティレベル | 標準 | 高 |
| 認可コード傍受 | 対策なし | PKCEで防御 |
| クライアントシークレット | 不要 | 不要(PKCEで代替) |
| 実装の複雑さ | シンプル | やや複雑 |
| OAuth 2.0標準準拠 | 部分的 | 完全準拠 |
| 業界推奨度 | 開発・テスト環境向け | 本番環境向け |
| AWSの推奨 | ❌ 非推奨(モバイル) | ✅ 推奨 |
本番環境での必要性
以下のような要件がある場合、PKCEの実装は必須です:
1. エンタープライズ向けアプリケーション
- 企業の機密情報を扱うアプリ
- 金融・医療・個人情報を含むアプリ
- コンプライアンス要件(SOC2、ISO 27001など)を満たす必要がある場合
2. アプリストア公開予定のアプリ
- Apple App StoreやGoogle Play Storeで公開する場合
- セキュリティレビューで指摘される可能性が高い
- ユーザーの信頼を得るため
3. 大規模ユーザーベースのアプリ
- 多数のユーザーが利用する場合、攻撃対象になりやすい
- セキュリティインシデントの影響範囲が大きい
このステップで学ぶこと
ステップ6では、以下の内容を実装・学習します:
- PKCE(Proof Key for Code Exchange) の仕組みと実装方法
- Authorization Code Grant フローへの移行手順
- Cognito Hosted UI の設定とカスタマイズ
- expo-auth-session を使用したモバイルアプリでのOAuth 2.0実装
- Code Verifier と Code Challenge の生成・検証ロジック
- セキュアなトークン管理とストレージ
- PKCEフローのテストとデバッグ手法
参考資料
PKCE についてさらに学びたい場合は、以下の公式ドキュメントを参照してください:
- RFC 7636 - Proof Key for Code Exchange (PKCE)
- Using PKCE in authorization code grants - Amazon Cognito
- Security best practices for Amazon Cognito user pools
- OAuth 2.0 for Native Apps (RFC 8252)
- How to use OAuth 2.0 in Amazon Cognito(AWS Security Blog)
進行状況について
このステップの詳細な実装手順、サンプルコード、動作確認方法は現在準備中です。 完成次第、このページを更新いたします。
それまでは、ステップ2で学習したUSER_PASSWORD_AUTHフローで開発を進めてください。 本番環境へのデプロイ前に、このステップを参照してセキュリティ強化を実施することを推奨します。