mcpField
D
descope-byos-builder

descope-byos-builder

github.com/descope開発1842 回インストール1 日前に更新
リポジトリ

descope-byos-builder について

descope-byos-builderは、Descopeのフローエンジンを維持しながら、エクスポートしたflow JSONからReact BYOS画面を構築するためのガイドです。画面やサブフローの解析、UI実装、認証フローの検証と、よくある失敗への対処を扱います。

主な機能

  1. 01エクスポートしたflow JSONの解析
  2. 02画面とインタラクションの対応付け
  3. 03React BYOS画面の構築
  4. 04サブフローと画面名の競合の追跡
  5. 05認証フローと一般的な問題の検証

活用シーン

  1. 01Descopeフロー向けのカスタムUI構築
  2. 02フロー変更後のBYOS更新
  3. 03認証状態や画面ハンドラーの問題調査
  4. 04カスタムUIとホスト画面の動作確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/descope/skills --skill descope-byos-builder

インストール後、Claude Code で/descope-byos-builderを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

parse-flow.mjs
references/
byos-component-patterns.md
gotchas.md

詳細

Descope BYOS ビルダー

Descope フローの JSON エクスポートを、state.next(interactionId, form) を呼び出す実用的な React BYOS 画面に変換します。以下の失敗例は実際の BYOS セッションで記録されたもので、どれも初回は15〜60分のロスにつながりました。

使用する場面

  • Descope のフローエンジンを維持しながら、Descope フロー上にカスタム UI を構築する場合(クライアント側での JWT 解析は不要で、フローのロジックもすべて維持されます)
  • Descope コンソールで基盤となるフローが変更された後に、既存の BYOS 実装を変更する場合
  • 「フローは完了するのにセッションの状態が正しくない」「ボタンを押しても何も起きない」「セッションが匿名のまま」「パスキーの認証手続きが中断される」といった症状をデバッグする場合
  • 既存の BYOS がホスト画面と同等の動作をしているか監査する場合
  • logged-in の後に実行される認証後の昇格サブフロー(例:add-passkeys)を追加する場合

次の場合は使用しないでください: ホストされた <Descope flowId=... /> ウィジェットだけでフローが完全に動作する場合(BYOS はトレードオフがあり、コードを変更しない限りフローの編集が反映されなくなります)。

絶対に守るルール

すべての BYOS コンポーネントは、エクスポートされたフロー JSON を根拠にしてください。 インタラクション ID、出力キー名、画面名を推測しないでください。カタログにあるすべての失敗は、もっともらしく見える値を誰かが勝手に作ったことに端を発しています。

必要な入力(まずユーザーに確認してください)

このスキルでは Descope コンソールからフローを取得できません。エージェントにコンソールへのアクセス権がないためです。作業を始める前に、以下をユーザーに提供するよう依頼してください。

  1. フロー JSON ファイル — メインフローと、それが呼び出すすべてのサブフロー(LoadSubflow arguments.flowId)の両方について、Descope コンソールからエクスポートしたもの。認証後の昇格サブフロー(例:add-passkeys)も含みます。ファイルパスまたは貼り付けた JSON のどちらでも構いません。
  2. プロジェクト ID と ベース URL(未設定の場合)— AuthProvider / SDK の初期化に必要です。
  3. React アプリのマウントポイント — BYOS のエントリーポイントをレンダリングするファイルまたはコンポーネントです。
  4. 既存の BYOS コード(変更する場合)— 現在の画面コンポーネントと画面マップへのパスです。

ユーザーがメインフローの JSON しか提供しない場合は、コンポーネントを生成する前に作業を止めて、サブフローを依頼してください。サブフローの JSON が不足していることが、実行時に [byos] no handler for screen "..." エラーが発生する最大の原因です。

ワークフロー

  1. ユーザーからフロー JSON を集めます。 エージェントは Descope コンソールにアクセスできないため、ユーザーがエクスポートして貼り付けるか、パスを指定する必要があります。メインフローと、それが呼び出すすべてのサブフロー(arguments.flowId を持つ LoadSubflow アクション)を用意してください。これには、logged-in アクションの後に実行される認証後の権限昇格サブフロー(例:add-passkeys)も含まれます。実行時にはすでにユーザーが認証済みのため、見落としやすいので注意してください。メインフローしか提供されていない場合は、フロー内の LoadSubflow アクションをスキャンし、先に各サブフローをエクスポートするようユーザーに依頼してください。サブフローの JSON が不足していると、画面を検出できず、実行時に [byos] no handler for screen … が発生します。

  2. このディレクトリにある parse-flow.mjs で解析します。 node parse-flow.mjs <path/to/flow.json> を実行すると、すべての画面タスクについて、screenName、allInputKeys、contextKeys、次のルール(interactionId → taskId)、UI ノードの概要(入力の name 属性、ボタンのラベル)、サブフローの呼び出しが出力されます。

  3. 画面名のマップを作成します。 すべてのフローを通じて、一意な画面名ごとに React コンポーネントを 1 つ作成します。名前の衝突に注意してください。複数のタスクで同じ名前が使われることがよくあります(例:「Welcome Screen」がメール入力とパスワード入力の両方に使われる)。名前が衝突する場合は、state.context.form.* のヒューリスティックに基づいて振り分けるルーターコンポーネントを作成してください(「注意点」→「画面名の衝突」を参照)。

  4. 各コンポーネントを作成します。 すべての BYOS 画面で行うことは、次の 4 つです。

    • 表示用にコンテキストを読み取る:state.context.sentTo.maskedEmail、state.context.form.email など
    • 入力を form に集める(setForm({ ...form, key: value }) を使用)
    • ボタンのクリック時に state.next(interactionId, payload) を呼び出す
    • Descope がエラーを報告したときに state.error?.text を表示する
  5. フローを接続します。 Descope React SDK は、BYOS のエントリポイントとして @descope/react-sdk の <Descope flowId="..." onScreenUpdate={handler} /> を提供します。onScreenUpdate は (screenName, state, next) を受け取り、対応する画面コンポーネントに処理を振り分けます。よく使われるパターンは、byosScreens マップを受け取り、内部で振り分けを行う薄い FlowOrByos ラッパーを作成することですが、FlowOrByos は SDK のエクスポートではなく、自分で作成する必要があります。エントリポイントにインスタンスを 1 つマウントしてください。onSuccess では、認証状態のキャッシュを無効化してください(「注意点」→「セッションの固定化」を参照)。

  6. エンドツーエンドで検証します。 フローがサポートするすべてのユーザージャーニーを確認してください。コンソールで [byos] no handler for screen "..." が表示される画面は、マップに登録されていません。

重要なルール

注記: 以下のルールのうち、いくつか(ネストされた <form> タグの動作、WebAuthn のセレモニーの所有者、ctxKey による事前入力、componentsConditions のフィールド名、E.164 形式以外の番号のサイレントな拒否)は、実際の BYOS セッションから経験的に導き出したもので、Descope の公式ドキュメントには明記されていません。ただし、本番環境で検証され、フローの JSON 構造との照合も済んでいます。

  • フォームのキーは入力ノードの name プロパティと一致します。 allInputKeys や inputsMetadata.key ではありません。Task 40 の Set Password 入力は name="newPassword" ですが、allInputKeys: ["newPassword_noPolicyOverrides"] です。{ newPassword: "..." } を送信してください。
  • state.next のペイロードには、現在の画面の出力だけを送信してください。 サブフローの境界を越えて累積したフォーム全体を展開すると、コンテキストが汚染され、気づかないまま失敗することがあります。
  • <form> タグをネストしないでください。 Descope のウェブコンポーネントは子要素を <form> でラップします。<div> と onClick、onKeyDown={makeEnterHandler(submit)} を使用してください。
  • OAuth ボタンでは、インタラクション ID だけでなく、ペイロードに provider を設定してください({ provider: 'google' })。
  • 電話番号は E.164 形式にする必要があります。 E.164 形式でない番号は SMS コネクターによってサイレントに拒否されます。フローは完了しますが、セッションは作成されません。
  • onSuccess で認証キャッシュを無効化してください。 セッションのアップグレード(匿名 → 検証済み)では同じ sub が再利用されることがあり、userId をキーとするキャッシュは自動更新されません。
  • 画面 JSON から componentsConditions を読み取り、表示/非表示のルールを反映してください(例:unauthUser.verifiedPhone が false の場合、「コードでサインイン」を非表示にします)。
  • WebAuthn フロー:セレモニーは SDK が実行します。 インタラクションが WebAuthn アクションタスク(webauthn-update-user-start/finish、Sign Up or In / Passkeys)にルーティングされたら、state.next(interactionId) を呼び出すだけにしてください。BYOS から navigator.credentials.create/get を呼び出してはいけません。@simplewebauthn/browser、startAuthentication、startRegistration、または WebAuthn ヘルパーライブラリをインポートしてはいけません。Descope SDK がセレモニーの両方の段階をすでに実行します。SDK は onScreenUpdate でアクションを検知し、セレモニーを自ら実行します。キャンセルやエラーは state.error.text に表示されます。
  • 事前入力には入力の ctxKey を読み取ってください。 入力ノードに props.ctxKey="someKey" がある場合は、useEffect でコンテキスト値をキーにして初回レンダリング時に state.context[someKey] を form[name] に設定してください(ローカルのフィールドが空の場合のみ設定します)。
  • Device Not Supported の分岐を反映してください。 WebAuthn フローでは、deviceInfo.webAuthnSupport に基づいて分岐することがよくあります。非対応の分岐は実際の画面タスクです。BYOS コンポーネントを用意しないと、プロモーションの途中でホスト型ウィジェットが表示されます。

共有画面名のヒューリスティック

2 つのタスクで同じ画面名を使用している場合、曖昧さを解消する手がかりを推測してはいけません。それぞれの経路をたどり、「フォームフィールドの蓄積」テーブルを作成する必要があります。

手順(必須 — 省略すると、気づきにくいバグが発生します):

  1. 重複する画面名を使用する各タスクについて、親フローのエントリーポイントに到達するまで next.rules をさかのぼります。
  2. 各経路について、途中にあるすべての画面タスクと、その allInputKeys を列挙します。これらが、その経路でユーザーが入力するフォームフィールドです。
  3. ちょうど一方の経路だけで値が設定されるフォームフィールドを見つけます。
  4. ヒューリスティックとして Boolean(state?.context?.form?.<that field>) を使用します。

**よくある間違い:**両方の経路を最後までたどらなかったため、両方の経路で値が設定されるフィールドを選んでしまうことです。

以下の例は特定のフローセットに基づいています。タスク ID、画面名、サブフロー名は異なります。これらは手法を示すものであり、必ず自分のフローに対して parse-flow.mjs を実行してください。

例:「Verify OTP」が sign-in-sms-otp と progressive-profile-sms で共有されている場合、form.phone がよさそうに見えます。しかし、どちらのフローでも、「Verify OTP」に到達する前に電話番号入力画面で電話番号を収集しています。代わりに form.password を選んでください。sign-in-sms-otp の経路は、form.password に値を書き込む親フローのパスワード画面から開始されます。一方、progressive-profile-sms の経路はマジックリンクの後から始まり、パスワードは入力されていません。

参考用のヒューリスティック(このスキルの元になったフローセットの例であり、すべてのケースに当てはまるわけではありません):

重複この画面の前に経路 A で収集する項目この画面の前に経路 B で収集する項目手がかり
"Welcome Screen"(メールアドレス入力 vs パスワード入力)—emailstate.context.form.email
"Verify OTP"(sign-in-sms-otp vs progressive-profile-sms)email、password、phoneemail、phonestate.context.form.password(phone ではありません。両方の経路で収集されます!)

3 つの経路が重複する例(メインフローと 2 つのサブフローで共有される Magic Link Sent):

経路この画面の前に収集する項目
メインフローemailパスワードなし
新規ユーザーのサブフローemail、password、fullNameパスワードと fullName
既存ユーザーのサブフローemail、passwordパスワードあり、fullName なし

述語:supportsChooseOther = !hasPassword || hasFullName。

選択したヒューリスティックと完全なトレースを、ルーターコンポーネントの先頭に記録してください。Descope コンソールで画面名を変更できる場合は、変更を依頼してください。ヒューリスティックよりも、一意の名前を使うほうが常に優れています。

**実際に成功したケース:**2 つの User Information タスク(verify-email-magic-link の既存ユーザー用と新規ユーザー用の分岐)は、コンソールで User Information - Unverified - Email Only と User Information - Unverified - Email and Name に変更されました(ヒューリスティックでは fullName の出力有無を判定できました)。名前の変更はより簡単で、わかりやすく、フローを編集しても影響を受けません。基本的には名前を変更してください。

注意点のリファレンス

失敗事例の完全な一覧(問題 → 症状 → 修正方法):references/gotchas.md を参照してください。気づかない失敗の原因を BYOS コードのせいにする前に、このファイルを確認してください。「動かない」という症状の多くは、既知の注意点に該当します。

検証

フローがサポートするユーザージャーニーを、それぞれエンドツーエンドでテストしてください。

  • フロー JSON では、メインフローが task N から開始し、action=logged-in の task M で終了していますか?
  • フロー内のすべての画面タスクに BYOS コンポーネントがあるか、ホスト型にフォールバックする判断が明示されていますか?
  • onSuccess が呼び出され、アプリは想定される認証済み状態に到達していますか(未認証のまま止まっていませんか)?
  • onError が呼び出され、Descope のエラーメッセージが表示されますか(何も表示されずに失敗していませんか)?
  • 認証後のプロモーション用サブフロー(パスキーへの移行など)を、Device Not Supported 分岐を含めてエンドツーエンドで確認しましたか?
  • WebAuthn 画面では state.next(interaction) のみが呼び出されていますか。BYOS コードで navigator.credentials.* を呼び出していませんか?

一連のユーザージャーニー全体を実行した際に、コンソールに [byos] no handler for screen "..." の警告が表示されないことを確認してください。

パーサー

parse-flow.mjs(このディレクトリ内)— Node スクリプトです。入力:フロー JSON のパス(複数可)。出力:各フローについて、画面タスク、その入力、終了時のインタラクション、UI ノード名、サブフローローダーを示す概要テーブルです。実行して出力を画面マップのコメントヘッダーに貼り付ければ、BYOS コンポーネントに必要なすべての定数の信頼できる情報源になります。

注意すべき点

次のような状況に気づいたら:

  • フロー JSON で確認していないインタラクション ID をハードコーディングしている — 中断して、まず解析してください
  • サブフロー境界をまたいで { ...form } を state.next に展開している — 出力だけを送信してください
  • 各フローのルールを確認せず、すべての「OTP を検証」バリエーションに同じボタンを追加している — コンテキストに応じて制御してください
  • BYOS コンポーネント内に <form> タグを書いている — <div> を使ってください
  • onSuccess で認証キャッシュを無効化せずに navigate() を呼び出している — 先に無効化してください
  • BYOS のクリックハンドラーから navigator.credentials.create/get を呼び出している — SDK が実行するため、インタラクションを呼び出すだけで十分です
  • @simplewebauthn/browser や WebAuthn ヘルパーをインポートしている — 同じ落とし穴です。サードパーティライブラリ版であり、SDK が実行します
  • 「その時点ではユーザーはすでにログインしているから」とサブフローのエクスポートを省略している — 認証後の昇格サブフローは実際の画面をレンダリングします

いずれの場合も、作業を止めて references/gotchas.md を読み直し、フロー JSON と照合してください。

参考資料

  • references/byos-component-patterns.md — 推奨されるコードパターン:コアの接続(onScreenUpdate → ByosState)、画面ルーター、画面のひな形、メール、OTP、電話、OAuth、衝突ルーター、ctxKey の事前入力に関する完全な例です。セットアップを始めるときは、まずこちらを確認してください。
  • references/gotchas.md — 問題の一覧:実際に発生した BYOS の失敗モードを 19 件、それぞれ症状 → 根本原因 → 修正方法の順で掲載しています。リリース前チェックリストも含まれています。
  • parse-flow.mjs — Node パーサー:node parse-flow.mjs <flow.json> [more.json ...]。画面タスク、実際のフォームキーの name プロパティ、インタラクション ID、サブフローローダー、衝突に関する警告を出力します。コンポーネントを書く前に実行してください。

コメント