🚀
WebView×OAuth2実践ガイド🚀—Flutter + AppAuthで安全ログインを作る
🚀 はじめに
モバイルアプリに OAuth 2.0 ログインを組み込むとき、「とりあえず WebView でログイン画面を開けばいいでしょ?」と考えがち。しかし実際には Cookie 共有・生体認証・Deep Link…課題が山盛りです。本稿では ネイティブ+ WebView アプリを題材に、AppAuth(Flutter)で安全&爆速に OAuth 2.0 を実装する手順を紹介します。
📚 WebView × OAuth2 の落とし穴
- WebView ログインは基本 NG ― RFC 8252 で非推奨
- Cookie ストアが分離 ― WKWebView は Safari と別
- リダイレクト受信が複雑 ― JS Bridge やポーリングが必要
解決策は 「認可コードはネイティブで、WebView にはセッションだけ渡す」 型!
🏗 アーキテクチャ
ASWebAuthenticationSession/Chrome Custom Tabs を使うだけでキーチェーン+ Face ID 自動連携 👌
🔧 Flutter 実装
1️⃣ 依存
dependencies:
flutter_appauth: ^6.0.2
uni_links: ^0.5.1
webview_flutter: ^4.7.0
webview_cookie_manager: ^2.0.1
2️⃣ 認可コード取得
dart
コピーする編集する
final auth = FlutterAppAuth();
final res = await auth.authorize(
AuthorizationRequest(
'DEMO_CLIENT', 'com.example.app:/oauth',
serviceConfiguration: AuthorizationServiceConfiguration(
authorizationEndpoint: 'https://idp.example.com/oauth/authorize',
tokenEndpoint: 'https://idp.example.com/oauth/token',
),
scopes: ['openid', 'profile'],
preferEphemeralSession: true,
),
);
final code = res.authorizationCode; // 認可コードを取得
3️⃣ BE でセッション発行
dart
コピーする編集する
final r = await dio.post('/api/auth/oauth',
data: {'code': code}); // 認可コードをバックエンドに送信
final sessionId = r.data['session_id'];
4️⃣ WebView へ Cookie 注入
dart
コピーする編集する
await WebviewCookieManager().setCookies([
Cookie('session_id', sessionId)
..domain = 'app.example.com'
..path = '/'
..secure = true
..httpOnly = true,
]);
Navigator.push(context, MaterialPageRoute(
builder: (_) => const WebView(
initialUrl: 'https://app.example.com/',
),
));
🔑 Deep Link 設定メモ(iOS / Android)
iOS (Universal Link)
- Xcode で Associated Domains を有効
-
applinks:your.domain.com
を追加 -
.well-known/apple-app-site-association
を配置(/oauth*
を許可)
Android (App Link)
AndroidManifest.xml
に intent-filter
を宣言し、
assetlinks.json
をサーバに置いて検証ツールで確認。
💡 ポイント
- 可能なら https の Universal/App Link を採用し、カスタムスキームは最小限に
- Flutter では
uni_links
で iOS/Android 共通コードで受信できる
⚔️ AppAuth と手作り実装の比較
AppAuth | 手作り (HTTP + WebView) | |
---|---|---|
認可 URL 生成 | ✅ 自動 | ⚠️ 手動で組立 |
PKCE 対応 | ✅ 標準 | ❌ 自前実装 |
AuthSession / CustomTabs | ✅ ラップ済 | ⚠️ プラグイン要 |
保守コスト | 🔽 低 | 🔼 高 |
🚀 ステージング用 IDP で先行開発するメリット
- チームの手が止まらない:ID 基盤完成を待たずクライアント側を完了
-
差し替え一瞬:エンドポイントとクライアント ID を
.env
で切替 - QA 前倒し:再認可・refresh 失効などのエッジケースを早期検証
規格に乗っかる = 最大の生産性ブースト 💪
🩹 よくあるハマり
症状 | 原因 |
---|---|
ログインループ | Cookie ドメイン or SameSite=None 抜け |
iOS だけ未ログイン | Cookie 注入 →WebView 起動順が逆 |
Face ID 出ない | WKWebView で開いている |
🎉 まとめ
- AppAuth なら ID プロバイダ非依存で認可コード取得
- コードはネイティブで受取り、WebView には Cookie だけ 渡すのが安全
- 生体認証・自動入力は OS が面倒を見る
- 仮 IDP で PoC → 本番基盤へ差し替えれば工数ゼロ
この型を覚えれば次の案件でもコピペで爆速実装できますね 🚀
Discussion
記事、大変参考になりました!ありがとうございます。
一点だけ気になったのですが、記事内で「codeをBEに渡している」といった記述がいくつか見られる箇所について、実際には認証サーバーとのやり取りで得た「アクセストークン」をバックエンドに渡してセッションを確立する流れかと理解しました。
もし「認可コード」ではなく「アクセストークン」を指しているのであれば、そのように明記されると、OAuthのフローに馴染みのない読者にとってはより誤解なく理解が進むかと思いました。
ご指摘ありがとうございます!おっしゃる通りでした。
標準的なOAuth2.0フローでは「認可コード」をバックエンドに渡すのが一般的です。シーケンス図では正しく描かれていましたが、サンプルコードでは「アクセストークン」を渡す実装になっていました。
この不一致を修正し、サンプルコードを「認可コード」をバックエンドに渡す形に更新しました。貴重なフィードバックに感謝します!
ありがとうございます!
若干私の解釈と違いがあり、質問させてください。
多分認可コードをBEに渡すのは、推奨されていないと思っています。
認可コードはクライアントでトークンを取得するために使用するべきかなと思います。。
なので、authorizeAndExchangeCodeメソッドを使用して、アクセストークンまで取得し、アクセストークンを下にセッション生成が良いかなと思いました!
以下、Geminiに聞いてみた結果です。
OIDC(OpenID Connect)において、フロントエンド(ユーザーエージェント、通常はブラウザ)で取得した認可コードをリソースサーバーに直接渡して、リソースサーバーがその認可コードを使ってIDプロバイダー(IdP)からトークン(アクセストークン、IDトークンなど)を取得する構成は、一般的には推奨されませんし、セキュリティ上の懸念があります。
以下にその理由と、より一般的な構成について説明します。
推奨されない理由(セキュリティ上の懸念点):
一般的なOIDC認可コードフロー:
より一般的で推奨されるOIDCの認可コードフローは以下のようになります。
リソースサーバーの役割:
リソースサーバーの主な役割は、アクセストークンを検証し、保護されたリソースへのアクセスを制御することです。トークンの取得プロセスに直接関与することは通常ありません。
代替案(BFF: Backend For Frontend パターンなど):
もしフロントエンドとリソースサーバーの間に何らかのバックエンドコンポーネントを置くことができるのであれば、BFF(Backend For Frontend)パターンを検討できます。この場合、BFFがOIDCクライアントとしての役割を担い、フロントエンドから認可コードを受け取ってトークンを取得し、適切に管理・利用することができます。
まとめ:
フロントエンドで取得した認可コードを直接リソースサーバーに渡してトークンを取得する構成は、セキュリティリスクや責任範囲の観点から推奨されません。OIDCの標準的なフローに従い、クライアントアプリケーション(通常はバックエンド)が認可コードを扱ってトークンを取得し、そのアクセストークンをリソースサーバーでのリソースアクセスに利用する形が望ましいです。
バックエンド+フロントエンドで構成されるOAuth Clientでは、認可コードをバックエンドに渡すことは適切です。
このようなケースでは、アクセストークンをバックエンドに渡すことがアンチパターンです。
アクセストークンはリソースサーバーが検証して利用するものであり、クライアントは本来その中身を知らなくても良いものであり、クライアントによるアクセストークン検証はOAuthの仕様で定義されていません。
この2つでは認可フローも当然異なります。この辺りを解説した記事を書いているので参考にしてみてください。
このGeminiの回答は、「リソースサーバーに認可コードを投げるのはNG」みたいな解説なのでそれは質問にあるバックエンドに認可コードを投げるのは適切かとは別の話です。認可コードを受け付けて良いという部分の説明としては、以下の部分です。
そしてそもそも、リソースアクセスのための仕組みであるOAuthをログインの目的で利用することが厳密には正しくない(OAuth認証()と呼ばれているもの)ことも認識しておきましょう。
なるほど!
私が勘違いしてました!!
丁寧なご説明ありがとうございます!理解しました!!