> ## Documentation Index
> Fetch the complete documentation index at: https://docs.siftstack.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Identity Provider (IdP) settings

> Settings, options, and behaviors for Identity Provider (IdP) integration in Sift.

export const MintTable = ({columns = [], rows = [], columnWidths = []}) => {
  const pushTextWithLineBreaks = (parts, text, keyBase) => {
    const segments = String(text).split(/\\n|\n/);
    segments.forEach((segment, idx) => {
      if (segment) {
        parts.push(<span key={`${keyBase}-text-${idx}`}>{segment}</span>);
      }
      if (idx < segments.length - 1) {
        parts.push(<br key={`${keyBase}-br-${idx}`} />);
      }
    });
  };
  const parseMarkdown = text => {
    if (text === null || text === undefined) return "";
    const str = String(text);
    const parts = [];
    let lastIndex = 0;
    const pattern = /(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*|\[([^\]]+)\]\(([^)]+)\))/g;
    let match;
    while (true) {
      match = pattern.exec(str);
      if (match === null) {
        break;
      }
      if (match.index > lastIndex) {
        pushTextWithLineBreaks(parts, str.substring(lastIndex, match.index), `before-${lastIndex}`);
      }
      const fullMatch = match[0];
      if (fullMatch.startsWith("`") && fullMatch.endsWith("`")) {
        parts.push(<code key={match.index}>{fullMatch.slice(1, -1)}</code>);
      } else if (fullMatch.startsWith("**") && fullMatch.endsWith("**")) {
        parts.push(<strong key={match.index}>{fullMatch.slice(2, -2)}</strong>);
      } else if (fullMatch.startsWith("*") && fullMatch.endsWith("*")) {
        parts.push(<em key={match.index}>{fullMatch.slice(1, -1)}</em>);
      } else if (fullMatch.startsWith("[")) {
        const linkText = match[2];
        const linkUrl = match[3];
        parts.push(<a key={match.index} href={linkUrl} className="text-black-600 dark:text-black-400">
            {linkText}
          </a>);
      }
      lastIndex = pattern.lastIndex;
    }
    if (lastIndex < str.length) {
      pushTextWithLineBreaks(parts, str.substring(lastIndex), `tail-${lastIndex}`);
    }
    if (parts.length > 0) {
      return parts;
    }
    const plainParts = [];
    pushTextWithLineBreaks(plainParts, str, "plain");
    return plainParts.length ? plainParts : str;
  };
  const safeColumns = Array.isArray(columns) ? columns : [];
  const safeRows = Array.isArray(rows) ? rows : [];
  const safeColumnWidths = Array.isArray(columnWidths) ? columnWidths : [];
  const hasColumnWidths = safeColumnWidths.some(w => w !== null && w !== undefined && w !== "");
  const toCssWidth = width => typeof width === "number" ? `${width}px` : String(width);
  const getColumnStyle = idx => {
    const rawWidth = safeColumnWidths[idx];
    if (rawWidth === null || rawWidth === undefined || rawWidth === "") {
      return undefined;
    }
    const width = toCssWidth(rawWidth);
    return {
      width,
      minWidth: width
    };
  };
  const containerStyle = hasColumnWidths ? undefined : {
    overflowX: "auto"
  };
  const tableStyle = hasColumnWidths ? {
    tableLayout: "fixed",
    width: "100%"
  } : {
    width: "max-content",
    minWidth: "100%"
  };
  if (!Array.isArray(columns) || !Array.isArray(rows) || !Array.isArray(columnWidths)) {
    console.warn("MintTable received invalid props:", {
      columns,
      rows,
      columnWidths
    });
  }
  if (!safeColumns.length && !safeRows.length) {
    return null;
  }
  return <div className="mint-table-container" style={containerStyle}>
      <table style={tableStyle}>
        {hasColumnWidths && <colgroup>
            {safeColumns.map((_, idx) => {
    const style = getColumnStyle(idx);
    return <col key={idx} style={style} />;
  })}
          </colgroup>}
        <thead>
          <tr>
            {safeColumns.map((col, idx) => <th key={idx} className="text-left" style={getColumnStyle(idx)}>
                <b>{parseMarkdown(col)}</b>
              </th>)}
          </tr>
        </thead>
        <tbody>
          {safeRows.map((row, rIdx) => {
    const safeRow = Array.isArray(row) ? row : [];
    return <tr key={rIdx}>
                {safeRow.map((cell, cIdx) => <td key={cIdx} style={getColumnStyle(cIdx)}>
                    {parseMarkdown(cell)}
                  </td>)}
              </tr>;
  })}
        </tbody>
      </table>
    </div>;
};

Sift supports the integration of external Identity Providers (IdPs) to manage users and groups. When changes occur in the IdP, such as creating, deleting, or renaming users or groups, or modifying group memberships, those changes are synchronized to Sift.

## Supported Identity Providers

Sift supports Identity Providers that use push-based provisioning to create and update users and groups in Sift based on the current configuration in the IdP. The following IdPs support push-based provisioning and have been verified to work with Sift:

<MintTable
  columns={['Identity Provider', 'Notes']}
  columnWidths={['30%', '70%']}
  rows={[
['Microsoft Entra ID', 'Supported via push-based provisioning.'],
['Oracle Identity and Access Management (Oracle IAM)', 'When entering the SCIM endpoint URL in Oracle IAM, split it into two parts: enter everything before `.com` excluding the protocol in the **Host Name** field, and enter everything after `.com` (including the initial `/`) in the **Base URI** field. \n\nFor example, if the SCIM Server URL is `https://sift.keycloak.com/realms/ScimExample/scim/v2`, the **Host Name** is `sift.keycloak.com` and the **Base URI** is `/realms/ScimExample/scim/v2`.'],
]}
/>

<Warning>
  **Pull-based provisioning**: Sift only supports IdPs that use push provisioning. IdPs that require pull-based provisioning, such as Google Workspace, require custom integration work.
</Warning>

## SCIM provisioning

SCIM provisioning in Sift synchronizes users and groups from your Identity Provider into Sift. When the IdP creates, deletes, or updates a user, a group, or a group's membership, those changes are applied in Sift during the next scheduled sync.

<MintTable
  columns={['Setting', 'Description']}
  columnWidths={['30%', '70%']}
  rows={[
['SCIM endpoint URL', 'The URL used by your IdP to send provisioning requests to Sift. Available from the **Users** page in Sift.'],
['Access token', 'The token used to authenticate SCIM provisioning requests from your IdP to Sift. Available from the **Users** page in Sift.'],
]}
/>

## Behavior

The following table describes known constraints and behaviors to be aware of when working with Identity Providers in Sift.

<MintTable
  columns={['Limitation', 'Description']}
  columnWidths={['25%', '75%']}
  rows={[
['Push provisioning only', 'Sift only supports IdPs that use push-based provisioning. IdPs that require pull-based provisioning, such as Google Workspace, require custom integration work.'],
['Automatic sync', 'Sift runs an automatic sync every 24 hours.'],
['Manual sync', 'Administrators can trigger a manual sync at any time to apply changes sooner.'],
['External groups', 'Groups synchronized from an IdP are called external groups in Sift. Membership and group attributes remain controlled by the IdP. Permissions for these groups can be changed in Sift.'],
]}
/>
