@equinor/fusion-framework-module-msal 8.0.4 → 8.0.5

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.
@@ -1,4 +1,4 @@
1
- import { PublicClientApplication, type Configuration } from '@azure/msal-browser';
1
+ import { PublicClientApplication, type CacheLookupPolicy, type Configuration } from '@azure/msal-browser';
2
2
  import type { IMsalClient, AcquireTokenResult, LoginOptions, LogoutOptions, LoginResult, AcquireTokenOptions } from './MsalClient.interface';
3
3
  export type { IMsalClient };
4
4
  /**
@@ -17,6 +17,23 @@ export type MsalClientConfig = Configuration & {
17
17
  /** Optional tenant identifier for Azure AD tenant */
18
18
  tenantId?: string;
19
19
  };
20
+ /**
21
+ * Cache lookup policy applied to every `acquireTokenSilent` call.
22
+ *
23
+ * Controls whether MSAL falls back to a hidden iframe when the refresh token
24
+ * fails. When `undefined`, MSAL's built-in default applies (cache → refresh
25
+ * token → iframe). Set to `CacheLookupPolicy.AccessTokenAndRefreshToken` to
26
+ * skip the iframe step and fail immediately with `InteractionRequiredAuthError`
27
+ * when the refresh token is revoked — avoiding the ~10–20 s
28
+ * `monitor_window_timeout` delay.
29
+ *
30
+ * When using {@link MsalConfigurator}, this defaults to
31
+ * `CacheLookupPolicy.AccessTokenAndRefreshToken`. When constructing `MsalClient`
32
+ * directly, the field is optional and defaults to `undefined` (MSAL default).
33
+ *
34
+ * Per-request `cacheLookupPolicy` on `SilentRequest` takes precedence over this value.
35
+ */
36
+ cacheLookupPolicy?: CacheLookupPolicy;
20
37
  };
21
38
  /**
22
39
  * MSAL v4 client implementation with extended properties and methods.
@@ -3,6 +3,7 @@ import { BaseConfigBuilder } from '@equinor/fusion-framework-module';
3
3
  import type { IMsalProvider } from './MsalProvider.interface';
4
4
  import { type ITelemetryProvider } from '@equinor/fusion-framework-module-telemetry';
5
5
  import { type MsalClientConfig, type IMsalClient } from './MsalClient';
6
+ import { CacheLookupPolicy } from '@azure/msal-browser';
6
7
  /**
7
8
  * Telemetry configuration for MSAL module.
8
9
  *
@@ -65,6 +66,30 @@ export declare class MsalConfigurator extends BaseConfigBuilder<MsalConfig> {
65
66
  * ```
66
67
  */
67
68
  setClientConfig(config?: MsalClientConfig): this;
69
+ /**
70
+ * Sets the cache lookup policy used for every silent token acquisition.
71
+ *
72
+ * Controls whether MSAL falls back to a hidden iframe when the refresh token
73
+ * fails. Defaults to `CacheLookupPolicy.AccessTokenAndRefreshToken`, which skips
74
+ * the iframe step and fails immediately with `InteractionRequiredAuthError` when
75
+ * the refresh token is revoked — avoiding the ~10–20 s `monitor_window_timeout`
76
+ * delay caused by MSAL's built-in iframe fallback.
77
+ *
78
+ * Set to `CacheLookupPolicy.Default` to restore MSAL's full waterfall:
79
+ * cache → refresh token → iframe.
80
+ *
81
+ * @param policy - Cache lookup policy to apply
82
+ * @returns The configurator instance for method chaining
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * import { CacheLookupPolicy } from '@azure/msal-browser';
87
+ *
88
+ * // Restore MSAL's built-in iframe fallback (not recommended for most apps)
89
+ * configurator.setCacheLookupPolicy(CacheLookupPolicy.Default);
90
+ * ```
91
+ */
92
+ setCacheLookupPolicy(policy: CacheLookupPolicy | undefined): this;
68
93
  /**
69
94
  * Sets a backend-issued authorization code for token exchange.
70
95
  *
@@ -1 +1 @@
1
- export declare const version = "8.0.4";
1
+ export declare const version = "8.0.5";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equinor/fusion-framework-module-msal",
3
- "version": "8.0.4",
3
+ "version": "8.0.5",
4
4
  "description": "Microsoft Authentication Library (MSAL) integration module for Fusion Framework",
5
5
  "main": "dist/esm/index.js",
6
6
  "types": "dist/types/index.d.ts",
package/src/MsalClient.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  PublicClientApplication,
3
+ type CacheLookupPolicy,
3
4
  type SilentRequest,
4
5
  type Configuration,
5
6
  type EndSessionRequest,
@@ -40,6 +41,23 @@ export type MsalClientConfig = Configuration & {
40
41
  /** Optional tenant identifier for Azure AD tenant */
41
42
  tenantId?: string;
42
43
  };
44
+ /**
45
+ * Cache lookup policy applied to every `acquireTokenSilent` call.
46
+ *
47
+ * Controls whether MSAL falls back to a hidden iframe when the refresh token
48
+ * fails. When `undefined`, MSAL's built-in default applies (cache → refresh
49
+ * token → iframe). Set to `CacheLookupPolicy.AccessTokenAndRefreshToken` to
50
+ * skip the iframe step and fail immediately with `InteractionRequiredAuthError`
51
+ * when the refresh token is revoked — avoiding the ~10–20 s
52
+ * `monitor_window_timeout` delay.
53
+ *
54
+ * When using {@link MsalConfigurator}, this defaults to
55
+ * `CacheLookupPolicy.AccessTokenAndRefreshToken`. When constructing `MsalClient`
56
+ * directly, the field is optional and defaults to `undefined` (MSAL default).
57
+ *
58
+ * Per-request `cacheLookupPolicy` on `SilentRequest` takes precedence over this value.
59
+ */
60
+ cacheLookupPolicy?: CacheLookupPolicy;
43
61
  };
44
62
 
45
63
  /**
@@ -65,6 +83,7 @@ export type MsalClientConfig = Configuration & {
65
83
  export class MsalClient extends PublicClientApplication implements IMsalClient {
66
84
  #tenantId?: string;
67
85
  #clientId?: string;
86
+ #cacheLookupPolicy?: CacheLookupPolicy;
68
87
 
69
88
  /**
70
89
  * Creates a new MSAL client instance.
@@ -75,6 +94,7 @@ export class MsalClient extends PublicClientApplication implements IMsalClient {
75
94
  super(config);
76
95
  this.#tenantId = config.auth?.tenantId;
77
96
  this.#clientId = config.auth?.clientId;
97
+ this.#cacheLookupPolicy = config.cacheLookupPolicy;
78
98
  }
79
99
 
80
100
  /**
@@ -257,7 +277,11 @@ export class MsalClient extends PublicClientApplication implements IMsalClient {
257
277
  'Attempting to acquire token silently',
258
278
  request.correlationId || FUSION_CORRELATION_ID,
259
279
  );
260
- return await this.acquireTokenSilent(request as SilentRequest);
280
+ return await this.acquireTokenSilent({
281
+ // Instance-level policy is the default; request-level cacheLookupPolicy takes precedence
282
+ cacheLookupPolicy: this.#cacheLookupPolicy,
283
+ ...(request as SilentRequest),
284
+ });
261
285
  } catch {
262
286
  // Silent acquisition failed - fall back to interactive
263
287
  this.getLogger().warning(
@@ -8,7 +8,7 @@ import {
8
8
  } from '@equinor/fusion-framework-module-telemetry';
9
9
  import { MsalClient, type MsalClientConfig, type IMsalClient } from './MsalClient';
10
10
  import { createClientLogCallback } from './create-client-log-callback';
11
- import { LogLevel } from '@azure/msal-browser';
11
+ import { CacheLookupPolicy, LogLevel } from '@azure/msal-browser';
12
12
  import { version } from './version';
13
13
 
14
14
  /**
@@ -45,6 +45,13 @@ const MsalConfigSchema = z.object({
45
45
  redirectUri: z.string().optional(),
46
46
  loginHint: z.string().optional(),
47
47
  authCode: z.string().optional(),
48
+ cacheLookupPolicy: z
49
+ .custom<CacheLookupPolicy>(
50
+ (val) =>
51
+ typeof val === 'number' &&
52
+ Object.values(CacheLookupPolicy).includes(val as CacheLookupPolicy),
53
+ )
54
+ .optional(),
48
55
  version: z.string().transform((x: string) => String(semver.coerce(x))),
49
56
  telemetry: TelemetryConfigSchema,
50
57
  });
@@ -98,6 +105,8 @@ export class MsalConfigurator extends BaseConfigBuilder<MsalConfig> {
98
105
  return telemetry;
99
106
  }
100
107
  });
108
+ // Default cache lookup policy to AccessTokenAndRefreshToken to avoid iframe fallback delays
109
+ this._set('cacheLookupPolicy', async () => CacheLookupPolicy.AccessTokenAndRefreshToken);
101
110
  }
102
111
 
103
112
  /**
@@ -125,6 +134,34 @@ export class MsalConfigurator extends BaseConfigBuilder<MsalConfig> {
125
134
  return this;
126
135
  }
127
136
 
137
+ /**
138
+ * Sets the cache lookup policy used for every silent token acquisition.
139
+ *
140
+ * Controls whether MSAL falls back to a hidden iframe when the refresh token
141
+ * fails. Defaults to `CacheLookupPolicy.AccessTokenAndRefreshToken`, which skips
142
+ * the iframe step and fails immediately with `InteractionRequiredAuthError` when
143
+ * the refresh token is revoked — avoiding the ~10–20 s `monitor_window_timeout`
144
+ * delay caused by MSAL's built-in iframe fallback.
145
+ *
146
+ * Set to `CacheLookupPolicy.Default` to restore MSAL's full waterfall:
147
+ * cache → refresh token → iframe.
148
+ *
149
+ * @param policy - Cache lookup policy to apply
150
+ * @returns The configurator instance for method chaining
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * import { CacheLookupPolicy } from '@azure/msal-browser';
155
+ *
156
+ * // Restore MSAL's built-in iframe fallback (not recommended for most apps)
157
+ * configurator.setCacheLookupPolicy(CacheLookupPolicy.Default);
158
+ * ```
159
+ */
160
+ setCacheLookupPolicy(policy: CacheLookupPolicy | undefined): this {
161
+ this._set('cacheLookupPolicy', async () => policy);
162
+ return this;
163
+ }
164
+
128
165
  /**
129
166
  * Sets a backend-issued authorization code for token exchange.
130
167
  *
@@ -346,6 +383,11 @@ export class MsalConfigurator extends BaseConfigBuilder<MsalConfig> {
346
383
  },
347
384
  };
348
385
  }
386
+ // Apply silent cache lookup policy if configured
387
+ if (config.cacheLookupPolicy !== undefined) {
388
+ clientConfig.cacheLookupPolicy = config.cacheLookupPolicy;
389
+ }
390
+
349
391
  // Instantiate MSAL client with fully configured options
350
392
  config.client = new MsalClient(clientConfig);
351
393
  }
@@ -1,4 +1,5 @@
1
1
  import type { ConfigBuilderCallbackArgs } from '@equinor/fusion-framework-module';
2
+ import { CacheLookupPolicy } from '@azure/msal-browser';
2
3
  import { describe, expect, it, vi } from 'vitest';
3
4
  import type { IMsalClient } from '../MsalClient.interface';
4
5
  import { type MsalConfig, MsalConfigurator } from '../MsalConfigurator';
@@ -72,4 +73,44 @@ describe('MsalConfigurator', () => {
72
73
 
73
74
  expect(config.client).toBeUndefined();
74
75
  });
76
+
77
+ describe('cacheLookupPolicy', () => {
78
+ it('defaults to CacheLookupPolicy.AccessTokenAndRefreshToken', async () => {
79
+ const configurator = new MsalConfigurator();
80
+ configurator.setClient(createClient());
81
+
82
+ const config = await configurator.createConfigAsync(
83
+ createConfigCallbackArgs(),
84
+ createInitialConfig(),
85
+ );
86
+
87
+ expect(config.cacheLookupPolicy).toBe(CacheLookupPolicy.AccessTokenAndRefreshToken);
88
+ });
89
+
90
+ it('setCacheLookupPolicy(undefined) clears the policy so MSAL default applies', async () => {
91
+ const configurator = new MsalConfigurator();
92
+ configurator.setClient(createClient());
93
+ configurator.setCacheLookupPolicy(undefined);
94
+
95
+ const config = await configurator.createConfigAsync(
96
+ createConfigCallbackArgs(),
97
+ createInitialConfig(),
98
+ );
99
+
100
+ expect(config.cacheLookupPolicy).toBeUndefined();
101
+ });
102
+
103
+ it('setCacheLookupPolicy overrides the default', async () => {
104
+ const configurator = new MsalConfigurator();
105
+ configurator.setClient(createClient());
106
+ configurator.setCacheLookupPolicy(CacheLookupPolicy.Default);
107
+
108
+ const config = await configurator.createConfigAsync(
109
+ createConfigCallbackArgs(),
110
+ createInitialConfig(),
111
+ );
112
+
113
+ expect(config.cacheLookupPolicy).toBe(CacheLookupPolicy.Default);
114
+ });
115
+ });
75
116
  });
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '8.0.4';
2
+ export const version = '8.0.5';