メインコンテンツまでスキップ

ネイティブアプリ 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_valuesmax_age で再認証を要求した場合、バインドされたセッションの ACR が不足していると再認証バインドフローに進みます。

エンドポイント

エンドポイントメソッド呼び出し元説明
/api/oidc/v1/authorizeGETWebView認可エンドポイント。User-Agent でネイティブアプリを検出し、バインドフローを開始
/api/native/v1/bindPOSTネイティブアプリアクセストークンで認証し、bound_session にユーザーセッションをバインド
/api/native/v1/completeGETWebViewnonce 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

動作の流れ

  1. IdP の /api/oidc/v1/authorize302 レスポンスで上記パスにリダイレクトします
  2. WebView がこのリダイレクトを検出したら、ナビゲーションをキャンセルします
  3. リダイレクト先の URL からクエリパラメータを抽出します
  4. 抽出した bind_id を使って、ネイティブ側から POST /api/native/v1/bind を呼び出します
備考

/native/start および /native/login-start はフロントエンドのフォールバックページとしても存在しますが、通常の動作ではインターセプトによりページの読み込みは発生しません。インターセプトに失敗した場合のみフォールバックページが表示されます。

iOS(WKWebView)の場合

WKNavigationDelegatedecidePolicyFor メソッドでナビゲーションを判定します。

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)の場合

WebViewClientshouldOverrideUrlLoading メソッドでナビゲーションを判定します。

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 とサーバー側の値が一致することで、バインドリクエストの正当性を検証しています
DPoP の使用を推奨

ネイティブアプリから WebView にセッションをバインドする場合、アクセストークンの横取りリスクを低減するため DPoP バインドを強制することを推奨します。 詳細は DPoP を参照してください。

関連ページ