@equinor/fusion-framework-module-msal 7.1.0 → 7.2.1
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/CHANGELOG.md +31 -0
- package/dist/esm/MsalConfigurator.js +19 -0
- package/dist/esm/MsalConfigurator.js.map +1 -1
- package/dist/esm/MsalProvider.js +54 -22
- package/dist/esm/MsalProvider.js.map +1 -1
- package/dist/esm/module.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/MsalConfigurator.d.ts +15 -0
- package/dist/types/MsalProvider.d.ts +9 -3
- package/dist/types/MsalProvider.interface.d.ts +2 -2
- package/dist/types/module.d.ts +5 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +3 -3
- package/src/MsalConfigurator.ts +20 -0
- package/src/MsalProvider.interface.ts +6 -2
- package/src/MsalProvider.ts +72 -28
- package/src/module.ts +5 -0
- package/src/version.ts +1 -1
|
@@ -113,6 +113,21 @@ export declare class MsalConfigurator extends BaseConfigBuilder<MsalConfig> {
|
|
|
113
113
|
* ```
|
|
114
114
|
*/
|
|
115
115
|
setRequiresAuth(requiresAuth: boolean): this;
|
|
116
|
+
/**
|
|
117
|
+
* Sets a default login hint for authentication flows.
|
|
118
|
+
*
|
|
119
|
+
* The login hint is used to pre-fill the username during authentication and
|
|
120
|
+
* enables silent SSO when no account is available.
|
|
121
|
+
*
|
|
122
|
+
* @param loginHint - The preferred username/email to use as login hint
|
|
123
|
+
* @returns The configurator instance for method chaining
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* configurator.setLoginHint('user@company.com');
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
setLoginHint(loginHint?: string): this;
|
|
116
131
|
/**
|
|
117
132
|
* @deprecated - since version 5.1.0, use setClient instead
|
|
118
133
|
*/
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
104
|
+
acquireToken(options?: AcquireTokenOptions | AcquireTokenOptionsLegacy): Promise<AcquireTokenResult>;
|
|
105
105
|
/**
|
|
106
106
|
* Authenticates a user interactively with Microsoft Identity Platform.
|
|
107
107
|
*
|
package/dist/types/module.d.ts
CHANGED
|
@@ -39,6 +39,11 @@ export type AuthConfigFn = (builder: {
|
|
|
39
39
|
* @param requiresAuth - If true, app will attempt automatic login on initialization
|
|
40
40
|
*/
|
|
41
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;
|
|
42
47
|
}) => void;
|
|
43
48
|
/**
|
|
44
49
|
* Enables MSAL authentication module in the framework.
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "7.1
|
|
1
|
+
export declare const version = "7.2.1";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@equinor/fusion-framework-module-msal",
|
|
3
|
-
"version": "7.1
|
|
3
|
+
"version": "7.2.1",
|
|
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": "^
|
|
62
|
-
"@equinor/fusion-framework-module
|
|
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": {
|
package/src/MsalConfigurator.ts
CHANGED
|
@@ -43,6 +43,7 @@ const MsalConfigSchema = z.object({
|
|
|
43
43
|
provider: z.custom<IMsalProvider>().optional(),
|
|
44
44
|
requiresAuth: z.boolean().optional(),
|
|
45
45
|
redirectUri: z.string().optional(),
|
|
46
|
+
loginHint: z.string().optional(),
|
|
46
47
|
authCode: z.string().optional(),
|
|
47
48
|
version: z.string().transform((x: string) => String(semver.coerce(x))),
|
|
48
49
|
telemetry: TelemetryConfigSchema,
|
|
@@ -180,6 +181,25 @@ export class MsalConfigurator extends BaseConfigBuilder<MsalConfig> {
|
|
|
180
181
|
return this;
|
|
181
182
|
}
|
|
182
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Sets a default login hint for authentication flows.
|
|
186
|
+
*
|
|
187
|
+
* The login hint is used to pre-fill the username during authentication and
|
|
188
|
+
* enables silent SSO when no account is available.
|
|
189
|
+
*
|
|
190
|
+
* @param loginHint - The preferred username/email to use as login hint
|
|
191
|
+
* @returns The configurator instance for method chaining
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```typescript
|
|
195
|
+
* configurator.setLoginHint('user@company.com');
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
setLoginHint(loginHint?: string): this {
|
|
199
|
+
this._set('loginHint', async () => loginHint);
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
|
|
183
203
|
/**
|
|
184
204
|
* @deprecated - since version 5.1.0, use setClient instead
|
|
185
205
|
*/
|
|
@@ -95,7 +95,9 @@ export interface IMsalProvider extends IProxyProvider {
|
|
|
95
95
|
* });
|
|
96
96
|
* ```
|
|
97
97
|
*/
|
|
98
|
-
acquireAccessToken(
|
|
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(
|
|
119
|
+
acquireToken(
|
|
120
|
+
options?: AcquireTokenOptions | AcquireTokenOptionsLegacy,
|
|
121
|
+
): Promise<AcquireTokenResult>;
|
|
118
122
|
|
|
119
123
|
/**
|
|
120
124
|
* Authenticates a user interactively with Microsoft Identity Platform.
|
package/src/MsalProvider.ts
CHANGED
|
@@ -61,6 +61,17 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
|
|
|
61
61
|
};
|
|
62
62
|
#requiresAuth?: boolean;
|
|
63
63
|
#authCode?: string;
|
|
64
|
+
#loginHint?: string;
|
|
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
|
+
}
|
|
64
75
|
|
|
65
76
|
/**
|
|
66
77
|
* The MSAL module version enum value indicating the API compatibility level.
|
|
@@ -127,6 +138,7 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
|
|
|
127
138
|
});
|
|
128
139
|
this.#requiresAuth = config.requiresAuth;
|
|
129
140
|
this.#telemetry = config.telemetry;
|
|
141
|
+
this.#loginHint = config.loginHint;
|
|
130
142
|
|
|
131
143
|
// Extract auth code from config if present
|
|
132
144
|
// This will be used during initialize to exchange for tokens
|
|
@@ -234,9 +246,9 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
|
|
|
234
246
|
} else if (!this.#client.hasValidClaims) {
|
|
235
247
|
// Priority 2: No valid session found - attempt automatic login
|
|
236
248
|
// This handles first-time app load when no authentication state exists
|
|
237
|
-
// Note: Using
|
|
249
|
+
// Note: Using default scopes here as we don't know what scopes the app needs yet
|
|
238
250
|
// App should call acquireToken with actual scopes after initialization
|
|
239
|
-
const loginResult = await this.login({ request: { scopes:
|
|
251
|
+
const loginResult = await this.login({ request: { scopes: this.defaultScopes } });
|
|
240
252
|
if (loginResult?.account) {
|
|
241
253
|
// Automatic login successful - set as active account
|
|
242
254
|
this.#client.setActiveAccount(loginResult.account);
|
|
@@ -269,7 +281,9 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
|
|
|
269
281
|
* }
|
|
270
282
|
* ```
|
|
271
283
|
*/
|
|
272
|
-
async acquireAccessToken(
|
|
284
|
+
async acquireAccessToken(
|
|
285
|
+
options?: AcquireTokenOptions | AcquireTokenOptionsLegacy,
|
|
286
|
+
): Promise<string | undefined> {
|
|
273
287
|
const { accessToken } = (await this.acquireToken(options)) ?? {};
|
|
274
288
|
return accessToken;
|
|
275
289
|
}
|
|
@@ -303,47 +317,68 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
|
|
|
303
317
|
* });
|
|
304
318
|
* ```
|
|
305
319
|
*/
|
|
306
|
-
async acquireToken(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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;
|
|
312
328
|
|
|
313
|
-
const
|
|
314
|
-
// Extract scopes from either new format (request.scopes) or legacy format (scopes)
|
|
315
|
-
const scopes = options.request?.scopes ?? options?.scopes ?? [];
|
|
329
|
+
const defaultScopes = this.defaultScopes;
|
|
316
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 ?? [];
|
|
339
|
+
|
|
340
|
+
const scopes =
|
|
341
|
+
candidateScopes.length > 0 ? candidateScopes : defaultScopes.length > 0 ? defaultScopes : [];
|
|
342
|
+
|
|
343
|
+
// Prepare telemetry properties for this token acquisition attempt
|
|
317
344
|
const telemetryProperties = { behavior, silent, scopes };
|
|
318
345
|
|
|
319
346
|
// Track usage of deprecated legacy scopes format for migration monitoring
|
|
320
|
-
if (options
|
|
347
|
+
if ((options as AcquireTokenOptionsLegacy)?.scopes) {
|
|
321
348
|
this._trackEvent('acquireToken.legacy-scopes-provided', TelemetryLevel.Warning, {
|
|
322
349
|
properties: telemetryProperties,
|
|
323
350
|
});
|
|
324
351
|
}
|
|
325
352
|
|
|
326
353
|
// Handle empty scopes - currently monitoring for telemetry, will throw in future
|
|
327
|
-
if (
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
|
|
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
|
+
}
|
|
335
370
|
}
|
|
336
371
|
|
|
337
372
|
try {
|
|
338
373
|
const measurement = this._trackMeasurement('acquireToken', TelemetryLevel.Information, {
|
|
339
374
|
properties: telemetryProperties,
|
|
340
375
|
});
|
|
341
|
-
// Merge account, original request options, and resolved scopes
|
|
342
|
-
// 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.
|
|
343
378
|
const result = await this.#client.acquireToken({
|
|
344
379
|
behavior,
|
|
345
380
|
silent,
|
|
346
|
-
request: { ...
|
|
381
|
+
request: { ...inputRequest, account, scopes },
|
|
347
382
|
});
|
|
348
383
|
measurement?.measure();
|
|
349
384
|
return result;
|
|
@@ -406,6 +441,16 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
|
|
|
406
441
|
async login(options: LoginOptions): Promise<LoginResult> {
|
|
407
442
|
const { behavior = 'redirect', silent = true, request } = options;
|
|
408
443
|
|
|
444
|
+
request.loginHint ??=
|
|
445
|
+
this.#loginHint ?? this.account?.username ?? this.account?.loginHint ?? undefined;
|
|
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
|
+
|
|
409
454
|
// Determine if silent login is possible based on available account/hint information
|
|
410
455
|
// Silent login requires either an account object or a loginHint to work
|
|
411
456
|
const canLoginSilently = silent && (request.account || request.loginHint);
|
|
@@ -416,10 +461,9 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
|
|
|
416
461
|
// This allows silent login to work automatically with existing authentication state
|
|
417
462
|
request.account ??= this.account ?? undefined;
|
|
418
463
|
|
|
419
|
-
//
|
|
420
|
-
//
|
|
421
|
-
if (
|
|
422
|
-
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) {
|
|
423
467
|
this._trackEvent('login.missing-scope', TelemetryLevel.Warning, {
|
|
424
468
|
properties: telemetryProperties,
|
|
425
469
|
});
|
package/src/module.ts
CHANGED
|
@@ -91,6 +91,11 @@ export type AuthConfigFn = (builder: {
|
|
|
91
91
|
* @param requiresAuth - If true, app will attempt automatic login on initialization
|
|
92
92
|
*/
|
|
93
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;
|
|
94
99
|
}) => void;
|
|
95
100
|
|
|
96
101
|
/**
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by genversion.
|
|
2
|
-
export const version = '7.1
|
|
2
|
+
export const version = '7.2.1';
|