@equinor/fusion-framework-module-msal 7.0.0 → 7.2.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/CHANGELOG.md +47 -0
- package/README.md +77 -0
- package/dist/esm/MsalConfigurator.js +51 -0
- package/dist/esm/MsalConfigurator.js.map +1 -1
- package/dist/esm/MsalProvider.js +59 -0
- 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/MsalClient.interface.d.ts +17 -1
- package/dist/types/MsalConfigurator.d.ts +43 -0
- package/dist/types/MsalProvider.d.ts +5 -0
- package/dist/types/module.d.ts +5 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +4 -4
- package/src/MsalClient.interface.ts +18 -0
- package/src/MsalConfigurator.ts +53 -0
- package/src/MsalProvider.ts +63 -0
- package/src/module.ts +5 -0
- package/src/version.ts +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IPublicClientApplication, AccountInfo, AuthenticationResult, PopupRequest, RedirectRequest } from '@azure/msal-browser';
|
|
1
|
+
import type { IPublicClientApplication, AccountInfo, AuthenticationResult, PopupRequest, RedirectRequest, AuthorizationCodeRequest } from '@azure/msal-browser';
|
|
2
2
|
/**
|
|
3
3
|
* Authentication behavior type determining the interaction method.
|
|
4
4
|
*
|
|
@@ -100,4 +100,20 @@ export interface IMsalClient extends IPublicClientApplication {
|
|
|
100
100
|
* @returns Promise resolving to authentication result or null/undefined
|
|
101
101
|
*/
|
|
102
102
|
acquireToken(options: AcquireTokenOptions): Promise<AcquireTokenResult>;
|
|
103
|
+
/**
|
|
104
|
+
* Exchange a backend-issued authorization code for tokens (SPA Auth Code Flow).
|
|
105
|
+
*
|
|
106
|
+
* This method enables automatic sign-in using a backend-issued auth code without
|
|
107
|
+
* requiring interactive MSAL flows. Primarily used during module initialization.
|
|
108
|
+
*
|
|
109
|
+
* @param request - Authorization code request with code and scopes
|
|
110
|
+
* @returns Promise resolving to authentication result with tokens
|
|
111
|
+
*
|
|
112
|
+
* @remarks
|
|
113
|
+
* - Auth codes are single-use and short-lived (typically 5-10 minutes)
|
|
114
|
+
* - MSAL handles token validation, caching, and refresh token management
|
|
115
|
+
* - Follows Microsoft's standard SPA Auth Code Flow pattern
|
|
116
|
+
* - Inherited from PublicClientApplication (MSAL Browser v4+)
|
|
117
|
+
*/
|
|
118
|
+
acquireTokenByCode(request: AuthorizationCodeRequest): Promise<AuthenticationResult>;
|
|
103
119
|
}
|
|
@@ -65,6 +65,34 @@ export declare class MsalConfigurator extends BaseConfigBuilder<MsalConfig> {
|
|
|
65
65
|
* ```
|
|
66
66
|
*/
|
|
67
67
|
setClientConfig(config?: MsalClientConfig): this;
|
|
68
|
+
/**
|
|
69
|
+
* Sets a backend-issued authorization code for token exchange.
|
|
70
|
+
*
|
|
71
|
+
* This enables the MSAL module to exchange a backend-generated auth code for tokens
|
|
72
|
+
* during initialization, allowing users to be automatically signed in without triggering
|
|
73
|
+
* an interactive MSAL login flow. The auth code is exchanged before the requiresAuth check,
|
|
74
|
+
* so tokens are cached and no login prompt appears.
|
|
75
|
+
*
|
|
76
|
+
* This follows Microsoft's standard SPA Auth Code Flow pattern and is compatible with
|
|
77
|
+
* MSAL Browser's acquireTokenByCode() method.
|
|
78
|
+
*
|
|
79
|
+
* @param authCode - The authorization code issued by the backend
|
|
80
|
+
* @returns The configurator instance for method chaining
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* // Backend provides auth code in HTML/config
|
|
85
|
+
* const config = { auth: { code: getAuthCodeFromBackend() } };
|
|
86
|
+
* configurator.setAuthCode(config.auth.code);
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* - Auth codes are single-use and short-lived (typically 5-10 minutes)
|
|
91
|
+
* - The exchange happens during module initialization before requiresAuth check
|
|
92
|
+
* - If exchange fails, the provider falls back to standard MSAL authentication flows
|
|
93
|
+
* - Requires backend to be configured with SPA Auth Code support
|
|
94
|
+
*/
|
|
95
|
+
setAuthCode(authCode: string): this;
|
|
68
96
|
/**
|
|
69
97
|
* Sets whether authentication is required for the application.
|
|
70
98
|
*
|
|
@@ -85,6 +113,21 @@ export declare class MsalConfigurator extends BaseConfigBuilder<MsalConfig> {
|
|
|
85
113
|
* ```
|
|
86
114
|
*/
|
|
87
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;
|
|
88
131
|
/**
|
|
89
132
|
* @deprecated - since version 5.1.0, use setClient instead
|
|
90
133
|
*/
|
|
@@ -74,12 +74,17 @@ export declare class MsalProvider extends BaseModuleProvider<MsalConfig> impleme
|
|
|
74
74
|
*
|
|
75
75
|
* This method must be called before using any authentication operations. It performs:
|
|
76
76
|
* - Client initialization
|
|
77
|
+
* - Auth code exchange (if backend-issued code provided)
|
|
77
78
|
* - Redirect result handling (if returning from auth flow)
|
|
78
79
|
* - Automatic login attempt if requiresAuth is enabled and no valid session exists
|
|
79
80
|
*
|
|
80
81
|
* @returns Promise that resolves when initialization is complete
|
|
81
82
|
*
|
|
82
83
|
* @remarks
|
|
84
|
+
* Auth code exchange happens before the requiresAuth check, allowing automatic sign-in
|
|
85
|
+
* without user interaction when a valid backend-issued code is provided. If exchange fails,
|
|
86
|
+
* the provider falls back to standard MSAL authentication flows.
|
|
87
|
+
*
|
|
83
88
|
* The provider will attempt automatic login with empty scopes if requiresAuth is true.
|
|
84
89
|
* Apps should call acquireToken with actual scopes after initialization completes.
|
|
85
90
|
*/
|
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
|
+
export declare const version = "7.2.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@equinor/fusion-framework-module-msal",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.2.0",
|
|
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",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"semver": "^7.5.4",
|
|
51
51
|
"typescript": "^5.8.2",
|
|
52
52
|
"zod": "^4.1.8",
|
|
53
|
-
"@equinor/fusion-framework-module
|
|
54
|
-
"@equinor/fusion-framework-module": "^
|
|
53
|
+
"@equinor/fusion-framework-module": "^5.0.5",
|
|
54
|
+
"@equinor/fusion-framework-module-telemetry": "^4.6.3"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"@types/semver": "^7.5.0",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"typescript": "^5.8.2",
|
|
60
60
|
"zod": "^4.1.8",
|
|
61
61
|
"@equinor/fusion-framework-module": "^5.0.5",
|
|
62
|
-
"@equinor/fusion-framework-module-telemetry": "^4.6.
|
|
62
|
+
"@equinor/fusion-framework-module-telemetry": "^4.6.3"
|
|
63
63
|
},
|
|
64
64
|
"peerDependenciesMeta": {
|
|
65
65
|
"@equinor/fusion-framework-module-telemetry": {
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
AuthenticationResult,
|
|
5
5
|
PopupRequest,
|
|
6
6
|
RedirectRequest,
|
|
7
|
+
AuthorizationCodeRequest,
|
|
7
8
|
} from '@azure/msal-browser';
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -118,4 +119,21 @@ export interface IMsalClient extends IPublicClientApplication {
|
|
|
118
119
|
* @returns Promise resolving to authentication result or null/undefined
|
|
119
120
|
*/
|
|
120
121
|
acquireToken(options: AcquireTokenOptions): Promise<AcquireTokenResult>;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Exchange a backend-issued authorization code for tokens (SPA Auth Code Flow).
|
|
125
|
+
*
|
|
126
|
+
* This method enables automatic sign-in using a backend-issued auth code without
|
|
127
|
+
* requiring interactive MSAL flows. Primarily used during module initialization.
|
|
128
|
+
*
|
|
129
|
+
* @param request - Authorization code request with code and scopes
|
|
130
|
+
* @returns Promise resolving to authentication result with tokens
|
|
131
|
+
*
|
|
132
|
+
* @remarks
|
|
133
|
+
* - Auth codes are single-use and short-lived (typically 5-10 minutes)
|
|
134
|
+
* - MSAL handles token validation, caching, and refresh token management
|
|
135
|
+
* - Follows Microsoft's standard SPA Auth Code Flow pattern
|
|
136
|
+
* - Inherited from PublicClientApplication (MSAL Browser v4+)
|
|
137
|
+
*/
|
|
138
|
+
acquireTokenByCode(request: AuthorizationCodeRequest): Promise<AuthenticationResult>;
|
|
121
139
|
}
|
package/src/MsalConfigurator.ts
CHANGED
|
@@ -43,6 +43,8 @@ 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(),
|
|
47
|
+
authCode: z.string().optional(),
|
|
46
48
|
version: z.string().transform((x: string) => String(semver.coerce(x))),
|
|
47
49
|
telemetry: TelemetryConfigSchema,
|
|
48
50
|
});
|
|
@@ -123,6 +125,38 @@ export class MsalConfigurator extends BaseConfigBuilder<MsalConfig> {
|
|
|
123
125
|
return this;
|
|
124
126
|
}
|
|
125
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Sets a backend-issued authorization code for token exchange.
|
|
130
|
+
*
|
|
131
|
+
* This enables the MSAL module to exchange a backend-generated auth code for tokens
|
|
132
|
+
* during initialization, allowing users to be automatically signed in without triggering
|
|
133
|
+
* an interactive MSAL login flow. The auth code is exchanged before the requiresAuth check,
|
|
134
|
+
* so tokens are cached and no login prompt appears.
|
|
135
|
+
*
|
|
136
|
+
* This follows Microsoft's standard SPA Auth Code Flow pattern and is compatible with
|
|
137
|
+
* MSAL Browser's acquireTokenByCode() method.
|
|
138
|
+
*
|
|
139
|
+
* @param authCode - The authorization code issued by the backend
|
|
140
|
+
* @returns The configurator instance for method chaining
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* // Backend provides auth code in HTML/config
|
|
145
|
+
* const config = { auth: { code: getAuthCodeFromBackend() } };
|
|
146
|
+
* configurator.setAuthCode(config.auth.code);
|
|
147
|
+
* ```
|
|
148
|
+
*
|
|
149
|
+
* @remarks
|
|
150
|
+
* - Auth codes are single-use and short-lived (typically 5-10 minutes)
|
|
151
|
+
* - The exchange happens during module initialization before requiresAuth check
|
|
152
|
+
* - If exchange fails, the provider falls back to standard MSAL authentication flows
|
|
153
|
+
* - Requires backend to be configured with SPA Auth Code support
|
|
154
|
+
*/
|
|
155
|
+
setAuthCode(authCode: string): this {
|
|
156
|
+
this._set('authCode', async () => authCode);
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
|
|
126
160
|
/**
|
|
127
161
|
* Sets whether authentication is required for the application.
|
|
128
162
|
*
|
|
@@ -147,6 +181,25 @@ export class MsalConfigurator extends BaseConfigBuilder<MsalConfig> {
|
|
|
147
181
|
return this;
|
|
148
182
|
}
|
|
149
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
|
+
|
|
150
203
|
/**
|
|
151
204
|
* @deprecated - since version 5.1.0, use setClient instead
|
|
152
205
|
*/
|
package/src/MsalProvider.ts
CHANGED
|
@@ -60,6 +60,8 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
|
|
|
60
60
|
scope: string[];
|
|
61
61
|
};
|
|
62
62
|
#requiresAuth?: boolean;
|
|
63
|
+
#authCode?: string;
|
|
64
|
+
#loginHint?: string;
|
|
63
65
|
|
|
64
66
|
/**
|
|
65
67
|
* The MSAL module version enum value indicating the API compatibility level.
|
|
@@ -126,6 +128,11 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
|
|
|
126
128
|
});
|
|
127
129
|
this.#requiresAuth = config.requiresAuth;
|
|
128
130
|
this.#telemetry = config.telemetry;
|
|
131
|
+
this.#loginHint = config.loginHint;
|
|
132
|
+
|
|
133
|
+
// Extract auth code from config if present
|
|
134
|
+
// This will be used during initialize to exchange for tokens
|
|
135
|
+
this.#authCode = config.authCode;
|
|
129
136
|
|
|
130
137
|
// Validate required client configuration
|
|
131
138
|
if (!config.client) {
|
|
@@ -145,12 +152,17 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
|
|
|
145
152
|
*
|
|
146
153
|
* This method must be called before using any authentication operations. It performs:
|
|
147
154
|
* - Client initialization
|
|
155
|
+
* - Auth code exchange (if backend-issued code provided)
|
|
148
156
|
* - Redirect result handling (if returning from auth flow)
|
|
149
157
|
* - Automatic login attempt if requiresAuth is enabled and no valid session exists
|
|
150
158
|
*
|
|
151
159
|
* @returns Promise that resolves when initialization is complete
|
|
152
160
|
*
|
|
153
161
|
* @remarks
|
|
162
|
+
* Auth code exchange happens before the requiresAuth check, allowing automatic sign-in
|
|
163
|
+
* without user interaction when a valid backend-issued code is provided. If exchange fails,
|
|
164
|
+
* the provider falls back to standard MSAL authentication flows.
|
|
165
|
+
*
|
|
154
166
|
* The provider will attempt automatic login with empty scopes if requiresAuth is true.
|
|
155
167
|
* Apps should call acquireToken with actual scopes after initialization completes.
|
|
156
168
|
*/
|
|
@@ -159,6 +171,54 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
|
|
|
159
171
|
// Initialize the underlying MSAL client first
|
|
160
172
|
await this.#client.initialize();
|
|
161
173
|
|
|
174
|
+
// Priority 0: Exchange auth code if provided by backend
|
|
175
|
+
// This must happen before the requiresAuth check so tokens are cached
|
|
176
|
+
if (this.#authCode) {
|
|
177
|
+
try {
|
|
178
|
+
this._trackEvent('initialize.exchanging-auth-code', TelemetryLevel.Information);
|
|
179
|
+
|
|
180
|
+
// Use MSAL's acquireTokenByCode to exchange backend auth code for tokens
|
|
181
|
+
// This follows Microsoft's standard SPA Auth Code Flow pattern
|
|
182
|
+
const clientId = this.#client.clientId;
|
|
183
|
+
if (!clientId) {
|
|
184
|
+
throw new Error('Client ID is required for auth code exchange');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Exchange the auth code for tokens using the client ID's default scope.
|
|
188
|
+
// The `/.default` scope represents all permissions configured for this app in Entra ID,
|
|
189
|
+
// ensuring the exchanged tokens have the correct app-level permissions without requiring
|
|
190
|
+
// the caller to specify scopes. This follows MSAL's recommended SPA auth code pattern.
|
|
191
|
+
// This method is inherited from PublicClientApplication (MSAL Browser v4+)
|
|
192
|
+
const result = await this.#client.acquireTokenByCode({
|
|
193
|
+
code: this.#authCode,
|
|
194
|
+
scopes: [`${clientId}/.default`],
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Successfully exchanged auth code - set active account
|
|
198
|
+
if (result.account) {
|
|
199
|
+
this.#client.setActiveAccount(result.account);
|
|
200
|
+
this._trackEvent('initialize.auth-code-exchanged-account', TelemetryLevel.Information, {
|
|
201
|
+
properties: {
|
|
202
|
+
username: result.account.username,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
// Auth code exchange failed - log and fall back to standard flows
|
|
208
|
+
this._trackException('initialize.auth-code-exchange-failed', TelemetryLevel.Warning, {
|
|
209
|
+
exception: error instanceof Error ? error : new Error(String(error)),
|
|
210
|
+
properties: {
|
|
211
|
+
message: error instanceof Error ? error.message : String(error),
|
|
212
|
+
reason: 'Auth code exchange failed, falling back to standard authentication flows',
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
// Continue to requiresAuth check - will trigger standard login if needed
|
|
216
|
+
} finally {
|
|
217
|
+
// Clear auth code to avoid repeated attempts
|
|
218
|
+
this.#authCode = undefined;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
162
222
|
// Only attempt authentication if this provider requires it
|
|
163
223
|
if (this.#requiresAuth) {
|
|
164
224
|
// Priority 1: Check if returning from redirect-based authentication
|
|
@@ -348,6 +408,9 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
|
|
|
348
408
|
async login(options: LoginOptions): Promise<LoginResult> {
|
|
349
409
|
const { behavior = 'redirect', silent = true, request } = options;
|
|
350
410
|
|
|
411
|
+
request.loginHint ??=
|
|
412
|
+
this.#loginHint ?? this.account?.username ?? this.account?.loginHint ?? undefined;
|
|
413
|
+
|
|
351
414
|
// Determine if silent login is possible based on available account/hint information
|
|
352
415
|
// Silent login requires either an account object or a loginHint to work
|
|
353
416
|
const canLoginSilently = silent && (request.account || request.loginHint);
|
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.
|
|
2
|
+
export const version = '7.2.0';
|