@microsoft/rayfin-auth-provider-fabric 1.24.0 → 1.27.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.
- package/dist/embeddedFabricLogin.d.ts +8 -5
- package/dist/embeddedFabricLogin.js +27 -5
- package/dist/ensureSignedInWithFabric.d.ts +6 -1
- package/dist/ensureSignedInWithFabric.js +24 -7
- package/dist/fabricAuthHelpers.d.ts +18 -0
- package/dist/fabricAuthHelpers.js +42 -0
- package/dist/initEmbeddedAuth.d.ts +7 -5
- package/dist/initEmbeddedAuth.js +22 -10
- package/package.json +4 -4
|
@@ -4,11 +4,14 @@ import type { FabricAuthOptions } from './types';
|
|
|
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.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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.
|
|
9
|
+
* 2. Generates PKCE parameters in local variables (no `localStorage`).
|
|
10
|
+
* 3. Sends `auth.requestHandoff` to the parent via {@link requestHandoff}.
|
|
11
|
+
* 4. Receives the handoff code from the host's `AuthPlugin`.
|
|
12
|
+
* 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
15
|
*
|
|
13
16
|
* The session is stored in the iframe's own `localStorage`.
|
|
14
17
|
*
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { generateCodeVerifier, generateCodeChallenge, generateState, } from '@microsoft/rayfin-auth';
|
|
2
2
|
import { AuthError } from '@microsoft/rayfin-lib';
|
|
3
3
|
import { requestHandoff } from './PostMessageAuthTransport';
|
|
4
|
+
import { markEmbeddedHandoffCompleted } from './fabricAuthHelpers';
|
|
4
5
|
/**
|
|
5
6
|
* Embedded-mode Fabric login — acquires a session via postMessage to the
|
|
6
7
|
* parent Fabric Extension Host without opening a popup.
|
|
7
8
|
*
|
|
8
|
-
* 1.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* 1. Clears any prior Rayfin session in this iframe (`auth.signOut()`)
|
|
10
|
+
* so a stale session from a previous Fabric user cannot be reused.
|
|
11
|
+
* 2. Generates PKCE parameters in local variables (no `localStorage`).
|
|
12
|
+
* 3. Sends `auth.requestHandoff` to the parent via {@link requestHandoff}.
|
|
13
|
+
* 4. Receives the handoff code from the host's `AuthPlugin`.
|
|
14
|
+
* 5. Exchanges the handoff code for tokens via `exchangeVerificationCode`.
|
|
15
|
+
* 6. Creates a session via `createSessionFromTokenResponse` and marks
|
|
16
|
+
* embedded handoff as completed for this page load.
|
|
13
17
|
*
|
|
14
18
|
* The session is stored in the iframe's own `localStorage`.
|
|
15
19
|
*
|
|
@@ -21,6 +25,23 @@ export async function embeddedFabricLogin(auth, options) {
|
|
|
21
25
|
if (!options.returnOrigin) {
|
|
22
26
|
throw new AuthError('returnOrigin is required for embedded Fabric authentication.', 'MISSING_RETURN_ORIGIN');
|
|
23
27
|
}
|
|
28
|
+
// Embedded mode: the parent Fabric host is the source of truth for "who
|
|
29
|
+
// is signed in". Discard any prior Rayfin session that may have been
|
|
30
|
+
// left behind in this iframe's localStorage by a previous Fabric user
|
|
31
|
+
// BEFORE we ask for a fresh handoff. Without this, a Fabric user
|
|
32
|
+
// switch (User A → User B) would leave the SPA authenticated as
|
|
33
|
+
// User A because tryResumeSession would happily return the stale
|
|
34
|
+
// User A session and we would never reach the handoff path.
|
|
35
|
+
//
|
|
36
|
+
// Errors from signOut are swallowed: the stale token may already be
|
|
37
|
+
// invalid server-side, and the local clear (which signOut performs in
|
|
38
|
+
// its own catch block) is what actually matters here.
|
|
39
|
+
try {
|
|
40
|
+
await auth.signOut();
|
|
41
|
+
}
|
|
42
|
+
catch (signOutError) {
|
|
43
|
+
console.debug('[FabricAuth:embedded] Pre-handoff signOut failed (non-fatal); local session has been cleared', signOutError);
|
|
44
|
+
}
|
|
24
45
|
// PKCE in local variables only — not persisted to localStorage.
|
|
25
46
|
const codeVerifier = generateCodeVerifier();
|
|
26
47
|
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
@@ -50,6 +71,7 @@ export async function embeddedFabricLogin(auth, options) {
|
|
|
50
71
|
redirectUri,
|
|
51
72
|
});
|
|
52
73
|
auth.createSessionFromTokenResponse(tokenResponse);
|
|
74
|
+
markEmbeddedHandoffCompleted();
|
|
53
75
|
console.debug('[FabricAuth:embedded] Session established');
|
|
54
76
|
}
|
|
55
77
|
//# sourceMappingURL=embeddedFabricLogin.js.map
|
|
@@ -6,11 +6,16 @@ import type { FabricAuthOptions } from './types';
|
|
|
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.
|
|
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.
|
|
10
12
|
* 2. **Refresh token** — if a refresh token is available, attempt `auth.refreshSession()`.
|
|
11
13
|
* Return the refreshed session on success; continue on failure.
|
|
14
|
+
* Also skipped on the first embedded call per page load.
|
|
12
15
|
* 3. **Embedded mode** — if running inside a Fabric iframe (`fabricEmbedded=true`),
|
|
13
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.
|
|
14
19
|
* 4. **Open Fabric broker** — no existing auth path available. Open the Fabric Portal
|
|
15
20
|
* in a new tab via `initiateFabricLogin()` and wait for the Fabric extension to post
|
|
16
21
|
* the handoff code via `postMessage`. The function exchanges the code internally
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AuthError } from '@microsoft/rayfin-lib';
|
|
2
2
|
import { embeddedFabricLogin } from './embeddedFabricLogin';
|
|
3
|
-
import { isEmbeddedMode, tryResumeSession } from './fabricAuthHelpers';
|
|
3
|
+
import { hasEmbeddedHandoffCompleted, isEmbeddedMode, tryResumeSession, } from './fabricAuthHelpers';
|
|
4
4
|
import { initiateFabricLogin } from './initiateFabricLogin';
|
|
5
5
|
/**
|
|
6
6
|
* Ensures the user is signed in via Fabric brokered authentication.
|
|
@@ -8,11 +8,16 @@ import { initiateFabricLogin } from './initiateFabricLogin';
|
|
|
8
8
|
* Implements a multi-step waterfall — the first step that succeeds short-circuits the rest:
|
|
9
9
|
*
|
|
10
10
|
* 1. **Already authenticated** — if `auth.getSession().isAuthenticated` is true,
|
|
11
|
-
* return the existing session immediately.
|
|
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
14
|
* 2. **Refresh token** — if a refresh token is available, attempt `auth.refreshSession()`.
|
|
13
15
|
* Return the refreshed session on success; continue on failure.
|
|
16
|
+
* Also skipped on the first embedded call per page load.
|
|
14
17
|
* 3. **Embedded mode** — if running inside a Fabric iframe (`fabricEmbedded=true`),
|
|
15
18
|
* 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.
|
|
16
21
|
* 4. **Open Fabric broker** — no existing auth path available. Open the Fabric Portal
|
|
17
22
|
* in a new tab via `initiateFabricLogin()` and wait for the Fabric extension to post
|
|
18
23
|
* the handoff code via `postMessage`. The function exchanges the code internally
|
|
@@ -28,13 +33,25 @@ import { initiateFabricLogin } from './initiateFabricLogin';
|
|
|
28
33
|
* @throws {AuthError} If all steps fail or the broker tab is blocked.
|
|
29
34
|
*/
|
|
30
35
|
export async function ensureSignedInWithFabric(auth, options) {
|
|
31
|
-
// Steps 1-2: existing session or refresh
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
// Steps 1-2: existing session or refresh.
|
|
37
|
+
// In embedded mode we MUST skip this on the first call per page load —
|
|
38
|
+
// the session in iframe localStorage may belong to a previously
|
|
39
|
+
// signed-in Fabric user. After the first successful handoff this load
|
|
40
|
+
// (`hasEmbeddedHandoffCompleted()` is true) the cached session is the
|
|
41
|
+
// current user's, so resuming is safe and avoids redundant handoffs.
|
|
42
|
+
const inEmbeddedMode = isEmbeddedMode(options);
|
|
43
|
+
const skipResume = inEmbeddedMode && !hasEmbeddedHandoffCompleted();
|
|
44
|
+
if (!skipResume) {
|
|
45
|
+
const resumed = await tryResumeSession(auth);
|
|
46
|
+
if (resumed) {
|
|
47
|
+
return resumed;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.debug('[FabricAuth] Embedded mode first load — skipping session resume to avoid stale cross-user session');
|
|
35
52
|
}
|
|
36
53
|
// Step 3: Embedded mode — postMessage auth via parent iframe host
|
|
37
|
-
if (
|
|
54
|
+
if (inEmbeddedMode) {
|
|
38
55
|
console.debug('[FabricAuth] Embedded mode detected, using postMessage auth');
|
|
39
56
|
try {
|
|
40
57
|
await embeddedFabricLogin(auth, options);
|
|
@@ -10,6 +10,24 @@ import type { FabricAuthOptions } from './types';
|
|
|
10
10
|
* `EmbeddedModeOptions` interface.
|
|
11
11
|
*/
|
|
12
12
|
export declare function isEmbeddedMode(options: FabricAuthOptions): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Returns whether an embedded postMessage handoff has already completed
|
|
15
|
+
* during the current page load.
|
|
16
|
+
*/
|
|
17
|
+
export declare function hasEmbeddedHandoffCompleted(): boolean;
|
|
18
|
+
/**
|
|
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).
|
|
22
|
+
*/
|
|
23
|
+
export declare function markEmbeddedHandoffCompleted(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Test-only helper that clears the embedded-handoff completion flag.
|
|
26
|
+
* Not exported from the package barrel.
|
|
27
|
+
*
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export declare function resetEmbeddedHandoffStateForTests(): void;
|
|
13
31
|
/**
|
|
14
32
|
* Attempts to resume an existing session without any user interaction.
|
|
15
33
|
*
|
|
@@ -11,6 +11,48 @@ import { isEmbeddedMode as sharedIsEmbeddedMode } from '@microsoft/fabric-embedd
|
|
|
11
11
|
export function isEmbeddedMode(options) {
|
|
12
12
|
return sharedIsEmbeddedMode(options);
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Module-level flag tracking whether an embedded postMessage handoff has
|
|
16
|
+
* already completed during the current page load.
|
|
17
|
+
*
|
|
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.
|
|
23
|
+
*
|
|
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.
|
|
30
|
+
*/
|
|
31
|
+
let embeddedHandoffCompletedThisLoad = false;
|
|
32
|
+
/**
|
|
33
|
+
* Returns whether an embedded postMessage handoff has already completed
|
|
34
|
+
* during the current page load.
|
|
35
|
+
*/
|
|
36
|
+
export function hasEmbeddedHandoffCompleted() {
|
|
37
|
+
return embeddedHandoffCompletedThisLoad;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
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).
|
|
43
|
+
*/
|
|
44
|
+
export function markEmbeddedHandoffCompleted() {
|
|
45
|
+
embeddedHandoffCompletedThisLoad = true;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Test-only helper that clears the embedded-handoff completion flag.
|
|
49
|
+
* Not exported from the package barrel.
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
export function resetEmbeddedHandoffStateForTests() {
|
|
54
|
+
embeddedHandoffCompletedThisLoad = false;
|
|
55
|
+
}
|
|
14
56
|
/**
|
|
15
57
|
* Attempts to resume an existing session without any user interaction.
|
|
16
58
|
*
|
|
@@ -11,11 +11,13 @@ import type { FabricAuthOptions } from './types';
|
|
|
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
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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).
|
|
19
21
|
*
|
|
20
22
|
* Apps that also support the popup flow should continue to call
|
|
21
23
|
* {@link ensureSignedInWithFabric} from a user-gesture handler for the
|
package/dist/initEmbeddedAuth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { embeddedFabricLogin } from './embeddedFabricLogin';
|
|
2
|
-
import { isEmbeddedMode, tryResumeSession } from './fabricAuthHelpers';
|
|
2
|
+
import { hasEmbeddedHandoffCompleted, isEmbeddedMode, tryResumeSession, } from './fabricAuthHelpers';
|
|
3
3
|
/**
|
|
4
4
|
* Initializes embedded Fabric authentication if running inside an iframe
|
|
5
5
|
* with `?fabricEmbedded=true`.
|
|
@@ -11,11 +11,13 @@ import { isEmbeddedMode, tryResumeSession } from './fabricAuthHelpers';
|
|
|
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
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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).
|
|
19
21
|
*
|
|
20
22
|
* Apps that also support the popup flow should continue to call
|
|
21
23
|
* {@link ensureSignedInWithFabric} from a user-gesture handler for the
|
|
@@ -29,10 +31,20 @@ export async function initEmbeddedAuth(auth, options) {
|
|
|
29
31
|
if (!isEmbeddedMode(options)) {
|
|
30
32
|
return null;
|
|
31
33
|
}
|
|
32
|
-
// Steps 1-2: existing session or refresh
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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()) {
|
|
41
|
+
const resumed = await tryResumeSession(auth);
|
|
42
|
+
if (resumed) {
|
|
43
|
+
return resumed;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.debug('[FabricAuth:initEmbedded] First embedded call this page load — skipping session resume to avoid stale cross-user session');
|
|
36
48
|
}
|
|
37
49
|
// Step 3: postMessage handoff (no popup, no window.open)
|
|
38
50
|
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.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"description": "Fabric brokered authentication provider for Rayfin SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
],
|
|
13
13
|
"type": "module",
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@microsoft/
|
|
16
|
-
"@microsoft/
|
|
17
|
-
"@microsoft/rayfin-lib": "1.
|
|
15
|
+
"@microsoft/fabric-embedded-host": "1.27.0",
|
|
16
|
+
"@microsoft/rayfin-auth": "1.27.0",
|
|
17
|
+
"@microsoft/rayfin-lib": "1.27.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"typescript": "^5.8.3",
|