ネイティブモバイルアプリケーション向けPKCE
カスタムURLスキームとユニバーサルリンクを使用してiOSとAndroidネイティブアプリケーションでOAuth 2.0認可コード + PKCEを実装する方法。
PKCE
詳細な説明
モバイルアプリ向けPKCE
モバイルアプリケーションはパブリッククライアントです。アプリバイナリは逆コンパイル可能なため、client_secretを安全に保存できません。PKCEは、iOS、Android、その他のネイティブプラットフォームで認可コードフローを保護するために不可欠です。
モバイル固有の考慮事項
カスタムURLスキーム vs ユニバーサルリンク
モバイルアプリは認可コールバックを受信するためにリダイレクトURIを登録します:
| 方法 | 例 | セキュリティ |
|---|---|---|
| カスタムURLスキーム | myapp://callback |
低 — 任意のアプリが同じスキームを登録可能 |
| ユニバーサルリンク(iOS) | https://app.example.com/callback |
高 — ドメイン所有権の検証 |
| App Links(Android) | https://app.example.com/callback |
高 — ドメイン所有権の検証 |
カスタムURLスキームよりもユニバーサル/App Linksを常に優先してください。カスタムスキームは同じスキームを登録する悪意のあるアプリによる傍受に脆弱です。
モバイルのフロー手順
- フロー開始前にアプリ内でPKCEペアを生成
- システムブラウザを開く(iOSではASWebAuthenticationSession、AndroidではCustom Tabs)— 埋め込みWebViewは使用しない
- ブラウザでユーザーが認証
- ユニバーサルリンク/カスタムURLスキームでコールバックを受信
- トークンのcode + code_verifierを交換
- トークンを安全に保存(iOSではKeychain、AndroidではKeystore)
iOS例(Swift)
import CryptoKit
let verifier = Data(count: 32).map { _ in
UInt8.random(in: 0...255)
}
let codeVerifier = Data(verifier).base64URLEncodedString()
let challenge = SHA256.hash(data: Data(codeVerifier.utf8))
let codeChallenge = Data(challenge).base64URLEncodedString()
Android例(Kotlin)
val verifier = ByteArray(32).also { SecureRandom().nextBytes(it) }
val codeVerifier = Base64.encodeToString(verifier, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
val digest = MessageDigest.getInstance("SHA-256").digest(codeVerifier.toByteArray())
val codeChallenge = Base64.encodeToString(digest, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
セキュリティベストプラクティス
- システムブラウザを使用し、埋め込みWebViewは使用しない(資格情報を傍受可能)
- カスタムURLスキームよりもユニバーサル/App Linksを優先
- プラットフォームのセキュアストレージ(Keychain/Keystore)にトークンを保存
- トークンエンドポイントに証明書ピンニングを実装
- 認可リクエストごとに新しいPKCEペアを使用
ユースケース
IDプロバイダー(Google Sign-In、Azure AD B2Cなど)を介してユーザーを認証するiOSまたはAndroidアプリ。アプリがPKCEペアを生成し、システムブラウザをログイン用に開き、ユニバーサルリンクで認可コードを受信してトークンに交換します。