@equinor/fusion-framework-module-msal 7.2.0 → 7.2.2

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.
@@ -3,7 +3,7 @@ import { TelemetryLevel } from '@equinor/fusion-framework-module-telemetry';
3
3
  import { BaseModuleProvider } from '@equinor/fusion-framework-module/provider';
4
4
  import type { MsalConfig } from './MsalConfigurator';
5
5
  import type { AcquireTokenOptionsLegacy, IMsalProvider } from './MsalProvider.interface';
6
- import type { AcquireTokenResult, IMsalClient, LoginOptions, LoginResult, LogoutOptions } from './MsalClient.interface';
6
+ import type { AcquireTokenOptions, AcquireTokenResult, IMsalClient, LoginOptions, LoginResult, LogoutOptions } from './MsalClient.interface';
7
7
  import type { AccountInfo, AuthenticationResult } from './types';
8
8
  import type { MsalModuleVersion } from './static';
9
9
  export type { IMsalProvider };
@@ -33,6 +33,12 @@ export type { IMsalProvider };
33
33
  */
34
34
  export declare class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsalProvider {
35
35
  #private;
36
+ /**
37
+ * Default OAuth scopes used when the caller provides no scopes.
38
+ *
39
+ * Resolves to the app's Entra ID configured permissions via the `/.default` scope.
40
+ */
41
+ get defaultScopes(): string[];
36
42
  /**
37
43
  * The MSAL module version enum value indicating the API compatibility level.
38
44
  *
@@ -106,7 +112,7 @@ export declare class MsalProvider extends BaseModuleProvider<MsalConfig> impleme
106
112
  * }
107
113
  * ```
108
114
  */
109
- acquireAccessToken(options: AcquireTokenOptionsLegacy): Promise<string | undefined>;
115
+ acquireAccessToken(options?: AcquireTokenOptions | AcquireTokenOptionsLegacy): Promise<string | undefined>;
110
116
  /**
111
117
  * Acquire full authentication result for the specified scopes
112
118
  *
@@ -136,7 +142,7 @@ export declare class MsalProvider extends BaseModuleProvider<MsalConfig> impleme
136
142
  * });
137
143
  * ```
138
144
  */
139
- acquireToken(options: AcquireTokenOptionsLegacy): Promise<AcquireTokenResult>;
145
+ acquireToken(options?: AcquireTokenOptions | AcquireTokenOptionsLegacy): Promise<AcquireTokenResult>;
140
146
  /**
141
147
  * Authenticates a user using Microsoft Authentication Library.
142
148
  *
@@ -83,7 +83,7 @@ export interface IMsalProvider extends IProxyProvider {
83
83
  * });
84
84
  * ```
85
85
  */
86
- acquireAccessToken(options: AcquireTokenOptionsLegacy): Promise<string | undefined>;
86
+ acquireAccessToken(options?: AcquireTokenOptions | AcquireTokenOptionsLegacy): Promise<string | undefined>;
87
87
  /**
88
88
  * Acquires a full authentication result including token and account information.
89
89
  *
@@ -101,7 +101,7 @@ export interface IMsalProvider extends IProxyProvider {
101
101
  * });
102
102
  * ```
103
103
  */
104
- acquireToken(options: AcquireTokenOptionsLegacy): Promise<AcquireTokenResult>;
104
+ acquireToken(options?: AcquireTokenOptions | AcquireTokenOptionsLegacy): Promise<AcquireTokenResult>;
105
105
  /**
106
106
  * Authenticates a user interactively with Microsoft Identity Platform.
107
107
  *
@@ -1,7 +1,6 @@
1
- import { type Module, type IModulesConfigurator } from '@equinor/fusion-framework-module';
1
+ import { type Module, type IModulesConfigurator, type ModuleConfigType } from '@equinor/fusion-framework-module';
2
2
  import { MsalConfigurator } from './MsalConfigurator';
3
3
  import { type IMsalProvider } from './MsalProvider';
4
- import type { MsalClientConfig } from './MsalClient';
5
4
  /**
6
5
  * MSAL authentication module configuration.
7
6
  *
@@ -28,23 +27,7 @@ export declare const module: MsalModule;
28
27
  * This function receives a builder object with methods to configure the MSAL client
29
28
  * and authentication requirements.
30
29
  */
31
- export type AuthConfigFn = (builder: {
32
- /**
33
- * Set MSAL client configuration
34
- * @param config - Client configuration with tenant ID, client ID, etc.
35
- */
36
- setClientConfig: (config: MsalClientConfig) => void;
37
- /**
38
- * Set whether authentication is required for the application
39
- * @param requiresAuth - If true, app will attempt automatic login on initialization
40
- */
41
- setRequiresAuth: (requiresAuth: boolean) => void;
42
- /**
43
- * Set a default login hint used for silent SSO and pre-filled usernames
44
- * @param loginHint - Preferred username/email to use for login hint
45
- */
46
- setLoginHint: (loginHint: string) => void;
47
- }) => void;
30
+ export type AuthConfigFn<TRef = unknown> = (configurator: ModuleConfigType<MsalModule>, ref?: TRef) => void;
48
31
  /**
49
32
  * Enables MSAL authentication module in the framework.
50
33
  *
@@ -81,7 +64,7 @@ export declare const enableMSAL: (configurator: IModulesConfigurator<any, any>,
81
64
  */
82
65
  export declare const configureMsal: (configure: AuthConfigFn) => {
83
66
  module: MsalModule;
84
- configure: AuthConfigFn;
67
+ configure: AuthConfigFn<unknown>;
85
68
  };
86
69
  declare module '@equinor/fusion-framework-module' {
87
70
  interface Modules {
@@ -1 +1 @@
1
- export declare const version = "7.2.0";
1
+ export declare const version = "7.2.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equinor/fusion-framework-module-msal",
3
- "version": "7.2.0",
3
+ "version": "7.2.2",
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",
@@ -58,8 +58,8 @@
58
58
  "semver": "^7.5.4",
59
59
  "typescript": "^5.8.2",
60
60
  "zod": "^4.1.8",
61
- "@equinor/fusion-framework-module": "^5.0.5",
62
- "@equinor/fusion-framework-module-telemetry": "^4.6.3"
61
+ "@equinor/fusion-framework-module-telemetry": "^4.6.3",
62
+ "@equinor/fusion-framework-module": "^5.0.5"
63
63
  },
64
64
  "peerDependenciesMeta": {
65
65
  "@equinor/fusion-framework-module-telemetry": {
@@ -95,7 +95,9 @@ export interface IMsalProvider extends IProxyProvider {
95
95
  * });
96
96
  * ```
97
97
  */
98
- acquireAccessToken(options: AcquireTokenOptionsLegacy): Promise<string | undefined>;
98
+ acquireAccessToken(
99
+ options?: AcquireTokenOptions | AcquireTokenOptionsLegacy,
100
+ ): Promise<string | undefined>;
99
101
 
100
102
  /**
101
103
  * Acquires a full authentication result including token and account information.
@@ -114,7 +116,9 @@ export interface IMsalProvider extends IProxyProvider {
114
116
  * });
115
117
  * ```
116
118
  */
117
- acquireToken(options: AcquireTokenOptionsLegacy): Promise<AcquireTokenResult>;
119
+ acquireToken(
120
+ options?: AcquireTokenOptions | AcquireTokenOptionsLegacy,
121
+ ): Promise<AcquireTokenResult>;
118
122
 
119
123
  /**
120
124
  * Authenticates a user interactively with Microsoft Identity Platform.
@@ -63,6 +63,16 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
63
63
  #authCode?: string;
64
64
  #loginHint?: string;
65
65
 
66
+ /**
67
+ * Default OAuth scopes used when the caller provides no scopes.
68
+ *
69
+ * Resolves to the app's Entra ID configured permissions via the `/.default` scope.
70
+ */
71
+ get defaultScopes(): string[] {
72
+ const clientId = this.#client.clientId;
73
+ return clientId ? [`${clientId}/.default`] : [];
74
+ }
75
+
66
76
  /**
67
77
  * The MSAL module version enum value indicating the API compatibility level.
68
78
  *
@@ -236,9 +246,9 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
236
246
  } else if (!this.#client.hasValidClaims) {
237
247
  // Priority 2: No valid session found - attempt automatic login
238
248
  // This handles first-time app load when no authentication state exists
239
- // Note: Using empty scopes here as we don't know what scopes the app needs yet
249
+ // Note: Using default scopes here as we don't know what scopes the app needs yet
240
250
  // App should call acquireToken with actual scopes after initialization
241
- const loginResult = await this.login({ request: { scopes: [] } });
251
+ const loginResult = await this.login({ request: { scopes: this.defaultScopes } });
242
252
  if (loginResult?.account) {
243
253
  // Automatic login successful - set as active account
244
254
  this.#client.setActiveAccount(loginResult.account);
@@ -271,7 +281,9 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
271
281
  * }
272
282
  * ```
273
283
  */
274
- async acquireAccessToken(options: AcquireTokenOptionsLegacy): Promise<string | undefined> {
284
+ async acquireAccessToken(
285
+ options?: AcquireTokenOptions | AcquireTokenOptionsLegacy,
286
+ ): Promise<string | undefined> {
275
287
  const { accessToken } = (await this.acquireToken(options)) ?? {};
276
288
  return accessToken;
277
289
  }
@@ -305,47 +317,68 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
305
317
  * });
306
318
  * ```
307
319
  */
308
- async acquireToken(options: AcquireTokenOptionsLegacy): Promise<AcquireTokenResult> {
309
- const {
310
- behavior = 'redirect',
311
- silent = true,
312
- request = {} as AcquireTokenOptions['request'],
313
- } = options;
320
+ async acquireToken(
321
+ options?: AcquireTokenOptions | AcquireTokenOptionsLegacy,
322
+ ): Promise<AcquireTokenResult> {
323
+ // Determine behavior and silent options, with defaults (redirect and true respectively)
324
+ const behavior = options?.behavior ?? 'redirect';
325
+
326
+ // Silent mode defaults to true, meaning the provider will attempt silent token acquisition first
327
+ const silent = options?.silent ?? true;
328
+
329
+ const defaultScopes = this.defaultScopes;
330
+
331
+ const inputRequest = options?.request;
332
+
333
+ // Determine the account to use for token acquisition, prioritizing request-specific account, then active account
334
+ const account = inputRequest?.account ?? this.account ?? undefined;
335
+
336
+ // Extract caller-provided scopes from either new format (request.scopes) or legacy format (scopes)
337
+ const candidateScopes =
338
+ inputRequest?.scopes ?? (options as AcquireTokenOptionsLegacy)?.scopes ?? [];
314
339
 
315
- const account = request.account ?? this.account ?? undefined;
316
- // Extract scopes from either new format (request.scopes) or legacy format (scopes)
317
- const scopes = options.request?.scopes ?? options?.scopes ?? [];
340
+ const scopes =
341
+ candidateScopes.length > 0 ? candidateScopes : defaultScopes.length > 0 ? defaultScopes : [];
318
342
 
343
+ // Prepare telemetry properties for this token acquisition attempt
319
344
  const telemetryProperties = { behavior, silent, scopes };
320
345
 
321
346
  // Track usage of deprecated legacy scopes format for migration monitoring
322
- if (options.scopes) {
347
+ if ((options as AcquireTokenOptionsLegacy)?.scopes) {
323
348
  this._trackEvent('acquireToken.legacy-scopes-provided', TelemetryLevel.Warning, {
324
349
  properties: telemetryProperties,
325
350
  });
326
351
  }
327
352
 
328
353
  // Handle empty scopes - currently monitoring for telemetry, will throw in future
329
- if (scopes.length === 0) {
330
- const exception = new Error('Empty scopes provided, not allowed');
331
- this._trackException('acquireToken.missing-scope', TelemetryLevel.Warning, {
332
- exception,
333
- properties: telemetryProperties,
334
- });
335
- // TODO: throw exception when sufficient metrics are collected
336
- // This allows us to monitor how often empty scopes are provided before enforcing validation
354
+ if (candidateScopes.length === 0) {
355
+ if (defaultScopes.length > 0) {
356
+ this._trackEvent('acquireToken.missing-scope.defaulted', TelemetryLevel.Warning, {
357
+ properties: { ...telemetryProperties, defaultScopes },
358
+ });
359
+ } else {
360
+ const exception = new Error(
361
+ 'Empty scopes provided and clientId is missing for default scope',
362
+ );
363
+ this._trackException('acquireToken.missing-scope', TelemetryLevel.Warning, {
364
+ exception,
365
+ properties: telemetryProperties,
366
+ });
367
+ // TODO: throw exception when sufficient metrics are collected
368
+ // This allows us to monitor how often empty scopes are provided before enforcing validation
369
+ }
337
370
  }
338
371
 
339
372
  try {
340
373
  const measurement = this._trackMeasurement('acquireToken', TelemetryLevel.Information, {
341
374
  properties: telemetryProperties,
342
375
  });
343
- // Merge account, original request options, and resolved scopes
344
- // Account ensures context awareness, request preserves custom options, scopes uses resolved value
376
+ // Merge account, original request options, and resolved scopes.
377
+ // Account ensures context awareness, request preserves custom options, scopes uses resolved value.
345
378
  const result = await this.#client.acquireToken({
346
379
  behavior,
347
380
  silent,
348
- request: { ...options.request, account, scopes },
381
+ request: { ...inputRequest, account, scopes },
349
382
  });
350
383
  measurement?.measure();
351
384
  return result;
@@ -411,6 +444,13 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
411
444
  request.loginHint ??=
412
445
  this.#loginHint ?? this.account?.username ?? this.account?.loginHint ?? undefined;
413
446
 
447
+ const defaultScopes = this.defaultScopes;
448
+
449
+ // Fallback to app default scope when possible; empty scopes tracked for monitoring
450
+ if (!request.scopes || request.scopes.length === 0) {
451
+ request.scopes = defaultScopes.length > 0 ? defaultScopes : [];
452
+ }
453
+
414
454
  // Determine if silent login is possible based on available account/hint information
415
455
  // Silent login requires either an account object or a loginHint to work
416
456
  const canLoginSilently = silent && (request.account || request.loginHint);
@@ -421,10 +461,9 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
421
461
  // This allows silent login to work automatically with existing authentication state
422
462
  request.account ??= this.account ?? undefined;
423
463
 
424
- // Default to empty scopes if none provided
425
- // Empty scopes are tracked for monitoring but allowed for compatibility
426
- if (!request.scopes) {
427
- request.scopes = [];
464
+ // If scopes are still empty here, we couldn't derive a default scope (e.g. missing clientId).
465
+ // Track for monitoring; behavior will be enforced once we have sufficient metrics.
466
+ if (request.scopes.length === 0) {
428
467
  this._trackEvent('login.missing-scope', TelemetryLevel.Warning, {
429
468
  properties: telemetryProperties,
430
469
  });
package/src/module.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  type Module,
3
3
  type IModulesConfigurator,
4
+ type ModuleConfigType,
4
5
  SemanticVersion,
5
6
  } from '@equinor/fusion-framework-module';
6
7
 
7
8
  import { MsalConfigurator } from './MsalConfigurator';
8
9
  import { MsalProvider, type IMsalProvider } from './MsalProvider';
9
- import type { MsalClientConfig } from './MsalClient';
10
10
 
11
11
  import { version } from './version';
12
12
 
@@ -80,23 +80,10 @@ export const module: MsalModule = {
80
80
  * This function receives a builder object with methods to configure the MSAL client
81
81
  * and authentication requirements.
82
82
  */
83
- export type AuthConfigFn = (builder: {
84
- /**
85
- * Set MSAL client configuration
86
- * @param config - Client configuration with tenant ID, client ID, etc.
87
- */
88
- setClientConfig: (config: MsalClientConfig) => void;
89
- /**
90
- * Set whether authentication is required for the application
91
- * @param requiresAuth - If true, app will attempt automatic login on initialization
92
- */
93
- setRequiresAuth: (requiresAuth: boolean) => void;
94
- /**
95
- * Set a default login hint used for silent SSO and pre-filled usernames
96
- * @param loginHint - Preferred username/email to use for login hint
97
- */
98
- setLoginHint: (loginHint: string) => void;
99
- }) => void;
83
+ export type AuthConfigFn<TRef = unknown> = (
84
+ configurator: ModuleConfigType<MsalModule>,
85
+ ref?: TRef,
86
+ ) => void;
100
87
 
101
88
  /**
102
89
  * Enables MSAL authentication module in the framework.
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '7.2.0';
2
+ export const version = '7.2.2';