@microsoft/rayfin-auth-provider-fabric 1.35.0-alpha.1412 → 1.35.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,17 +4,22 @@ import type { FabricAuthOptions } from './types.js';
4
4
  * Embedded-mode Fabric login — acquires a session via postMessage to the
5
5
  * parent Fabric Extension Host without opening a popup.
6
6
  *
7
- * 1. Clears any prior Rayfin session in this iframe (`auth.signOut()`)
8
- * so a stale session from a previous Fabric user cannot be reused.
7
+ * 1. Discards any prior session when the host stamps no `?_fu=` hint (see below).
9
8
  * 2. Generates PKCE parameters in local variables (no `localStorage`).
10
9
  * 3. Sends `auth.requestHandoff` to the parent via `requestHandoff`.
11
10
  * 4. Receives the handoff code from the host's `AuthPlugin`.
12
11
  * 5. Exchanges the handoff code for tokens via `exchangeVerificationCode`.
13
- * 6. Creates a session via `createSessionFromTokenResponse` and marks
14
- * embedded handoff as completed for this page load.
12
+ * 6. Creates a session via `createSessionFromTokenResponse`.
15
13
  *
16
14
  * The session is stored in the iframe's own `localStorage`.
17
15
  *
16
+ * **Sign-out is conditional.** This function used to sign out unconditionally, as the only defence
17
+ * against reusing a previous Fabric user's session — at the cost of destroying a valid session (and
18
+ * the serve cookie sealed to it) on every single embedded load. A host that stamps `?_fu=` gives the
19
+ * workload gate an identity to compare, so that defence is redundant and the sign-out is skipped. A
20
+ * host that does not is indistinguishable from the pre-feature world, so the original behaviour is
21
+ * kept for it rather than silently dropping the protection during version skew.
22
+ *
18
23
  * @param auth - Auth instance for token exchange and session management.
19
24
  * @param options - Fabric auth options (`returnOrigin` required).
20
25
  * @throws `AuthError` - On missing options, transport failure, or token exchange error.
@@ -3,21 +3,27 @@ import { createSessionFromTokenResponse } from '@microsoft/rayfin-auth/_internal
3
3
  import { AuthError, assertBrowser } from '@microsoft/rayfin-lib';
4
4
  import { requestHandoff } from './PostMessageAuthTransport.js';
5
5
  import { markEmbeddedHandoffCompleted } from './fabricAuthHelpers.js';
6
+ import { hasFabricUserHint } from './fabricUserHint.js';
6
7
  /**
7
8
  * Embedded-mode Fabric login — acquires a session via postMessage to the
8
9
  * parent Fabric Extension Host without opening a popup.
9
10
  *
10
- * 1. Clears any prior Rayfin session in this iframe (`auth.signOut()`)
11
- * so a stale session from a previous Fabric user cannot be reused.
11
+ * 1. Discards any prior session when the host stamps no `?_fu=` hint (see below).
12
12
  * 2. Generates PKCE parameters in local variables (no `localStorage`).
13
13
  * 3. Sends `auth.requestHandoff` to the parent via `requestHandoff`.
14
14
  * 4. Receives the handoff code from the host's `AuthPlugin`.
15
15
  * 5. Exchanges the handoff code for tokens via `exchangeVerificationCode`.
16
- * 6. Creates a session via `createSessionFromTokenResponse` and marks
17
- * embedded handoff as completed for this page load.
16
+ * 6. Creates a session via `createSessionFromTokenResponse`.
18
17
  *
19
18
  * The session is stored in the iframe's own `localStorage`.
20
19
  *
20
+ * **Sign-out is conditional.** This function used to sign out unconditionally, as the only defence
21
+ * against reusing a previous Fabric user's session — at the cost of destroying a valid session (and
22
+ * the serve cookie sealed to it) on every single embedded load. A host that stamps `?_fu=` gives the
23
+ * workload gate an identity to compare, so that defence is redundant and the sign-out is skipped. A
24
+ * host that does not is indistinguishable from the pre-feature world, so the original behaviour is
25
+ * kept for it rather than silently dropping the protection during version skew.
26
+ *
21
27
  * @param auth - Auth instance for token exchange and session management.
22
28
  * @param options - Fabric auth options (`returnOrigin` required).
23
29
  * @throws `AuthError` - On missing options, transport failure, or token exchange error.
@@ -27,22 +33,16 @@ export async function embeddedFabricLogin(auth, options) {
27
33
  if (!options.returnOrigin) {
28
34
  throw new AuthError('returnOrigin is required for embedded Fabric authentication.', 'MISSING_RETURN_ORIGIN');
29
35
  }
30
- // Embedded mode: the parent Fabric host is the source of truth for "who
31
- // is signed in". Discard any prior Rayfin session that may have been
32
- // left behind in this iframe's localStorage by a previous Fabric user
33
- // BEFORE we ask for a fresh handoff. Without this, a Fabric user
34
- // switch (User A → User B) would leave the SPA authenticated as
35
- // User A because tryResumeSession would happily return the stale
36
- // User A session and we would never reach the handoff path.
37
- //
38
- // Errors from signOut are swallowed: the stale token may already be
39
- // invalid server-side, and the local clear (which signOut performs in
40
- // its own catch block) is what actually matters here.
41
- try {
42
- await auth.signOut();
43
- }
44
- catch (signOutError) {
45
- console.debug('[FabricAuth:embedded] Pre-handoff signOut failed (non-fatal); local session has been cleared', signOutError);
36
+ if (!hasFabricUserHint()) {
37
+ // Legacy host: nothing server-side is comparing identities, so fall back to discarding any prior
38
+ // session before the handoff. Errors are swallowed — the stale token may already be invalid
39
+ // server-side, and the local clear that signOut performs in its own catch is what matters.
40
+ try {
41
+ await auth.signOut();
42
+ }
43
+ catch (signOutError) {
44
+ console.debug('[FabricAuth:embedded] Pre-handoff signOut failed (non-fatal); local session has been cleared', signOutError);
45
+ }
46
46
  }
47
47
  // PKCE in local variables only — not persisted to localStorage.
48
48
  const codeVerifier = generateCodeVerifier();
@@ -6,16 +6,14 @@ import type { FabricAuthOptions } from './types.js';
6
6
  * Implements a multi-step waterfall — the first step that succeeds short-circuits the rest:
7
7
  *
8
8
  * 1. **Already authenticated** — if `auth.getSession().isAuthenticated` is true,
9
- * return the existing session immediately. **Skipped on the first
10
- * embedded call per page load** to avoid silently reusing a stale
11
- * session belonging to a previously signed-in Fabric user.
9
+ * return the existing session immediately. On a legacy embedded host (one that
10
+ * stamps no `?_fu=` hint) this is skipped on the first call per page load, so a
11
+ * session belonging to a previously signed-in Fabric user cannot be reused.
12
12
  * 2. **Refresh token** — if a refresh token is available, attempt `auth.refreshSession()`.
13
13
  * Return the refreshed session on success; continue on failure.
14
- * Also skipped on the first embedded call per page load.
14
+ * Subject to the same skip.
15
15
  * 3. **Embedded mode** — if running inside a Fabric iframe (`fabricEmbedded=true`),
16
16
  * use `embeddedFabricLogin()` to acquire a session via `postMessage` handoff.
17
- * `embeddedFabricLogin` performs a hard `auth.signOut()` before the
18
- * handoff so the new session always reflects the current Fabric user.
19
17
  * 4. **Open Fabric broker** — no existing auth path available. Open the Fabric Portal
20
18
  * in a new tab via `initiateFabricLogin()` and wait for the Fabric extension to post
21
19
  * the handoff code via `postMessage`. The function exchanges the code internally
@@ -1,6 +1,7 @@
1
1
  import { AuthError, assertBrowser } from '@microsoft/rayfin-lib';
2
2
  import { embeddedFabricLogin } from './embeddedFabricLogin.js';
3
3
  import { hasEmbeddedHandoffCompleted, isEmbeddedMode, tryResumeSession, } from './fabricAuthHelpers.js';
4
+ import { hasFabricUserHint } from './fabricUserHint.js';
4
5
  import { initiateFabricLogin } from './initiateFabricLogin.js';
5
6
  /**
6
7
  * Ensures the user is signed in via Fabric brokered authentication.
@@ -8,16 +9,14 @@ import { initiateFabricLogin } from './initiateFabricLogin.js';
8
9
  * Implements a multi-step waterfall — the first step that succeeds short-circuits the rest:
9
10
  *
10
11
  * 1. **Already authenticated** — if `auth.getSession().isAuthenticated` is true,
11
- * return the existing session immediately. **Skipped on the first
12
- * embedded call per page load** to avoid silently reusing a stale
13
- * session belonging to a previously signed-in Fabric user.
12
+ * return the existing session immediately. On a legacy embedded host (one that
13
+ * stamps no `?_fu=` hint) this is skipped on the first call per page load, so a
14
+ * session belonging to a previously signed-in Fabric user cannot be reused.
14
15
  * 2. **Refresh token** — if a refresh token is available, attempt `auth.refreshSession()`.
15
16
  * Return the refreshed session on success; continue on failure.
16
- * Also skipped on the first embedded call per page load.
17
+ * Subject to the same skip.
17
18
  * 3. **Embedded mode** — if running inside a Fabric iframe (`fabricEmbedded=true`),
18
19
  * use `embeddedFabricLogin()` to acquire a session via `postMessage` handoff.
19
- * `embeddedFabricLogin` performs a hard `auth.signOut()` before the
20
- * handoff so the new session always reflects the current Fabric user.
21
20
  * 4. **Open Fabric broker** — no existing auth path available. Open the Fabric Portal
22
21
  * in a new tab via `initiateFabricLogin()` and wait for the Fabric extension to post
23
22
  * the handoff code via `postMessage`. The function exchanges the code internally
@@ -66,23 +65,26 @@ import { initiateFabricLogin } from './initiateFabricLogin.js';
66
65
  */
67
66
  export async function ensureSignedInWithFabric(auth, options) {
68
67
  assertBrowser('ensureSignedInWithFabric');
69
- // Steps 1-2: existing session or refresh.
70
- // In embedded mode we MUST skip this on the first call per page load —
71
- // the session in iframe localStorage may belong to a previously
72
- // signed-in Fabric user. After the first successful handoff this load
73
- // (`hasEmbeddedHandoffCompleted()` is true) the cached session is the
74
- // current user's, so resuming is safe and avoids redundant handoffs.
75
68
  const inEmbeddedMode = isEmbeddedMode(options);
76
- const skipResume = inEmbeddedMode && !hasEmbeddedHandoffCompleted();
77
- if (!skipResume) {
69
+ // Steps 1-2: existing session or refresh.
70
+ //
71
+ // A host that stamps `?_fu=` has already had its cookie identity-checked by the workload gate
72
+ // before this code ran — a cross-user cookie is cleared and a fresh sign-in bootstrap is served,
73
+ // overwriting this origin's persisted session — so resuming is safe from the first call.
74
+ //
75
+ // A host that stamps no hint is indistinguishable from the pre-feature world: nothing compared the
76
+ // identities, so the persisted session may belong to a previously signed-in Fabric user. There we
77
+ // keep the original behaviour and skip resume until a handoff has run this page load.
78
+ const skipResume = inEmbeddedMode && !hasFabricUserHint() && !hasEmbeddedHandoffCompleted();
79
+ if (skipResume) {
80
+ console.debug('[FabricAuth] Embedded host stamped no user hint — skipping session resume to avoid a stale cross-user session');
81
+ }
82
+ else {
78
83
  const resumed = await tryResumeSession(auth);
79
84
  if (resumed) {
80
85
  return resumed;
81
86
  }
82
87
  }
83
- else {
84
- console.debug('[FabricAuth] Embedded mode first load — skipping session resume to avoid stale cross-user session');
85
- }
86
88
  // Step 3: Embedded mode — postMessage auth via parent iframe host
87
89
  if (inEmbeddedMode) {
88
90
  console.debug('[FabricAuth] Embedded mode detected, using postMessage auth');
@@ -11,21 +11,17 @@ import type { FabricAuthOptions } from './types.js';
11
11
  */
12
12
  export declare function isEmbeddedMode(options: FabricAuthOptions): boolean;
13
13
  /**
14
- * Returns whether an embedded postMessage handoff has already completed
15
- * during the current page load.
14
+ * Returns whether an embedded postMessage handoff has already completed during this page load.
16
15
  */
17
16
  export declare function hasEmbeddedHandoffCompleted(): boolean;
18
17
  /**
19
- * Marks that an embedded postMessage handoff has completed for this page
20
- * load. Subsequent calls to {@link tryResumeSession} will be allowed
21
- * (the established session is the current user's session).
18
+ * Marks that an embedded postMessage handoff has completed for this page load.
22
19
  */
23
20
  export declare function markEmbeddedHandoffCompleted(): void;
24
21
  /**
25
- * Test-only helper that clears the embedded-handoff completion flag.
26
- * Not exported from the package barrel.
22
+ * Clears the embedded-handoff completion flag.
27
23
  *
28
- * @internal
24
+ * @internal Test helper. Not exported from the package barrel.
29
25
  */
30
26
  export declare function resetEmbeddedHandoffStateForTests(): void;
31
27
  /**
@@ -12,43 +12,34 @@ export function isEmbeddedMode(options) {
12
12
  return sharedIsEmbeddedMode(options);
13
13
  }
14
14
  /**
15
- * Module-level flag tracking whether an embedded postMessage handoff has
16
- * already completed during the current page load.
15
+ * Tracks whether an embedded postMessage handoff has completed during the current page load.
17
16
  *
18
- * In embedded mode the parent Fabric host is the source of truth for "who
19
- * is signed in". A stale Rayfin session left over from a previous user in
20
- * the iframe's `localStorage` must NOT be silently reused — otherwise
21
- * switching Fabric users without reloading the iframe leaves the SPA
22
- * authenticated as the previous user.
17
+ * Only consulted on the **legacy** embedded path — a host that stamps no `?_fu=` hint, and therefore
18
+ * gives the workload gate nothing to compare. There, a persisted session cannot be trusted to belong
19
+ * to the current Fabric user, so the first auth call per page load skips resume and forces a full
20
+ * handoff. Once that handoff has run, the stored session is known to be the current user's and later
21
+ * calls in the same load may resume it without thrashing the postMessage channel.
23
22
  *
24
- * On the first embedded auth call per page load we therefore skip
25
- * {@link tryResumeSession} and run the full sign-out + handoff path
26
- * inside {@link embeddedFabricLogin}. Once that completes we set this
27
- * flag so subsequent calls (e.g. additional components asking for the
28
- * session) can reuse the freshly established session via
29
- * {@link tryResumeSession} without thrashing the postMessage channel.
23
+ * Hosts that do stamp a hint never reach this flag: the gate rejects a cross-user cookie before the
24
+ * app boots, so resume is safe from the first call.
30
25
  */
31
26
  let embeddedHandoffCompletedThisLoad = false;
32
27
  /**
33
- * Returns whether an embedded postMessage handoff has already completed
34
- * during the current page load.
28
+ * Returns whether an embedded postMessage handoff has already completed during this page load.
35
29
  */
36
30
  export function hasEmbeddedHandoffCompleted() {
37
31
  return embeddedHandoffCompletedThisLoad;
38
32
  }
39
33
  /**
40
- * Marks that an embedded postMessage handoff has completed for this page
41
- * load. Subsequent calls to {@link tryResumeSession} will be allowed
42
- * (the established session is the current user's session).
34
+ * Marks that an embedded postMessage handoff has completed for this page load.
43
35
  */
44
36
  export function markEmbeddedHandoffCompleted() {
45
37
  embeddedHandoffCompletedThisLoad = true;
46
38
  }
47
39
  /**
48
- * Test-only helper that clears the embedded-handoff completion flag.
49
- * Not exported from the package barrel.
40
+ * Clears the embedded-handoff completion flag.
50
41
  *
51
- * @internal
42
+ * @internal Test helper. Not exported from the package barrel.
52
43
  */
53
44
  export function resetEmbeddedHandoffStateForTests() {
54
45
  embeddedHandoffCompletedThisLoad = false;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Fabric user-hint detection.
3
+ *
4
+ * The AppBackend host stamps an opaque `?_fu=` hint onto the embedded iframe URL, and the workload's
5
+ * static-hosting gate compares it against the hint sealed into the serve cookie. Its presence is the
6
+ * SDK's signal that **the host is new enough for cross-user protection to exist server-side**, and
7
+ * therefore that a persisted session may safely be resumed.
8
+ *
9
+ * When it is absent — an older AppBackend that does not stamp it, or a host that could not resolve
10
+ * the signed-in user — no server-side comparison is happening, so the SDK falls back to the original
11
+ * behaviour: discard any persisted session and run a full handoff on every embedded load. That is
12
+ * slower, but it is the only thing standing between a previous Fabric user's session and the current
13
+ * one on a host that cannot help.
14
+ *
15
+ * The URL value is captured eagerly at module load, so a client-side navigation that strips the query
16
+ * string does not make the SDK think the host got older mid-session and start signing the user out.
17
+ *
18
+ * That capture is deliberately held in module state rather than `sessionStorage`. Storage outlives a
19
+ * document, so a hint from an earlier load would still answer for a load the host never stamped — for
20
+ * example after a user switch where identity resolution failed — and the SDK would resume a persisted
21
+ * session that no server-side comparison ever vetted. Module state dies with the document, which is
22
+ * exactly the lifetime this signal is allowed to have.
23
+ *
24
+ * @internal Not exported from the package barrel.
25
+ */
26
+ /**
27
+ * Captures the `?_fu=` value from the current URL.
28
+ *
29
+ * Called once at module load to snapshot the entry URL. Also the in-package injection point for
30
+ * tests; it is not exported from the barrel, so app code cannot use it to fake the signal.
31
+ *
32
+ * @returns `true` when the URL carried a hint, whether or not it had already been captured.
33
+ */
34
+ export declare function persistFabricUserHintFromUrl(): boolean;
35
+ /**
36
+ * Whether the host stamped a Fabric user hint for this app.
37
+ *
38
+ * Reports the entry URL captured at module load and nothing else. It deliberately does not re-read
39
+ * the live URL: on the client the hint's *presence* suppresses the cross-user sign-out, so a value
40
+ * added after load - by app routing, a query-preserving link, or anything else the gate never saw -
41
+ * would let the page vouch for itself. The server-side comparison has the opposite polarity, where a
42
+ * forged hint can only force a re-authentication, so this direction is the one that has to be sealed.
43
+ *
44
+ * @returns `true` when server-side identity gating is in play and a persisted session may be reused.
45
+ */
46
+ export declare function hasFabricUserHint(): boolean;
47
+ /**
48
+ * Clears the captured hint.
49
+ *
50
+ * @internal Test helper.
51
+ */
52
+ export declare function clearFabricUserHint(): void;
53
+ //# sourceMappingURL=fabricUserHint.d.ts.map
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Fabric user-hint detection.
3
+ *
4
+ * The AppBackend host stamps an opaque `?_fu=` hint onto the embedded iframe URL, and the workload's
5
+ * static-hosting gate compares it against the hint sealed into the serve cookie. Its presence is the
6
+ * SDK's signal that **the host is new enough for cross-user protection to exist server-side**, and
7
+ * therefore that a persisted session may safely be resumed.
8
+ *
9
+ * When it is absent — an older AppBackend that does not stamp it, or a host that could not resolve
10
+ * the signed-in user — no server-side comparison is happening, so the SDK falls back to the original
11
+ * behaviour: discard any persisted session and run a full handoff on every embedded load. That is
12
+ * slower, but it is the only thing standing between a previous Fabric user's session and the current
13
+ * one on a host that cannot help.
14
+ *
15
+ * The URL value is captured eagerly at module load, so a client-side navigation that strips the query
16
+ * string does not make the SDK think the host got older mid-session and start signing the user out.
17
+ *
18
+ * That capture is deliberately held in module state rather than `sessionStorage`. Storage outlives a
19
+ * document, so a hint from an earlier load would still answer for a load the host never stamped — for
20
+ * example after a user switch where identity resolution failed — and the SDK would resume a persisted
21
+ * session that no server-side comparison ever vetted. Module state dies with the document, which is
22
+ * exactly the lifetime this signal is allowed to have.
23
+ *
24
+ * @internal Not exported from the package barrel.
25
+ */
26
+ const USER_HINT_QUERY_PARAM = '_fu';
27
+ // Scoped to this document. See the note above on why this is not persisted.
28
+ let hintSeenThisDocument = false;
29
+ /**
30
+ * Captures the `?_fu=` value from the current URL.
31
+ *
32
+ * Called once at module load to snapshot the entry URL. Also the in-package injection point for
33
+ * tests; it is not exported from the barrel, so app code cannot use it to fake the signal.
34
+ *
35
+ * @returns `true` when the URL carried a hint, whether or not it had already been captured.
36
+ */
37
+ export function persistFabricUserHintFromUrl() {
38
+ if (typeof window === 'undefined') {
39
+ return false;
40
+ }
41
+ try {
42
+ const hint = new URLSearchParams(window.location.search).get(USER_HINT_QUERY_PARAM);
43
+ if (hint) {
44
+ hintSeenThisDocument = true;
45
+ return true;
46
+ }
47
+ }
48
+ catch {
49
+ // window.location may be unavailable in some non-browser hosts.
50
+ }
51
+ return false;
52
+ }
53
+ // Captured at module load so a session resumed before any auth call still sees the hint.
54
+ persistFabricUserHintFromUrl();
55
+ /**
56
+ * Whether the host stamped a Fabric user hint for this app.
57
+ *
58
+ * Reports the entry URL captured at module load and nothing else. It deliberately does not re-read
59
+ * the live URL: on the client the hint's *presence* suppresses the cross-user sign-out, so a value
60
+ * added after load - by app routing, a query-preserving link, or anything else the gate never saw -
61
+ * would let the page vouch for itself. The server-side comparison has the opposite polarity, where a
62
+ * forged hint can only force a re-authentication, so this direction is the one that has to be sealed.
63
+ *
64
+ * @returns `true` when server-side identity gating is in play and a persisted session may be reused.
65
+ */
66
+ export function hasFabricUserHint() {
67
+ return hintSeenThisDocument;
68
+ }
69
+ /**
70
+ * Clears the captured hint.
71
+ *
72
+ * @internal Test helper.
73
+ */
74
+ export function clearFabricUserHint() {
75
+ hintSeenThisDocument = false;
76
+ }
77
+ //# sourceMappingURL=fabricUserHint.js.map
@@ -11,13 +11,11 @@ import type { FabricAuthOptions } from './types.js';
11
11
  * **Behaviour:**
12
12
  * - If `fabricEmbedded=true` is **not** in the URL and `options.fabricEmbedded`
13
13
  * is not `true`, returns `null` immediately (no-op).
14
- * - On the first embedded call per page load, skips session resume to
15
- * avoid silently reusing a stale session belonging to a previously
16
- * signed-in Fabric user, and goes straight to the postMessage handoff.
17
- * `embeddedFabricLogin` performs a hard `auth.signOut()` before the
18
- * handoff so the new session always reflects the current Fabric user.
19
- * - On subsequent calls within the same page load, runs the standard
20
- * resume waterfall (existing session → refresh token → handoff).
14
+ * - When the host stamps a `?_fu=` hint, runs the standard resume waterfall
15
+ * (existing session → refresh token → handoff).
16
+ * - When it does not, skips resume on the first call per page load and goes
17
+ * straight to the handoff, preserving the pre-hint protection against reusing
18
+ * a previously signed-in Fabric user's session.
21
19
  *
22
20
  * Apps that also support the popup flow should continue to call
23
21
  * {@link ensureSignedInWithFabric} from a user-gesture handler for the
@@ -1,5 +1,6 @@
1
1
  import { embeddedFabricLogin } from './embeddedFabricLogin.js';
2
2
  import { hasEmbeddedHandoffCompleted, isEmbeddedMode, tryResumeSession, } from './fabricAuthHelpers.js';
3
+ import { hasFabricUserHint } from './fabricUserHint.js';
3
4
  /**
4
5
  * Initializes embedded Fabric authentication if running inside an iframe
5
6
  * with `?fabricEmbedded=true`.
@@ -11,13 +12,11 @@ import { hasEmbeddedHandoffCompleted, isEmbeddedMode, tryResumeSession, } from '
11
12
  * **Behaviour:**
12
13
  * - If `fabricEmbedded=true` is **not** in the URL and `options.fabricEmbedded`
13
14
  * is not `true`, returns `null` immediately (no-op).
14
- * - On the first embedded call per page load, skips session resume to
15
- * avoid silently reusing a stale session belonging to a previously
16
- * signed-in Fabric user, and goes straight to the postMessage handoff.
17
- * `embeddedFabricLogin` performs a hard `auth.signOut()` before the
18
- * handoff so the new session always reflects the current Fabric user.
19
- * - On subsequent calls within the same page load, runs the standard
20
- * resume waterfall (existing session → refresh token → handoff).
15
+ * - When the host stamps a `?_fu=` hint, runs the standard resume waterfall
16
+ * (existing session → refresh token → handoff).
17
+ * - When it does not, skips resume on the first call per page load and goes
18
+ * straight to the handoff, preserving the pre-hint protection against reusing
19
+ * a previously signed-in Fabric user's session.
21
20
  *
22
21
  * Apps that also support the popup flow should continue to call
23
22
  * {@link ensureSignedInWithFabric} from a user-gesture handler for the
@@ -31,20 +30,17 @@ export async function initEmbeddedAuth(auth, options) {
31
30
  if (!isEmbeddedMode(options)) {
32
31
  return null;
33
32
  }
34
- // Steps 1-2: existing session or refresh.
35
- // Skip on the first embedded call per page load — a session left in
36
- // iframe localStorage may belong to a previously signed-in Fabric
37
- // user, and silently resuming it would defeat the handoff. Once
38
- // `embeddedFabricLogin` has succeeded for this load, the cached
39
- // session is the current user's and resuming is safe.
40
- if (hasEmbeddedHandoffCompleted()) {
33
+ // Steps 1-2: existing session or refresh. Skipped on a legacy host (no `?_fu=` hint) until a
34
+ // handoff has run this page load, because nothing server-side compared the session's identity
35
+ // against the current Fabric user. See ensureSignedInWithFabric for the full reasoning.
36
+ if (hasFabricUserHint() || hasEmbeddedHandoffCompleted()) {
41
37
  const resumed = await tryResumeSession(auth);
42
38
  if (resumed) {
43
39
  return resumed;
44
40
  }
45
41
  }
46
42
  else {
47
- console.debug('[FabricAuth:initEmbedded] First embedded call this page load — skipping session resume to avoid stale cross-user session');
43
+ console.debug('[FabricAuth:initEmbedded] Embedded host stamped no user hint — skipping session resume to avoid a stale cross-user session');
48
44
  }
49
45
  // Step 3: postMessage handoff (no popup, no window.open)
50
46
  console.debug('[FabricAuth:initEmbedded] Starting postMessage handoff flow');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/rayfin-auth-provider-fabric",
3
- "version": "1.35.0-alpha.1412",
3
+ "version": "1.35.0-beta.0",
4
4
  "description": "Fabric brokered authentication provider for Rayfin SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,9 +13,9 @@
13
13
  ],
14
14
  "type": "module",
15
15
  "dependencies": {
16
- "@microsoft/rayfin-auth": "1.35.0-alpha.1412",
17
- "@microsoft/rayfin-lib": "1.35.0-alpha.1412",
18
- "@microsoft/fabric-embedded-host": "1.35.0-alpha.1412"
16
+ "@microsoft/rayfin-auth": "1.35.0-beta.0",
17
+ "@microsoft/fabric-embedded-host": "1.35.0-beta.0",
18
+ "@microsoft/rayfin-lib": "1.35.0-beta.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "typescript": "^5.8.3",