为 Fabric 应用配置 Fabric SSO 身份验证

为Fabric应用设置Fabric单一登录(SSO),以便用户可以通过Fabric门户使用Microsoft Entra ID登录。 本文介绍切换流,并演示如何为已部署的应用启用所需的配置和 SDK 集成。

先决条件

Fabric SSO 的工作原理

Fabric 单点登录 (SSO) 使用应用程序与 Fabric 门户之间基于 postMessage 的安全交接机制。 没有重定向或回调页面:

  1. 您的应用会在弹出窗口中打开 Fabric 门户,并注册一个 postMessage 监听器。
  2. 用户在Fabric门户中通过Microsoft Entra ID进行身份验证。
  3. Fabric扩展通过 window.opener.postMessage() 将移交代码发送回应用。
  4. SDK 交换 Rayfin 会话令牌的交接代码并创建会话。
  5. Fabric弹出窗口会自动关闭。

该流程通过 PKCE(代码交换证明密钥)、state 随机数和 postMessage 来源验证来保护,以防止授权码拦截和跨站点请求伪造。

启用Fabric身份验证

将Fabric身份验证配置添加到 rayfin/rayfin.yml 文件:

services:
  auth:
    enabled: true
    allowedRedirectUris:
      - http://localhost:5173
    fabric:
      enabled: true

对于已部署的应用程序,请重新部署以推送更新的设置:

npx rayfin up

对于已部署的应用,npx rayfin up 会将您已部署的应用回调 URL 添加到 allowedRedirectUris

安装Fabric身份验证提供程序(可选)

使用 npm create @microsoft/rayfin@latest 创建的项目已包含 @microsoft/rayfin-auth-provider-fabric。 仅当你要为尚未包含该软件包的项目添加 Fabric 身份验证时,才需要手动安装该软件包:

npm install @microsoft/rayfin-auth-provider-fabric

将登录和注册添加到应用

Fabric SSO 对登录和注册使用单个 API:ensureSignedInWithFabric()。 当用户首次登录时,Fabric会根据用户的Microsoft Entra ID标识自动为其预配 Rayfin 会话-没有单独的注册呼叫。 同一代码路径也适用于回访用户。

可以使用 VS Code 中的GitHub Copilot手动添加此代码或生成此代码。

手动添加登录

在用户手势处理程序中调用 ensureSignedInWithFabric()(例如,在按钮选择事件中):

import { RayfinClient } from '@microsoft/rayfin-client';
import { ensureSignedInWithFabric } from '@microsoft/rayfin-auth-provider-fabric';

const client = new RayfinClient({
  baseUrl: import.meta.env.VITE_RAYFIN_API_URL,
  publishableKey: import.meta.env.VITE_RAYFIN_PUBLISHABLE_KEY,
});

const fabricOptions = {
  workspaceId: import.meta.env.VITE_FABRIC_WORKSPACE_ID,
  projectId: import.meta.env.VITE_FABRIC_ITEM_ID,
  fabricPortalUrl: import.meta.env.VITE_FABRIC_PORTAL_URL,
  returnOrigin: window.location.origin,
};

async function handleSignIn() {
  // Signs in existing users and provisions new users on first sign-in.
  const session = await ensureSignedInWithFabric(client.auth, fabricOptions);
  if (session.isAuthenticated && session.user) {
    console.log('Signed in as:', session.user.email);
  }
}

必须从同步用户手势处理程序调用该函数,以避免弹出阻止程序。 在页面加载时调用它,或者在用户交互之前的异步调用链中调用它,都会触发浏览器的弹窗保护机制。

returnOrigin 必须是裸源(方案和主机,无路径),例如 https://app.contoso.com。 SDK 使用它来验证传入 postMessage 事件。

手动添加注销

调用 client.auth.signOut() 以结束会话并清除缓存的令牌:

async function handleSignOut() {
  await client.auth.signOut();
  console.log('Signed out');
}

订阅会话变更,以便在登录或注销完成时更新用户界面:

client.auth.onSessionChange((session) => {
  console.log('Session changed:', session?.isAuthenticated ? 'signed in' : 'signed out');
});

使用 GitHub Copilot 生成登录和注册

如果在 VS Code 中使用 GitHub Copilot,请在 Fabric Apps 项目中打开Copilot 对话助手,并使用此类提示来搭建身份验证代码的基架。 Copilot 遵循 Fabric VS Code 扩展中附带的 Rayfin 技能里的模式。

目标 示例 Copilot 提示词
添加登录按钮 Add a Sign in with Fabric button to my React app using ensureSignedInWithFabric from @microsoft/rayfin-auth-provider-fabric. Read workspaceId, projectId, and fabricPortalUrl from VITE_* env vars and set returnOrigin to window.location.origin.
添加注销按钮 Add a Sign out button that calls client.auth.signOut() and updates the UI when the session ends.
添加一个支持身份验证的 React Hook Create a useFabricAuth React hook that exposes session, signIn, signOut, and isAuthenticated, using ensureSignedInWithFabric and client.auth.onSessionChange.
支持嵌入式模式 Update my app's entry point to call initEmbeddedAuth on page load so users signed in through the Fabric portal don't have to click Sign in again.
限制路由 Wrap the /dashboard route so it calls ensureSignedInWithFabric before rendering and redirects unauthenticated users to a sign-in page.

Copilot生成代码后,请查看编辑并确保:

  • 调用 ensureSignedInWithFabric() 在用户手势处理程序(例如 onClick)内运行,而不是在页面加载时运行。
  • returnOrigin是一个裸源,并且与rayfin/rayfin.yml中的allowedRedirectUris条目之一匹配。
  • @microsoft/rayfin-auth-provider-fabric 导入(而不是从已弃用的回调辅助函数导入)。

在 Fabric iframe 中使用嵌入式模式

当应用在 Fabric iframe 内加载(例如,当用户从 Fabric 门户打开应用时),请使用嵌入模式而不是弹出窗口流:

  • 嵌入模式通过 postMessage 从父框架获取会话。
  • 它不会打开弹出窗口,不需要用户手势,因此可以安全地调用页面加载。
  • SDK 从 ?fabricEmbedded=true URL 中自动检测嵌入模式。 还可以通过在选项中设置 fabricEmbedded: true 来强制它。

在应用启动中提前调用 initEmbeddedAuth()

import { initEmbeddedAuth } from '@microsoft/rayfin-auth-provider-fabric';
import { client } from './lib/rayfin';

const session = await initEmbeddedAuth(client.auth, {
  workspaceId: import.meta.env.VITE_FABRIC_WORKSPACE_ID,
  projectId: import.meta.env.VITE_FABRIC_ITEM_ID,
  fabricPortalUrl: import.meta.env.VITE_FABRIC_PORTAL_URL,
  returnOrigin: window.location.origin,
});

if (session) {
  console.log('Signed in via embedded mode:', session.user?.email);
}

initEmbeddedAuth() 会在应用未以嵌入模式运行时返回 null,因此可以安全地无条件调用。 ensureSignedInWithFabric() 还会在回退到弹出窗口流之前自动尝试嵌入模式。

在 React 中使用Fabric身份验证

创建集成登录、注册和注销的自定义挂钩:

import { useState, useEffect, useCallback } from 'react';
import { ensureSignedInWithFabric } from '@microsoft/rayfin-auth-provider-fabric';
import { client } from './lib/rayfin';

const fabricOptions = {
  workspaceId: import.meta.env.VITE_FABRIC_WORKSPACE_ID,
  projectId: import.meta.env.VITE_FABRIC_ITEM_ID,
  fabricPortalUrl: import.meta.env.VITE_FABRIC_PORTAL_URL,
  returnOrigin: window.location.origin,
};

export function useFabricAuth() {
  const [session, setSession] = useState(client.auth.getSession());

  useEffect(() => client.auth.onSessionChange(setSession), []);

  // Signs in existing users and provisions new users on first sign-in.
  const signIn = useCallback(async () => {
    const result = await ensureSignedInWithFabric(client.auth, fabricOptions);
    setSession(result);
    return result;
  }, []);

  const signOut = useCallback(async () => {
    await client.auth.signOut();
  }, []);

  return {
    session,
    signIn,
    signOut,
    isAuthenticated: session?.isAuthenticated ?? false,
  };
}

在组件中使用挂钩:

function App() {
  const { isAuthenticated, signIn, signOut } = useFabricAuth();

  if (!isAuthenticated) {
    return <button onClick={signIn}>Sign in with Fabric</button>;
  }

  return (
    <>
      <Dashboard />
      <button onClick={signOut}>Sign out</button>
    </>
  );
}

API 参考

ensureSignedInWithFabric

function ensureSignedInWithFabric(
  auth: Auth,
  options: FabricAuthOptions
): Promise<OpaqueSession>;

实现四步身份验证瀑布:

  1. 如果已通过身份验证,则返回现有会话。
  2. 尝试通过刷新令牌进行静默刷新。
  3. 嵌入模式 - 如果在 Fabric iframe 中运行,则通过 postMessage 获取会话到父帧。
  4. 在弹出窗口中打开 Fabric 门户(弹出流程),并等待 postMessage 切换完成。

步骤 1 到 3 可以在页面加载时安全地调用。 步骤 4 将打开弹出窗口,必须在用户手势处理程序中运行。

FabricAuthOptions

财产 类型 Description
workspaceId string Fabric工作区 ID。
projectId string Fabric应用项 ID。
fabricPortalUrl string Fabric 门户基本 URL(例如,https://app.fabric.microsoft.com)。
returnOrigin string 应用的交付来源 postMessage (例如, window.location.origin)。 必须是裸源(方案和主机,无路径)。
fabricEmbedded boolean(可选) 强制嵌入模式。 从 ?fabricEmbedded=true URL 中自动检测到。

帮助函数

Function Description
initEmbeddedAuth(auth, options) 页面加载安全的嵌入式身份验证。如果在 Fabric iframe 中运行,则返回会话;否则返回 null
initiateFabricLogin(auth, options) 低级别弹出窗口流。 在弹出窗口中使用 PKCE 参数打开 Fabric 门户,并监听 postMessage 交接。
isEmbeddedMode(options) 如果应用在嵌入式模式下运行(Fabric iframe),则返回 true

安全功能

  • PKCE S256 – 每个流都会生成加密代码验证器和质询,以防止授权代码截获。
  • State 随机数 – 随机生成的随机数会将交接响应与发起该请求的选项卡关联起来,从而防止跨站点请求伪造。
  • postMessage 源验证 – SDK 对 event.origin 传入消息进行验证,并拒绝来自意外源的消息。
  • 自动清理 – PKCE 状态会在 5 分钟后过期,并在下一次流程执行时由垃圾回收机制清理。
  • 流程超时 – 如果在 5 分钟内未收到交接消息,弹出窗口流程将超时。

排查身份验证问题

浏览器阻止了Fabric门户窗口。 确保 ensureSignedInWithFabric() 从同步用户手势处理程序(例如按钮 onClick)调用。 在用户交互之前,请勿在页面加载或异步链中调用它。

会话未持久化

确认 RayfinClient 已配置正确的 baseUrlpublishableKey。 回调选项卡和原始选项卡必须共享相同的源,才能BroadcastChannellocalStorage正常工作。

身份验证在长时间延迟后失败

登录流将在 5 分钟后过期。 如果你开始登录流程,但未在该时限内完成,整个流程将失败。 关闭弹出窗口,再次选择登录按钮以启动新流。