edit-embed について
edit-embedは、編集可能なDomoコンテンツを埋め込む開発スキルです。Identity BrokerのJWT認証、ロール、インスタンス振り分け、iframe設定を説明します。
主な機能
- 01Identity Broker認証用JWTの作成
- 02属性に基づくDomoインスタンスへの振り分け
- 03Domoロールの割り当て
- 04iframeでの編集セッション埋め込み
- 05Domoページへのディープリンク
活用シーン
- 01外部ユーザーによる埋め込みダッシュボードの編集
- 02テナントユーザーのDomoインスタンスへの振り分け
- 03ロールに応じた編集権限の付与
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ stahura/ domo-ai-vibe-rules --skill edit-embedインストール後、Claude Code で/edit-embedを入力して読み込まれたか確認します。
同梱ファイルreferences/identity-broker.md
詳細
Domo 埋め込み編集エクスペリエンス
外部ユーザーが、埋め込み iframe を通じて Domo のコンテンツ(ダッシュボード、カード、アラート、レポート、データソース)を作成、編集、保存、共有できるようにします。JWT 認証で Domo Identity Broker を使用します。読み取り専用の埋め込みとは根本的に異なります。
読み取り専用の埋め込みについては、programmatic-filters を参照してください。クライアント側のフィルタリングについては、jsapi-filters を参照してください。
仕組み
- サーバーがユーザーを認証し、ユーザーの ID、ロール、ルーティング属性を含む JWT を作成します
- JWT は、Domo から提供された共有シークレットで署名されます
- サーバーが編集 URL を構築します:
{IDP_URL}/jwt?token={jwt_token} - クライアントは、URL を iframe の
srcとして直接描画します(POST フォームは不要です) - Identity Broker が JWT を検証し、ロールに基づいてユーザーを正しい Domo インスタンスにルーティングします
主な違い:読み取り専用=OAuth → 埋め込みトークン → POST フォーム。編集=JWT → Identity Broker URL → iframe の src。
前提条件
Domo の CSM と連携して Identity Broker を設定してください。次の情報を提供します:Domo インスタンスの URL、認証方式(JWT)、ルーティング属性キー、属性とインスタンスのマッピング。
次の情報が提供されます:Identity Broker の URL、JWT 署名シークレット(UUID)、属性キーの設定。
必須の環境変数: IDP_URL(Broker の URL)、JWT_SECRET(署名シークレット)、KEY_ATTRIBUTE(ルーティング属性名)。
Domo Identity Broker
ユーザーを認証(JWT 署名を検証)し、マッピング属性に基づいて正しい Domo インスタンスにルーティングします。SAML2、OIDC、JWT、OAuth2 をサポートしています。埋め込みでは JWT が最も一般的です。
インスタンスのマッピング
各マッピング属性値は、Domo インスタンスに対応します:
| 属性値 | Domo インスタンス |
|---|---|
acme-corp | acme.domo.com |
globex | globex.domo.com |
initech | initech.domo.com |
CSM 経由で設定します(Web フォームのデータセットまたは Excel)。カンマ区切りの値を使用すると、複数のインスタンスにルーティングされます。
JWT トークンの構造
必須フィールド
| フィールド | 型 | 説明 |
|---|---|---|
sub | string | ユーザーの識別子。通常はメールアドレスです |
exp | number | 有効期限(EPOCH タイムスタンプ)。短く設定してください(5 分を推奨) |
jti | string | 一意のトークン識別子。リプレイ攻撃を防ぐため、UUID v4 を使用してください |
{KEY_ATTRIBUTE} | string or string[] | ユーザーを Domo インスタンスに対応付けるルーティング属性 |
一般的な任意フィールド
| フィールド | 型 | 説明 |
|---|---|---|
name | string | Domo でのユーザーの表示名 |
email | string | ユーザーのメールアドレス |
role | string | Domo のロール:Admin、Privileged、Editor、または Participant |
employee_id | string | 従業員識別子 |
title | string | 職位 |
department | string | 部門名 |
location | string | 所在地 |
phone | string | 電話番号 |
locale | string | ロケールの設定 |
timezone | string | タイムゾーンの設定 |
groups | string[] | Domo 内のグループ割り当て |
Domo のロール
| ロール | 権限 |
|---|---|
Admin | フルアクセス — ユーザー、データ、コンテンツ、設定を管理します |
Privileged | ダッシュボード、カード、データフローの作成・編集、データソースの管理 |
Editor | ダッシュボードとカードの作成・編集、データソースへのアクセスは制限されます |
Participant | 共有コンテンツのみの表示と操作(省略時のデフォルト) |
外部ユーザーのほとんどには、Editor または Participant を設定してください。
サーバーサイドの実装
ステップ 1:JWT トークンを作成する
Node.js / TypeScript:
import jwt from "jsonwebtoken";
import { v4 as uuidv4 } from "uuid";
function createEditToken(user: {
username: string;
email: string;
domoRole?: string;
mappingValue?: string | string[];
}) {
const jwtBody: Record<string, unknown> = {
sub: user.username,
name: user.username,
email: user.email,
role: user.domoRole || "Participant",
jti: uuidv4(),
};
// Add the routing attribute for instance mapping
const keyAttribute = process.env.KEY_ATTRIBUTE;
if (keyAttribute && user.mappingValue) {
jwtBody[keyAttribute] = user.mappingValue;
}
return jwt.sign(jwtBody, process.env.JWT_SECRET!, {
expiresIn: "5m",
algorithm: "HS256",
});
}
Python:
import jwt
import uuid
import time
import os
def create_edit_token(user):
payload = {
'sub': user['username'],
'name': user['username'],
'email': user['email'],
'role': user.get('domo_role', 'Participant'),
'jti': str(uuid.uuid4()),
'exp': int(time.time()) + 300 # 5 minutes
}
key_attribute = os.environ.get('KEY_ATTRIBUTE')
if key_attribute and user.get('mapping_value'):
payload[key_attribute] = user['mapping_value']
return jwt.encode(payload, os.environ['JWT_SECRET'], algorithm='HS256')
ステップ 2:編集 URL を構築する
const editUrl = `${process.env.IDP_URL}/jwt?token=${editToken}`;
特定のページへのディープリンクも設定できます。
const editUrl = `${process.env.IDP_URL}/jwt?token=${editToken}&destination=/page/${pageId}`;
ステップ 3:API ルートを作成する
Next.js App Router:
// app/api/editembed/route.ts
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import jwt from "jsonwebtoken";
import { v4 as uuidv4 } from "uuid";
export async function POST(req: NextRequest) {
try {
const { embedID } = await req.json();
// Authenticate the user against your own auth system
const token = req.cookies.get("token")?.value;
if (!token) {
return NextResponse.json(
{ message: "Unauthorized: Please log in" },
{ status: 401 },
);
}
const user = await verifyAndGetUser(token);
if (!user) {
return NextResponse.json({ message: "User not found" }, { status: 404 });
}
// Handle comma-separated mapping values
let mappingValue = user.mappingValue;
if (typeof mappingValue === "string" && mappingValue.includes(",")) {
mappingValue = mappingValue.split(",").map((s: string) => s.trim());
}
// Build the JWT payload
const jwtBody: Record<string, unknown> = {
sub: user.username,
name: user.username,
role: user.domoRole || "Participant",
email: user.email,
jti: uuidv4(),
};
const keyAttr = process.env.KEY_ATTRIBUTE;
if (keyAttr && mappingValue) {
jwtBody[keyAttr] = mappingValue;
}
// Sign the token
const editToken = jwt.sign(jwtBody, process.env.JWT_SECRET || "", {
expiresIn: "5m",
});
// Return the Identity Broker URL
const editUrl = `${process.env.IDP_URL}/jwt?token=${editToken}`;
return NextResponse.json(editUrl);
} catch (error) {
console.error("Error in /api/editembed:", error);
return NextResponse.json(
{ message: "Server error occurred" },
{ status: 500 },
);
}
}
Express の例:
app.post("/api/editembed", authenticateUser, (req, res) => {
const user = req.user;
let mappingValue = user.mappingValue;
if (typeof mappingValue === "string" && mappingValue.includes(",")) {
mappingValue = mappingValue.split(",").map((s) => s.trim());
}
const jwtBody = {
sub: user.username,
name: user.username,
role: user.domoRole || "Participant",
email: user.email,
jti: uuidv4(),
};
const keyAttr = process.env.KEY_ATTRIBUTE;
if (keyAttr && mappingValue) {
jwtBody[keyAttr] = mappingValue;
}
const editToken = jwt.sign(jwtBody, process.env.JWT_SECRET, {
expiresIn: "5m",
});
const editUrl = `${process.env.IDP_URL}/jwt?token=${editToken}`;
res.json(editUrl);
});
ステップ 4:iframe に表示する
編集用埋め込みは iframe の src から直接読み込まれます(読み取り専用埋め込みのような POST フォームは使用しません)。
function EditEmbed({ editUrl }: { editUrl: string }) {
return (
<iframe
src={editUrl}
style={{ width: "100%", height: "100%", border: "none" }}
allow="fullscreen"
/>
);
}
読み取り専用モードと編集モードの処理
アプリが読み取り専用モードと編集モードの両方に対応している場合、埋め込みコンポーネントで両方のフローを処理する必要があります。
function EmbedDashboard({ embedID }: { embedID: string }) {
const [embedURL, setEmbedURL] = useState<string | null>(null);
const [embedToken, setEmbedToken] = useState<string | null>(null);
const isEditMode = embedID === "edit";
useEffect(() => {
const endpoint = isEditMode ? "/api/editembed" : "/api/getembedtoken";
fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ embedID }),
credentials: "include",
})
.then((res) => res.json())
.then((data) => {
if (isEditMode) {
setEmbedURL(data); // Edit returns a direct URL string
setEmbedToken(null);
} else {
setEmbedURL(data.embedUrl); // Read-only returns { embedUrl, embedToken }
setEmbedToken(data.embedToken);
}
});
}, [embedID, isEditMode]);
// Edit mode: direct iframe src
if (isEditMode && embedURL) {
return (
<iframe
src={embedURL}
style={{ width: "100%", height: "100%", border: "none" }}
/>
);
}
// Read-only mode: POST form submission to iframe
// (handle embedToken + form submission as in programmatic-filters skill)
}
編集用埋め込みのユーザー管理
主なユーザープロパティ
| プロパティ | 用途 | 例 |
|---|---|---|
domoRole | 編集権限を制御します | 'Editor'、'Participant' |
mappingValue | ユーザーを適切な Domo インスタンスにルーティングします | 'acme-corp' または ['acme-corp', 'globex'] |
email | Domo のユーザー ID に必要です | '[email protected]' |
複数インスタンスを利用するユーザー
ユーザーが複数の Domo インスタンスにアクセスする必要がある場合は、mappingValue をカンマ区切りの文字列または配列として保存してください。
// Single instance
user.mappingValue = "acme-corp";
// Multiple instances
user.mappingValue = "acme-corp, globex";
// or
user.mappingValue = ["acme-corp", "globex"];
JWT を作成する際は、両方の形式を処理してください。
let mappingValue = user.mappingValue;
if (typeof mappingValue === "string" && mappingValue.includes(",")) {
mappingValue = mappingValue.split(",").map((s) => s.trim());
}
ディープリンク
const editUrl = `${IDP_URL}/jwt?token=${editToken}&destination=/page/${pageId}`;
const editUrl = `${IDP_URL}/jwt?token=${editToken}&destination=/kpicard/${cardId}`;
注意点とベストプラクティス
- トークンの有効期限: JWT の有効期間は短く設定してください(5 分を推奨)。JWT は初回認証にのみ使用され、その後もセッションは継続します。
- 署名アルゴリズム:
HS256を使用してください。Domo の Identity Broker ではこれが必要です。 - JTI の一意性: 常に UUID v4 を使用してください。Domo は、再利用された JTI 値を拒否する場合があります(リプレイ攻撃対策)。
- マッピング値:
KEY_ATTRIBUTEの名前は、CSM と合意した値(customer、keyAttribute、tenant)を使用します。値は Domo のインスタンスマッピングと完全に一致している必要があります。 - シークレット管理:
JWT_SECRETをクライアント側に公開しないでください。環境変数またはシークレットマネージャーを使用してください。 - セキュリティモデル: 読み取り専用埋め込みでは、フィルターを使って特定のダッシュボードに範囲を限定します。編集用埋め込みでは、ロールに応じた完全な Domo セッションが付与されるため、ロールの割り当ては慎重に行ってください。
- インスタンスマッピングの変更: 安定した値(変更される可能性のある会社名ではなく、テナント ID など)を使用してください。
環境変数リファレンス
| 変数 | 説明 | 例 |
|---|---|---|
IDP_URL | Identity Broker の URL | https://yourcompany.identity.domo.com |
JWT_SECRET | 共有署名シークレット(UUID 形式) | aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeee |
KEY_ATTRIBUTE | インスタンスのルーティングに使う属性キー | keyAttribute、customer、tenant |
TypeScript 型定義
type DomoRole = "Admin" | "Privileged" | "Editor" | "Participant";
interface EditEmbedUser {
username: string;
email: string;
domoRole?: DomoRole;
mappingValue?: string | string[];
}
interface EditJwtPayload {
sub: string;
name?: string;
email?: string;
role?: DomoRole;
jti: string;
exp?: number;
[keyAttribute: string]: unknown; // dynamic routing attribute
}
クイックリファレンス
Identity Broker の設定とインスタンスマッピング管理の詳細については、references/identity-broker.md を参照してください。