ネイティブアプリ WebView 内の Web サービスログイン
PocketSign Link v2 に対応したネイティブアプリの WebView 内で Web サービスを表示し、その WebView 上で OIDC 認可フローを実行するための仕組みです。
ネイティブアプリ側のログインセッションを WebView にバインドすることで、ユーザーに追加のログイン操作を求めることなく、WebView 内の Web サービスで OIDC 認証を完了させることができます。
自社のネイティブアプリ内の WebView で v2 連携済みの Web サービスを表示したい外部アプリ開発者を対象としています。
概要
通常のブラウザでは、ユーザーが IdP のログイン画面で認証を行います。しかし、ネイティブアプリの WebView 内では以下の問題があります。
- WebView には IdP のログインセッション(Cookie)がない
- ネイティブアプリ側ではすでにユーザーが認証済みである
セッションバインドは、ネイティブアプリが持つ認証済みセッションを WebView に引き継ぐことで、この問題を解決します。
前提条件
- ネイティブアプリが KLON IdP にログイン済みで、
nativeスコープを含むアクセストークンを保持していること - WebView の User-Agent に
KLON-NativeAppサフィックスが含まれていること - Web サービスの OIDC クライアントが登録済みであること
フロー図
未認証時(初回ログインバインド)
再認証時
Web サービスが acr_values や max_age で再認証を要求した場合、バインドされたセッションの ACR が不足していると再認証バインドフローに進みます。
エンドポイント
| エンドポイント | メソッド | 呼び出し元 | 説明 |
|---|---|---|---|
/api/oidc/v1/authorize | GET | WebView | 認可エンドポイント。User-Agent でネイティブアプリを検出し、バインドフローを開始 |
/api/native/v1/bind | POST | ネイティブアプリ | アクセストークンで認証し、bound_session にユーザーセッションをバインド |
/api/native/v1/complete | GET | WebView | nonce Cookie を検証し、WebView にセッション Cookie を発行 |
ネイティブアプリ側の実装
1. User-Agent の設定
WebView の User-Agent 文字列に KLON-NativeApp を含めてください。
Mozilla/5.0 ... Mobile/15E148 KLON-NativeApp
IdP はこのサフィックスを検出してバインドフローに分岐します。
2. URL インターセプト
WebView のナビゲーションをインターセプトし、IdP からのリダイレクト URL を検出してネイティブ側の処理に移行する必要があります。
インターセプト対象のパス
| パス | 発生条件 | 抽出するパラメータ |
|---|---|---|
/native/start | 未認証時 | bind_id |
/native/login-start | 再認証時 | bind_id, request_acr_values, max_age |
動作の流れ
- IdP の
/api/oidc/v1/authorizeが302レスポンスで上記パスにリダイレクトします - WebView がこのリダイレクトを検出したら、ナビゲーションをキャンセルします
- リダイレクト先の URL からクエリパラメータを抽出します
- 抽出した
bind_idを使って、ネイティブ側からPOST /api/native/v1/bindを呼び出します
/native/start および /native/login-start はフロントエンドのフォールバックページとしても存在しますが、通常の動作ではインターセプトによりページの読み込みは発生しません。インターセプトに失敗した場合のみフォールバックページが表示されます。
iOS(WKWebView)の場合
WKNavigationDelegate の decidePolicyFor メソッドでナビゲーションを判定します。
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}
if url.path == "/native/start" || url.path == "/native/login-start" {
// ナビゲーションをキャンセルしてネイティブ処理へ
decisionHandler(.cancel)
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
let bindId = components?.queryItems?.first(where: { $0.name == "bind_id" })?.value
// bindId を使ってバインド処理を開始
startBindFlow(bindId: bindId, url: url)
return
}
decisionHandler(.allow)
}
Android(WebView)の場合
WebViewClient の shouldOverrideUrlLoading メソッドでナビゲーションを判定します。
override fun shouldOverrideUrlLoading(
view: WebView,
request: WebResourceRequest
): Boolean {
val url = request.url
val path = url.path ?: return false
if (path == "/native/start" || path == "/native/login-start") {
// ナビゲーションをキャンセルしてネイティブ処理へ
val bindId = url.getQueryParameter("bind_id")
// bindId を使ってバインド処理を開始
startBindFlow(bindId, url)
return true
}
return false
}
React Native(WebView)の場合
onShouldStartLoadWithRequest コールバックでナビゲーションを判定します。
<WebView
onShouldStartLoadWithRequest={(request) => {
const url = new URL(request.url);
if (url.pathname === "/native/start" || url.pathname === "/native/login-start") {
// ナビゲーションをキャンセルしてネイティブ処理へ
const bindId = url.searchParams.get("bind_id");
startBindFlow(bindId, url);
return false;
}
return true;
}}
/>
3. POST /api/native/v1/bind の呼び出し
curl -X POST "https://id.mock.klon.you/api/native/v1/bind" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-d '{"bind_id": "<BIND_ID>"}'
- アクセストークンには
nativeスコープが必要です - レスポンスの
bind_complete_urlを取得します
4. バインド完了
取得した bind_complete_url を WebView でロードします。サーバーが Cookie を発行し、認可フローに自動的にリダイレクトされます。
バインドされたセッションの性質
- バインドされたセッション(bound session)は、ネイティブアプリのログインセッションを親として紐づけられます
- 親セッションが無効化されると、バインドされたセッションも連動して無効化されます
- バインドされたセッション上で直接再認証を行うことはできません。再認証が必要な場合は、再認証バインドフロー(
/native/login-start)を経由してネイティブアプリ側で実行します - 1 つの親セッションに対して有効なバインドされたセッションは 1 つのみです。新しいバインドが作成されると、以前のバインドは無効化されます
セキュリティ
- バインドフローには 5 分の有効期限があります。タイムアウトした場合はフローの最初からやり直してください
bind_idは使い捨てです。一度バインドが完了したbind_idは再利用できません- nonce Cookie とサーバー側の値が一致することで、バインドリクエストの正当性を検証しています
:::caution DPoP の使用を推奨 ネイティブアプリから WebView にセッションをバインドする場合、アクセストークンの横取りリスクを低減するため DPoP バインドを強制することを推奨します。 詳細は DPoP を参照してください。 :::
関連ページ
- ネイティブアプリの OIDC フローは ネイティブアプリ向け OIDC 連携
- 通常のブラウザログインフローは 通常のログイン連携
- 再認証の条件は 再認証と ACR / AMR