Fabric SSO
Sign users in with Microsoft Entra ID through Fabric SSO, using the popup flow for browser apps or embedded flow in the Fabric portal.
Fabric SSO signs users in with the Microsoft Entra identity they use for Fabric. Use it when your Rayfin browser app should let Fabric broker interactive sign-in. There is no separate login form for Rayfin Fabric apps.
Note
Fabric SSO requires a backend deployed to Fabric, not a frontend deployed to Fabric.
The popup flow works from any origin in allowedRedirectUris, including http://localhost:5173.
Only the embedded iframe flow requires the app itself to be loaded inside the Fabric portal.
The SDK supports two Fabric-brokered browser flows:
- Popup flow — a standalone browser app opens the Fabric broker in a new tab, receives a handoff code, and exchanges it for a Rayfin session.
- Embedded flow — a Rayfin app loaded inside the Fabric portal iframe requests a handoff from the parent frame with no popup, redirect, or user click.
For code that already has a delegated Entra token, use Direct Entra token sign-in. For a third-party portal that embeds a Rayfin app in an iframe, use External embed host. New in 1.36
How the popup flow works
- Your app calls
ensureSignedInWithFabric()from a user gesture. - The SDK opens a Fabric broker URL in a popup and registers
postMessagelisteners. - The broker sends
brokeredAuth.ready. - The SDK replies with a PKCE S256 challenge and state nonce.
- The broker returns a handoff code.
- The SDK exchanges the handoff code for Rayfin tokens and installs an opaque session on
client.auth.
No callback page or redirect is needed for the modern broker path. The popup closes automatically after the handoff completes.
The popup path preserves existing path and query parameters on fabricPortalUrl.
How the embedded flow works
- The Fabric shell loads the Rayfin app in an iframe with
?fabricEmbedded=true. - Your app calls and awaits
initEmbeddedAuth()once at startup. - The SDK generates PKCE parameters in memory and posts
auth.requestHandoffto the parent frame. - The parent returns a handoff code.
- The SDK exchanges the handoff code for Rayfin tokens and installs the session.
In Rayfin 1.36, embedded startup first performs a short external-embed classification handshake. A third-party parent can acknowledge that handshake for external embed host. If no parent acknowledges, the normal Fabric embedded flow continues. Changed in 1.36
Await initEmbeddedAuth() before rendering authenticated content or trusting a stored session.
Do not skip it when client.auth.getSession() already reports an authenticated session; in embedded scenarios the SDK may need to discard a stale session from a different user.
Local frontend development
The popup flow works from localhost.
Fabric SSO needs a deployed Fabric backend, but the frontend can be served by Vite from an allowed origin.
Only the embedded iframe flow needs the app itself to be loaded inside the Fabric portal.
-
Sign in to the Rayfin CLI on your development machine:
npx rayfin login -
Keep your local frontend origin in
allowedRedirectUris.http://localhost:5173is the default Vite origin:rayfin/rayfin.yml services: auth: enabled: true allowedRedirectUris: - http://localhost:5173 fabric: enabled: true -
Start the development loop:
npm run devIn scaffolded projects,
npm run devrunsrayfin dev. That command provisions or reuses the Fabric backend, applies runtime settings, writesRAYFIN_PUBLIC_*values torayfin/.env, maps them to framework variables such asVITE_FABRIC_ITEM_ID, and starts Vite. -
Sign in from the button that calls
ensureSignedInWithFabric(). The popup opens the Fabric broker, the user authenticates there, and the handoff returns toreturnOrigin— your local Vite origin.
The embedded flow is different.
It runs only after npx rayfin up deploys the static frontend and you open the app from the Fabric portal.
ensureSignedInWithFabric() falls back to the popup when the app is not embedded, so the same sign-in button still covers standalone local development.
Scripts can still deploy the backend without publishing static hosting:
npx rayfin up --exclude-services staticHostingUse rayfin dev for the normal development loop.
Use rayfin up --exclude-services staticHosting for automation that needs to update the backend while another process serves the frontend.
Local automatic sign-in
Some 1.36 templates use the local development adapter to seed auth during rayfin dev.
When services.staticHosting.assetAccess is protected, the Vite adapter exposes a loopback-only /.rayfin/dev/session-token endpoint for the local dev server. New in 1.36
That endpoint uses the developer's rayfin login session to obtain a delegated Entra token.
It exchanges that token for a Rayfin token response and app code installs it with signInWithBrokeredToken().
Public static hosting can opt into the same behavior with rayfinLocalDev({ autoLogin: true }).
The exchange requires services.auth.fabric.externalEntraExchange: true, which the Universal App template sets.
The 1.36 universal app template wires this in a development-only code path: it dynamically imports @microsoft/rayfin-local-dev, checks isRayfinLocalAutoLoginEnabled(), calls fetchRayfinLocalSessionToken(), then passes the result to signInWithBrokeredToken(client.auth, sessionToken).
See Develop locally for the full local dev routing and automatic sign-in behavior.
Use the helper functions from @microsoft/rayfin-local-dev only in development-only code paths:
import { signInWithBrokeredToken } from '@microsoft/rayfin-auth-provider-fabric';
import { client } from '../services/rayfinClient';
export async function tryLocalAutoLogin() {
if (!import.meta.env.DEV) return null;
const localDev = await import('@microsoft/rayfin-local-dev');
if (!localDev.isRayfinLocalAutoLoginEnabled()) return null;
const sessionToken = await localDev.fetchRayfinLocalSessionToken();
if (!sessionToken) return null;
return signInWithBrokeredToken(client.auth, sessionToken);
}If the endpoint is not active, fetchRayfinLocalSessionToken() returns undefined.
If it is active but the token exchange fails, it throws an actionable error.
Enable Fabric SSO
Add the fabric block and every browser origin that will run the popup flow:
services:
auth:
enabled: true
allowedRedirectUris:
- http://localhost:5173
fabric:
enabled: trueallowedRedirectUris uses bare origins such as http://localhost:5173 or https://app.example.com.
The popup flow binds the handoff to returnOrigin, so the origin your app passes to the SDK must be allowed by the backend.
Apply the setting with the development loop or a deployment:
npm run devnpx rayfin upInstall the provider package
Fabric auth ships as a separate companion package:
npm install @microsoft/rayfin-auth-provider-fabricKeep Rayfin packages on the same version.
rayfin up checks static-hosted apps for an auth SDK new enough for protected static hosting and may prompt you to upgrade Rayfin packages before uploading.
Build options from runtime config
Fabric auth needs Fabric coordinates at runtime to build FabricAuthOptions.
Scaffolded projects receive these from rayfin dev or rayfin up.
Source variable (rayfin/.env) | Vite variable (.env.local) | Maps to | Example |
|---|---|---|---|
RAYFIN_PUBLIC_ITEM_ID | VITE_FABRIC_ITEM_ID | projectId | 21b98705-08d5-448c-ab32-d88a3d00af41 |
RAYFIN_PUBLIC_WORKSPACE_ID | VITE_FABRIC_WORKSPACE_ID | workspaceId | b80c0e39-468a-4742-8f0a-458dc6b1c918 |
RAYFIN_PUBLIC_PORTAL_URL | VITE_FABRIC_PORTAL_URL | fabricPortalUrl | https://app.fabric.microsoft.com/ |
import type { FabricAuthOptions } from '@microsoft/rayfin-auth-provider-fabric';
export const fabricOptions: FabricAuthOptions = {
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,
};After rayfin dev or rayfin up, the CLI records deployment metadata in rayfin/.deployments.json and merges the corresponding RAYFIN_PUBLIC_* variables into rayfin/.env.
The framework environment file is regenerated before the frontend starts.
Popup flow: sign in from a button
Call ensureSignedInWithFabric() from a synchronous user-gesture handler, such as a button click.
The function may call window.open(), so calling it on page load or after an unrelated await can trigger popup blockers.
import { RayfinClient } from '@microsoft/rayfin-client';
import { ensureSignedInWithFabric } from '@microsoft/rayfin-auth-provider-fabric';
import { fabricOptions } from './fabricOptions';
const client = new RayfinClient({
baseUrl: import.meta.env.VITE_RAYFIN_API_URL,
publishableKey: import.meta.env.VITE_RAYFIN_PUBLISHABLE_KEY,
});
export async function signIn() {
const session = await ensureSignedInWithFabric(client.auth, fabricOptions);
console.log('Signed in:', session.user?.email);
return session;
}Embedded flow: automatic sign-in on startup
Call initEmbeddedAuth() once during app startup.
It returns null when the page is not embedded and never opens a popup.
import { RayfinClient } from '@microsoft/rayfin-client';
import { initEmbeddedAuth } from '@microsoft/rayfin-auth-provider-fabric';
import { fabricOptions } from './fabricOptions';
const client = new RayfinClient({
baseUrl: import.meta.env.VITE_RAYFIN_API_URL,
publishableKey: import.meta.env.VITE_RAYFIN_PUBLISHABLE_KEY,
});
export async function bootstrapAuth() {
const embeddedSession = await initEmbeddedAuth(client.auth, fabricOptions);
if (embeddedSession) {
console.log('Embedded session established:', embeddedSession.user?.email);
}
}Warning
Import @microsoft/rayfin-auth-provider-fabric statically in your app entry module.
The package captures the ?fabricEmbedded=true URL flag during module load, before client-side navigation can strip the query string.
Use both Fabric SSO flows
Most browser apps should support both environments:
- Call and await
initEmbeddedAuth()at startup. - Subscribe to
client.auth.onSessionChange()for UI state. - Wire
ensureSignedInWithFabric()to a sign-in button for standalone browser use.
ensureSignedInWithFabric() runs this waterfall:
- Return a safe existing session.
- Try a refresh token.
- Use embedded
postMessagehandoff when Fabric embedded mode or the external-embed handshake applies. - Open the Fabric broker popup.
For page-load initialization with no user gesture available, use initEmbeddedAuth() instead.
It skips the popup step and returns null when no embedded session is available.
API reference
ensureSignedInWithFabric(auth, options)
function ensureSignedInWithFabric(
auth: Auth,
options: FabricAuthOptions
): Promise<OpaqueSession>;The primary Fabric SSO entry point. Steps 1–3 are safe to call on page load. Step 4 opens a new browser tab and must run inside a user-gesture handler.
initEmbeddedAuth(auth, options)
function initEmbeddedAuth(
auth: Auth,
options: FabricAuthOptions
): Promise<OpaqueSession | null>;Call once at app startup.
Returns the authenticated session when running in embedded mode or external embed mode, or null when neither scenario applies.
initiateFabricLogin(auth, options)
function initiateFabricLogin(auth: Auth, options: FabricAuthOptions): Promise<void>;Low-level popup function that opens the Fabric broker, delivers the PKCE challenge, waits for the handoff, exchanges the code, and installs the session.
Most apps should use ensureSignedInWithFabric() instead.
signInWithBrokeredToken(auth, tokenResponse)
function signInWithBrokeredToken(
auth: Auth,
tokenResponse: TokenResponse
): Promise<OpaqueSession>;Installs a Rayfin token response that a trusted caller already obtained from the brokered-token endpoint. The local development adapter uses this helper for automatic local sign-in. New in 1.36
FabricAuthOptions
| Property | Type | Description |
|---|---|---|
workspaceId | string | The Fabric workspace ID. |
projectId | string | The Rayfin item ID or AppBackend artifact ID. |
fabricPortalUrl | string | The Fabric portal base URL. Existing path and query parameters are preserved. |
returnOrigin | string | Your app's bare origin, for example window.location.origin. |
fabricEmbedded | boolean (optional) | Force embedded mode. The SDK also auto-detects it from ?fabricEmbedded=true and stored embedded state. |
callbackUrl | string (optional, deprecated) | Full callback URL for legacy Fabric portals that redirect instead of using postMessage. Defaults to ${returnOrigin}/auth/callback. |
Legacy callback bridge
Newer Fabric portals hand off the sign-in code via postMessage.
Older portals can redirect the popup to a callback page in your app.
For that legacy path, call bridgeFabricCallback() as early as possible on the callback page.
import { bridgeFabricCallback } from '@microsoft/rayfin-auth-provider-fabric';
const bridged = bridgeFabricCallback();
if (!bridged) {
console.log('No Fabric handoff detected');
}The function exists for backward compatibility with older Fabric portals.
Security details
- Popup and embedded flows use PKCE S256 and state nonces.
- The SDK holds the PKCE verifier in memory and does not persist it to
localStorage. - Incoming popup handoff messages are correlated by state.
- Popup origin checks are advisory because the broker extension frame can run at an origin different from
fabricPortalUrl; state plus PKCE are the enforcement boundary. - Normal embedded
auth.requestHandoffmessages do not usefabricPortalUrlastargetOriginbecause the parent frame is the Fabric extension iframe, not the top-level portal. - External embed handoff messages are pinned to the parent origin acknowledged during classification. New in 1.36
- The popup flow times out after five minutes.
Troubleshooting
| Symptom or error | Meaning | Fix |
|---|---|---|
Popup blocked / TAB_BLOCKED | The browser blocked the broker popup. | Call ensureSignedInWithFabric() directly from a button click. |
MISSING_WORKSPACE_ID, MISSING_PROJECT_ID, MISSING_RETURN_ORIGIN, MISSING_FABRIC_PORTAL_URL | Required FabricAuthOptions fields are absent. | Build options from the VITE_FABRIC_* values produced by rayfin dev or rayfin up. |
PKCE_CHALLENGE_FAILED | The browser could not compute the PKCE challenge. | Retry in a supported browser context. |
CHALLENGE_DELIVERY_FAILED | The SDK could not deliver the PKCE challenge to the broker frame. | Retry sign-in and check browser popup or iframe restrictions. |
Timeout after five minutes / FABRIC_AUTH_TIMEOUT | The popup handoff did not finish. | Check returnOrigin, allowed redirect origins, and whether the broker tab was closed. |
initEmbeddedAuth() returns null | The app was not detected as embedded and no external embed host acknowledged. | In Fabric, confirm the URL contains ?fabricEmbedded=true; in external embed, register the host before mounting the iframe. |
NO_PARENT_WINDOW | Embedded auth was requested outside an iframe. | ensureSignedInWithFabric() falls back to popup auth; remove stale fabricEmbedded flags if this recurs. |
STATE_MISMATCH | The response state did not match the request state. | Retry the flow from the start and investigate repeated failures as a security issue. |
SESSION_NOT_ESTABLISHED | A handoff completed but no authenticated session was installed. | Check backend auth configuration and SDK package versions. |
| Session not persisting | RayfinClient is configured with the wrong backend or publishable key. | Confirm baseUrl and publishableKey match the deployed or dev backend. |
Add Fabric SSO sign-in to my Rayfin app.
Enable services.auth.fabric in rayfin/rayfin.yml and keep allowedRedirectUris scoped to my app origins, including http://localhost:5173 for local frontend development. Install @microsoft/rayfin-auth-provider-fabric.
At app startup, statically import the provider package and call initEmbeddedAuth(client.auth, fabricOptions). Await that call before rendering authenticated UI. Add a sign-in button that calls ensureSignedInWithFabric(client.auth, fabricOptions) directly from the click handler.
Build FabricAuthOptions from VITE_FABRIC_WORKSPACE_ID, VITE_FABRIC_ITEM_ID, VITE_FABRIC_PORTAL_URL, and window.location.origin. Use client.auth.onSessionChange(...), not onAuthStateChange.