mcpField
E
edit-embed

edit-embed

github.com/stahuraDevelopment24190 installsUpdated 26w ago
Repository

About edit-embed

edit-embed is a developer skill for embedding Domo content with editing capabilities. It explains the Identity Broker JWT flow, role-based access, instance routing, and iframe setup.

Key features

  1. 01Creates JWTs for Identity Broker authentication
  2. 02Routes users to Domo instances by attributes
  3. 03Supports Domo role assignment
  4. 04Embeds edit sessions in iframes
  5. 05Provides deep links to Domo pages

Use cases

  1. 01Letting external users edit embedded dashboards
  2. 02Routing tenant users to their Domo instances
  3. 03Giving users role-specific editing access

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/stahura/domo-ai-vibe-rules --skill edit-embed

After installing, type/edit-embedin Claude Code to confirm it loaded.

Bundled filesreferences/identity-broker.md

Details

From the project README

Domo Embedded Edit Experience

Let external users create, edit, save, and share Domo content (dashboards, cards, alerts, reports, data sources) through an embedded iframe. Uses the Domo Identity Broker with JWT auth — fundamentally different from read-only embeds.

For read-only embeds, see programmatic-filters. For client-side filtering, see jsapi-filters.

How It Works

  1. Your server authenticates the user and creates a JWT with identity, role, and routing attributes
  2. JWT is signed with a shared secret from Domo
  3. Server constructs edit URL: {IDP_URL}/jwt?token={jwt_token}
  4. Client renders URL directly as iframe src (no POST form needed)
  5. Identity Broker validates JWT, routes user to correct Domo instance by role

Key difference: read-only = OAuth → embed token → POST form. Edit = JWT → Identity Broker URL → iframe src.

Prerequisites

Work with your Domo CSM to set up the Identity Broker. Provide: your Domo instance URL, auth method (JWT), routing attribute key, and attribute-to-instance mappings.

You receive: Identity Broker URL, JWT signing secret (UUID), and attribute key config.

Required env vars: IDP_URL (Broker URL), JWT_SECRET (signing secret), KEY_ATTRIBUTE (routing attribute name).


The Domo Identity Broker

Authenticates users (validates JWT signature) and routes them to the correct Domo instance based on the mapping attribute. Supports SAML2, OIDC, JWT, OAuth2 — JWT is most common for embeds.

Instance Mapping

Each mapping attribute value corresponds to a Domo instance:

Attribute ValueDomo Instance
acme-corpacme.domo.com
globexglobex.domo.com
initechinitech.domo.com

Configured via your CSM (webform dataset or Excel). Comma-separated values route to multiple instances.


JWT Token Structure

Required Fields
FieldTypeDescription
substringThe user's identifier — typically their email address
expnumberExpiration time (EPOCH timestamp). Keep this short (5 minutes recommended)
jtistringUnique token identifier. Use a UUID v4 to prevent replay attacks
{KEY_ATTRIBUTE}string or string[]The routing attribute that maps the user to a Domo instance
Common Optional Fields
FieldTypeDescription
namestringDisplay name for the user in Domo
emailstringUser's email address
rolestringDomo role: Admin, Privileged, Editor, or Participant
employee_idstringEmployee identifier
titlestringJob title
departmentstringDepartment name
locationstringLocation
phonestringPhone number
localestringLocale preference
timezonestringTimezone preference
groupsstring[]Group assignments within Domo
Domo Roles
RoleCapabilities
AdminFull access — manage users, data, content, and settings
PrivilegedCreate/edit dashboards, cards, dataflows; manage data sources
EditorCreate/edit dashboards and cards; limited data source access
ParticipantView and interact with shared content only (default if omitted)

Most external users should be Editor or Participant.


Server-Side Implementation

Step 1: Create the JWT Token

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')
Step 2: Construct the Edit URL
const editUrl = `${process.env.IDP_URL}/jwt?token=${editToken}`;

Optionally deep-link to a specific page:

const editUrl = `${process.env.IDP_URL}/jwt?token=${editToken}&destination=/page/${pageId}`;
Step 3: Build the API Route

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 example:

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);
});
Step 4: Render in an Iframe

Edit embeds load directly via iframe src (no POST form like read-only embeds):

function EditEmbed({ editUrl }: { editUrl: string }) {
  return (
    <iframe
      src={editUrl}
      style={{ width: "100%", height: "100%", border: "none" }}
      allow="fullscreen"
    />
  );
}

Handling Read-Only vs Edit Mode

If your app supports both read-only and edit modes, your embed component needs to handle both flows:

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)
}

User Management for Edit Embeds

Key User Properties
PropertyPurposeExample
domoRoleControls edit capabilities'Editor', 'Participant'
mappingValueRoutes user to correct Domo instance'acme-corp' or ['acme-corp', 'globex']
emailRequired for Domo user identity'[email protected]'
Multi-Instance Users

If a user needs access to multiple Domo instances, store their mappingValue as a comma-separated string or array:

// Single instance
user.mappingValue = "acme-corp";

// Multiple instances
user.mappingValue = "acme-corp, globex";
// or
user.mappingValue = ["acme-corp", "globex"];

When building the JWT, handle both formats:

let mappingValue = user.mappingValue;
if (typeof mappingValue === "string" && mappingValue.includes(",")) {
  mappingValue = mappingValue.split(",").map((s) => s.trim());
}

Deep Linking

const editUrl = `${IDP_URL}/jwt?token=${editToken}&destination=/page/${pageId}`;
const editUrl = `${IDP_URL}/jwt?token=${editToken}&destination=/kpicard/${cardId}`;

Gotchas and Best Practices

  • Token expiration: Keep JWTs short-lived (5 min recommended). Only used for initial auth — session persists after.
  • Signing algorithm: Use HS256. Domo expects this for the Identity Broker.
  • JTI uniqueness: Always UUID v4. Domo may reject reused JTI values (replay protection).
  • Mapping values: KEY_ATTRIBUTE name is whatever you agreed on with your CSM (customer, keyAttribute, tenant). Values must match Domo's instance mapping exactly.
  • Secret management: Never expose JWT_SECRET client-side. Use env vars or a secrets manager.
  • Security model: Read-only embeds scope to specific dashboards with filters. Edit embeds give a full Domo session per role — be conservative with role assignment.
  • Instance mapping changes: Use stable values (tenant IDs, not company names that might change).

Environment Variables Reference

VariableDescriptionExample
IDP_URLIdentity Broker URLhttps://yourcompany.identity.domo.com
JWT_SECRETShared signing secret (UUID format)aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeee
KEY_ATTRIBUTEAttribute key for instance routingkeyAttribute, customer, tenant

TypeScript Type Definitions

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
}

Quick Reference

Read references/identity-broker.md for additional details on Identity Broker configuration and instance mapping management.

Comments

Markdown supported