@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.
@@ -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
  */
@@ -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.
@@ -1 +1 @@
1
- export declare const version = "7.0.0";
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.0.0",
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-telemetry": "^4.6.1",
54
- "@equinor/fusion-framework-module": "^5.0.5"
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.1"
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
  }
@@ -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
  */
@@ -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.0.0';
2
+ export const version = '7.2.0';