@equinor/fusion-framework-module-msal 5.1.1 → 6.0.0-next.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.
Files changed (144) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/README.md +237 -40
  3. package/dist/esm/MsalClient.interface.js +2 -0
  4. package/dist/esm/MsalClient.interface.js.map +1 -0
  5. package/dist/esm/MsalClient.js +215 -0
  6. package/dist/esm/MsalClient.js.map +1 -0
  7. package/dist/esm/MsalConfigurator.js +248 -0
  8. package/dist/esm/MsalConfigurator.js.map +1 -0
  9. package/dist/esm/MsalProvider.interface.js +2 -0
  10. package/dist/esm/MsalProvider.interface.js.map +1 -0
  11. package/dist/esm/MsalProvider.js +525 -0
  12. package/dist/esm/MsalProvider.js.map +1 -0
  13. package/dist/esm/MsalProxyProvider.interface.js +2 -0
  14. package/dist/esm/MsalProxyProvider.interface.js.map +1 -0
  15. package/dist/esm/__tests__/versioning/resolve-version.test.js +29 -38
  16. package/dist/esm/__tests__/versioning/resolve-version.test.js.map +1 -1
  17. package/dist/esm/create-client-log-callback.js +87 -0
  18. package/dist/esm/create-client-log-callback.js.map +1 -0
  19. package/dist/esm/create-proxy-provider.js +84 -0
  20. package/dist/esm/create-proxy-provider.js.map +1 -0
  21. package/dist/esm/index.js +1 -1
  22. package/dist/esm/index.js.map +1 -1
  23. package/dist/esm/module.js +64 -16
  24. package/dist/esm/module.js.map +1 -1
  25. package/dist/esm/static.js +32 -2
  26. package/dist/esm/static.js.map +1 -1
  27. package/dist/esm/types.js +9 -0
  28. package/dist/esm/types.js.map +1 -1
  29. package/dist/esm/util/compare-origin.js +11 -0
  30. package/dist/esm/util/compare-origin.js.map +1 -0
  31. package/dist/esm/{v2/client/util/url.js → util/normalize-uri.js} +1 -10
  32. package/dist/esm/util/normalize-uri.js.map +1 -0
  33. package/dist/esm/{v2/client/util/browser.js → util/redirect.js} +1 -1
  34. package/dist/esm/util/redirect.js.map +1 -0
  35. package/dist/esm/v2/IAuthClient.interface.js +2 -0
  36. package/dist/esm/v2/IAuthClient.interface.js.map +1 -0
  37. package/dist/esm/v2/IPublicClientApplication.interface.js +2 -0
  38. package/dist/esm/v2/IPublicClientApplication.interface.js.map +1 -0
  39. package/dist/esm/v2/MsalProvider.interface.js +2 -0
  40. package/dist/esm/v2/MsalProvider.interface.js.map +1 -0
  41. package/dist/esm/v2/create-proxy-client.js +155 -0
  42. package/dist/esm/v2/create-proxy-client.js.map +1 -0
  43. package/dist/esm/v2/create-proxy-provider.js +140 -0
  44. package/dist/esm/v2/create-proxy-provider.js.map +1 -0
  45. package/dist/esm/v2/map-account-info.js +18 -0
  46. package/dist/esm/v2/map-account-info.js.map +1 -0
  47. package/dist/esm/v2/map-authentication-result.js +22 -0
  48. package/dist/esm/v2/map-authentication-result.js.map +1 -0
  49. package/dist/esm/version.js +1 -1
  50. package/dist/esm/version.js.map +1 -1
  51. package/dist/esm/versioning/resolve-version.js +28 -16
  52. package/dist/esm/versioning/resolve-version.js.map +1 -1
  53. package/dist/tsconfig.tsbuildinfo +1 -1
  54. package/dist/types/MsalClient.d.ts +141 -0
  55. package/dist/types/MsalClient.interface.d.ts +103 -0
  56. package/dist/types/MsalConfigurator.d.ts +147 -0
  57. package/dist/types/MsalProvider.d.ts +291 -0
  58. package/dist/types/MsalProvider.interface.d.ts +159 -0
  59. package/dist/types/MsalProxyProvider.interface.d.ts +52 -0
  60. package/dist/types/create-client-log-callback.d.ts +38 -0
  61. package/dist/types/create-proxy-provider.d.ts +19 -0
  62. package/dist/types/index.d.ts +5 -4
  63. package/dist/types/module.d.ts +70 -4
  64. package/dist/types/static.d.ts +32 -1
  65. package/dist/types/types.d.ts +14 -6
  66. package/dist/types/util/redirect.d.ts +1 -0
  67. package/dist/types/v2/IAuthClient.interface.d.ts +68 -0
  68. package/dist/types/v2/IPublicClientApplication.interface.d.ts +68 -0
  69. package/dist/types/v2/MsalProvider.interface.d.ts +85 -0
  70. package/dist/types/v2/create-proxy-client.d.ts +22 -0
  71. package/dist/types/v2/create-proxy-provider.d.ts +24 -0
  72. package/dist/types/v2/map-account-info.d.ts +9 -0
  73. package/dist/types/v2/map-authentication-result.d.ts +9 -0
  74. package/dist/types/v2/types.d.ts +12 -0
  75. package/dist/types/version.d.ts +1 -1
  76. package/dist/types/versioning/resolve-version.d.ts +1 -1
  77. package/package.json +11 -6
  78. package/src/MsalClient.interface.ts +121 -0
  79. package/src/MsalClient.ts +274 -0
  80. package/src/MsalConfigurator.ts +289 -0
  81. package/src/MsalProvider.interface.ts +175 -0
  82. package/src/MsalProvider.ts +597 -0
  83. package/src/MsalProxyProvider.interface.ts +71 -0
  84. package/src/__tests__/versioning/resolve-version.test.ts +29 -42
  85. package/src/create-client-log-callback.ts +101 -0
  86. package/src/create-proxy-provider.ts +89 -0
  87. package/src/index.ts +6 -7
  88. package/src/module.ts +88 -20
  89. package/src/static.ts +32 -3
  90. package/src/types.ts +15 -7
  91. package/src/util/compare-origin.ts +11 -0
  92. package/src/{v2/client/util/url.ts → util/normalize-uri.ts} +0 -10
  93. package/src/v2/IAuthClient.interface.ts +91 -0
  94. package/src/v2/IPublicClientApplication.interface.ts +71 -0
  95. package/src/v2/MsalProvider.interface.ts +92 -0
  96. package/src/v2/create-proxy-client.ts +186 -0
  97. package/src/v2/create-proxy-provider.ts +156 -0
  98. package/src/v2/map-account-info.ts +20 -0
  99. package/src/v2/map-authentication-result.ts +24 -0
  100. package/src/v2/types.ts +12 -0
  101. package/src/version.ts +1 -1
  102. package/src/versioning/resolve-version.ts +35 -28
  103. package/tsconfig.json +3 -0
  104. package/dist/esm/v2/client/behavior.js +0 -5
  105. package/dist/esm/v2/client/behavior.js.map +0 -1
  106. package/dist/esm/v2/client/client.js +0 -142
  107. package/dist/esm/v2/client/client.js.map +0 -1
  108. package/dist/esm/v2/client/create-auth-client.js +0 -36
  109. package/dist/esm/v2/client/create-auth-client.js.map +0 -1
  110. package/dist/esm/v2/client/index.js +0 -5
  111. package/dist/esm/v2/client/index.js.map +0 -1
  112. package/dist/esm/v2/client/log/console.js +0 -45
  113. package/dist/esm/v2/client/log/console.js.map +0 -1
  114. package/dist/esm/v2/client/request.js +0 -2
  115. package/dist/esm/v2/client/request.js.map +0 -1
  116. package/dist/esm/v2/client/util/browser.js.map +0 -1
  117. package/dist/esm/v2/client/util/url.js.map +0 -1
  118. package/dist/esm/v2/configurator.js +0 -42
  119. package/dist/esm/v2/configurator.js.map +0 -1
  120. package/dist/esm/v2/index.js +0 -3
  121. package/dist/esm/v2/index.js.map +0 -1
  122. package/dist/esm/v2/provider.js +0 -115
  123. package/dist/esm/v2/provider.js.map +0 -1
  124. package/dist/types/v2/client/behavior.d.ts +0 -13
  125. package/dist/types/v2/client/client.d.ts +0 -89
  126. package/dist/types/v2/client/create-auth-client.d.ts +0 -27
  127. package/dist/types/v2/client/index.d.ts +0 -5
  128. package/dist/types/v2/client/log/console.d.ts +0 -28
  129. package/dist/types/v2/client/request.d.ts +0 -65
  130. package/dist/types/v2/configurator.d.ts +0 -32
  131. package/dist/types/v2/index.d.ts +0 -2
  132. package/dist/types/v2/provider.d.ts +0 -59
  133. package/src/v2/client/behavior.ts +0 -14
  134. package/src/v2/client/client.ts +0 -180
  135. package/src/v2/client/create-auth-client.ts +0 -48
  136. package/src/v2/client/index.ts +0 -8
  137. package/src/v2/client/log/console.ts +0 -58
  138. package/src/v2/client/request.ts +0 -66
  139. package/src/v2/configurator.ts +0 -58
  140. package/src/v2/index.ts +0 -2
  141. package/src/v2/provider.ts +0 -178
  142. /package/dist/types/{v2/client/util/browser.d.ts → util/compare-origin.d.ts} +0 -0
  143. /package/dist/types/{v2/client/util/url.d.ts → util/normalize-uri.d.ts} +0 -0
  144. /package/src/{v2/client/util/browser.ts → util/redirect.ts} +0 -0
@@ -0,0 +1,24 @@
1
+ import type { IMsalProvider } from '../MsalProvider.interface';
2
+ import type { IMsalProvider as IMsalProvider_v2 } from './MsalProvider.interface';
3
+ /**
4
+ * Creates a proxy provider for MSAL v2 compatibility.
5
+ *
6
+ * This function creates a Proxy that wraps the MSAL v4 provider and provides
7
+ * v2-compatible method signatures and return types while using the latest
8
+ * MSAL v4 implementation under the hood. The proxy handles type conversions
9
+ * and method adaptations to maintain backward compatibility.
10
+ *
11
+ * @param provider - The base MSAL v4 provider instance to wrap
12
+ * @returns A proxy provider implementing the v2-compatible interface
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const baseProvider = new MsalProvider(config);
17
+ * const v2Proxy = createProxyProvider(baseProvider);
18
+ *
19
+ * // Use v2-compatible API
20
+ * await v2Proxy.login();
21
+ * const token = await v2Proxy.acquireAccessToken({ scopes: ['User.Read'] });
22
+ * ```
23
+ */
24
+ export declare function createProxyProvider(provider: IMsalProvider): IMsalProvider_v2;
@@ -0,0 +1,9 @@
1
+ import type { AccountInfo } from '@azure/msal-browser';
2
+ import type { AccountInfo as AccountInfo_v2 } from './types';
3
+ /**
4
+ * Maps current AccountInfo to v2 AccountInfo format.
5
+ *
6
+ * @param account - The current AccountInfo to convert
7
+ * @returns The v2 AccountInfo format
8
+ */
9
+ export declare function mapAccountInfo(account: AccountInfo): AccountInfo_v2;
@@ -0,0 +1,9 @@
1
+ import type { AuthenticationResult } from '@azure/msal-browser';
2
+ import type { AuthenticationResult as AuthenticationResult_v2 } from './types';
3
+ /**
4
+ * Maps current AuthenticationResult to v2 AuthenticationResult format.
5
+ *
6
+ * @param result - The current AuthenticationResult to convert
7
+ * @returns The v2 AuthenticationResult format
8
+ */
9
+ export declare function mapAuthenticationResult(result: AuthenticationResult): AuthenticationResult_v2;
@@ -1,3 +1,9 @@
1
+ /**
2
+ * MSAL v2 compatible AccountInfo type.
3
+ *
4
+ * This type represents account information in MSAL v2 format
5
+ * to maintain backward compatibility while using MSAL v4 implementation.
6
+ */
1
7
  export type AccountInfo = {
2
8
  homeAccountId: string;
3
9
  environment: string;
@@ -9,6 +15,12 @@ export type AccountInfo = {
9
15
  [key: string]: string | number | string[] | object | undefined | unknown;
10
16
  };
11
17
  };
18
+ /**
19
+ * MSAL v2 compatible AuthenticationResult type.
20
+ *
21
+ * This type represents authentication results in MSAL v2 format
22
+ * to maintain backward compatibility while using MSAL v4 implementation.
23
+ */
12
24
  export type AuthenticationResult = {
13
25
  authority: string;
14
26
  uniqueId: string;
@@ -1 +1 @@
1
- export declare const version = "5.1.1";
1
+ export declare const version = "6.0.0-next.0";
@@ -1,4 +1,4 @@
1
- import { type SemVer } from 'semver';
1
+ import { SemVer } from 'semver';
2
2
  import type { ResolvedVersion } from './types';
3
3
  /**
4
4
  * Resolves and validates a version string against the latest available MSAL version.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equinor/fusion-framework-module-msal",
3
- "version": "5.1.1",
3
+ "version": "6.0.0-next.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",
@@ -36,14 +36,19 @@
36
36
  "directory": "packages/modules/msal"
37
37
  },
38
38
  "dependencies": {
39
- "@azure/msal-browser": "^2.21.0",
40
- "@types/semver": "^7.5.0",
39
+ "@azure/msal-browser": "^4.25.0",
41
40
  "semver": "^7.5.4",
42
- "zod": "^4.1.8",
43
- "@equinor/fusion-framework-module": "^5.0.4"
41
+ "zod": "^4.1.8"
44
42
  },
45
43
  "devDependencies": {
46
- "typescript": "^5.8.2"
44
+ "@types/semver": "^7.5.0",
45
+ "typescript": "^5.8.2",
46
+ "@equinor/fusion-framework-module": "^5.0.5",
47
+ "@equinor/fusion-framework-module-telemetry": "4.4.0-next.0"
48
+ },
49
+ "peerDependencies": {
50
+ "@equinor/fusion-framework-module-telemetry": "^4.4.0-next.0",
51
+ "@equinor/fusion-framework-module": "^5.0.5"
47
52
  },
48
53
  "scripts": {
49
54
  "build": "tsc -b",
@@ -0,0 +1,121 @@
1
+ import type {
2
+ IPublicClientApplication,
3
+ AccountInfo,
4
+ AuthenticationResult,
5
+ PopupRequest,
6
+ RedirectRequest,
7
+ } from '@azure/msal-browser';
8
+
9
+ /**
10
+ * Authentication behavior type determining the interaction method.
11
+ *
12
+ * - 'popup': Opens authentication in a popup window (returns result immediately)
13
+ * - 'redirect': Navigates browser to authentication page (returns void, result via handleRedirectPromise)
14
+ */
15
+ export type AuthBehavior = 'popup' | 'redirect';
16
+
17
+ /**
18
+ * Options for acquiring an access token.
19
+ *
20
+ * This type ensures either the legacy or modern approach is used by requiring
21
+ * the request parameter while allowing optional configuration for behavior and silent mode.
22
+ *
23
+ * @property request - MSAL request object for popup or redirect authentication
24
+ * @property behavior - Optional authentication method (popup or redirect). Defaults to 'redirect'
25
+ * @property silent - Optional flag to attempt silent token acquisition first. Defaults to true if account is available
26
+ */
27
+ export type AcquireTokenOptions = {
28
+ request: PopupRequest | RedirectRequest;
29
+ behavior?: AuthBehavior;
30
+ silent?: boolean;
31
+ };
32
+
33
+ /**
34
+ * Result type for token acquisition operations.
35
+ *
36
+ * Returns the authentication result on success, or null/undefined on failure or redirect.
37
+ * For redirect flows, returns undefined (void) because browser navigation interrupts execution.
38
+ */
39
+ export type AcquireTokenResult = AuthenticationResult | null | undefined;
40
+
41
+ /**
42
+ * Options for user login/authentication.
43
+ *
44
+ * @property request - MSAL request object for popup or redirect authentication
45
+ * @property behavior - Optional authentication method (popup or redirect). Defaults to 'redirect'
46
+ * @property silent - Optional flag to attempt silent SSO authentication first. Defaults to true
47
+ */
48
+ export type LoginOptions = {
49
+ request: PopupRequest | RedirectRequest;
50
+ behavior?: AuthBehavior;
51
+ silent?: boolean;
52
+ };
53
+
54
+ /**
55
+ * Options for user logout.
56
+ *
57
+ * @property redirectUri - Optional URI to redirect to after logout completes
58
+ * @property account - Optional account to log out (defaults to active account if not provided)
59
+ */
60
+ export type LogoutOptions = {
61
+ redirectUri?: string;
62
+ account?: AccountInfo;
63
+ };
64
+
65
+ /**
66
+ * Result type for login operations.
67
+ *
68
+ * Returns the authentication result on success, or undefined on redirect-based flows
69
+ * (where the browser navigates away). For redirect flows, result is available via handleRedirectPromise.
70
+ */
71
+ export type LoginResult = AuthenticationResult | undefined;
72
+
73
+ /**
74
+ * Interface for MSAL v4 client with additional properties and methods.
75
+ *
76
+ * This interface extends the standard MSAL v4 PublicClientApplication
77
+ * with additional properties and methods needed for the framework.
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * const client: IMsalClient = new MsalClient(config);
82
+ *
83
+ * // Access additional properties
84
+ * const tenantId = client.tenantId;
85
+ * const hasValidClaims = client.hasValidClaims;
86
+ *
87
+ * // Use enhanced methods
88
+ * const result = await client.login({ request: { scopes: ['User.Read'] } });
89
+ * ```
90
+ */
91
+ export interface IMsalClient extends IPublicClientApplication {
92
+ /** Configured client ID */
93
+ clientId: string | undefined;
94
+
95
+ /** Tenant ID for the client domain */
96
+ tenantId: string | undefined;
97
+
98
+ /** Check if the current account has valid claims */
99
+ hasValidClaims: boolean;
100
+
101
+ /**
102
+ * Login user with enhanced options
103
+ * @param options - Login configuration options
104
+ * @returns Promise resolving to authentication result or undefined
105
+ */
106
+ login(options: LoginOptions): Promise<LoginResult>;
107
+
108
+ /**
109
+ * Logout user with enhanced options
110
+ * @param options - Logout configuration options
111
+ * @returns Promise resolving to void
112
+ */
113
+ logout(options: LogoutOptions): Promise<void>;
114
+
115
+ /**
116
+ * Acquire access token with enhanced options
117
+ * @param options - Token acquisition configuration options
118
+ * @returns Promise resolving to authentication result or null/undefined
119
+ */
120
+ acquireToken(options: AcquireTokenOptions): Promise<AcquireTokenResult>;
121
+ }
@@ -0,0 +1,274 @@
1
+ import {
2
+ PublicClientApplication,
3
+ type SilentRequest,
4
+ type Configuration,
5
+ type EndSessionRequest,
6
+ type PopupRequest,
7
+ type RedirectRequest,
8
+ } from '@azure/msal-browser';
9
+
10
+ import type {
11
+ IMsalClient,
12
+ AcquireTokenResult,
13
+ LoginOptions,
14
+ LogoutOptions,
15
+ LoginResult,
16
+ AcquireTokenOptions,
17
+ } from './MsalClient.interface';
18
+
19
+ export type { IMsalClient };
20
+
21
+ /**
22
+ * MSAL client configuration extending the standard MSAL Configuration.
23
+ *
24
+ * This type adds tenant-specific configuration options while maintaining
25
+ * full compatibility with the base MSAL Configuration type.
26
+ *
27
+ * @remarks
28
+ * The `tenantId` in the auth configuration is optional but recommended for
29
+ * multi-tenant applications. When provided, it's used for better tenant isolation
30
+ * and can be accessed via the client's `tenantId` property.
31
+ */
32
+ export type MsalClientConfig = Configuration & {
33
+ auth: {
34
+ /** Optional tenant identifier for Azure AD tenant */
35
+ tenantId?: string;
36
+ };
37
+ };
38
+
39
+ /**
40
+ * MSAL v4 client implementation with extended properties and methods.
41
+ *
42
+ * This class extends the standard MSAL PublicClientApplication to provide
43
+ * additional properties (tenantId, clientId, hasValidClaims) and enhanced
44
+ * authentication methods with better options for behavior and silent flows.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const config: MsalClientConfig = {
49
+ * auth: {
50
+ * clientId: 'your-client-id',
51
+ * authority: 'https://login.microsoftonline.com/your-tenant-id',
52
+ * tenantId: 'your-tenant-id'
53
+ * }
54
+ * };
55
+ * const client = new MsalClient(config);
56
+ * await client.initialize();
57
+ * ```
58
+ */
59
+ export class MsalClient extends PublicClientApplication implements IMsalClient {
60
+ #tenantId?: string;
61
+ #clientId?: string;
62
+
63
+ /**
64
+ * Creates a new MSAL client instance.
65
+ *
66
+ * @param config - MSAL client configuration including auth settings
67
+ */
68
+ constructor(config: MsalClientConfig) {
69
+ super(config);
70
+ this.#tenantId = config.auth?.tenantId;
71
+ this.#clientId = config.auth?.clientId;
72
+ }
73
+
74
+ /**
75
+ * Tenant identifier for the configured Azure AD tenant.
76
+ *
77
+ * @returns The tenant ID string if configured, undefined otherwise
78
+ */
79
+ get tenantId(): string | undefined {
80
+ return this.#tenantId;
81
+ }
82
+
83
+ /**
84
+ * Client identifier (application ID) for the configured Azure AD application.
85
+ *
86
+ * @returns The client ID string if configured, undefined otherwise
87
+ */
88
+ get clientId(): string | undefined {
89
+ return this.#clientId;
90
+ }
91
+
92
+ /**
93
+ * Checks if the currently active account has valid ID token claims.
94
+ *
95
+ * This property validates that the ID token's expiration claim (exp) is in the future,
96
+ * indicating the account is still authenticated and the session is valid.
97
+ *
98
+ * @returns True if the account has unexpired token claims, false otherwise
99
+ */
100
+ get hasValidClaims(): boolean {
101
+ const idTokenClaims = this.getActiveAccount()?.idTokenClaims;
102
+ // Compare token expiration time (seconds since epoch) with current time
103
+ return Number(idTokenClaims?.exp) > Number(Math.ceil(Date.now() / 1000));
104
+ }
105
+
106
+ /**
107
+ * Authenticates user with support for silent SSO, popup, and redirect flows.
108
+ *
109
+ * @param options - Login configuration with request, behavior, and silent flag
110
+ * @returns Promise resolving to authentication result
111
+ *
112
+ * @remarks
113
+ * Authentication flow priority:
114
+ * 1. Silent SSO (if enabled and account/loginHint provided)
115
+ * 2. Interactive method based on behavior (popup or redirect)
116
+ *
117
+ * **Behavior differences:**
118
+ * - **Popup**: Opens authentication popup window and returns authentication result immediately
119
+ * - **Redirect**: Navigates browser to Microsoft login page. Returns `void` because the browser
120
+ * navigates to a new page. After redirect completes, the result will be available via
121
+ * `handleRedirectPromise()` when the app loads on the new page.
122
+ */
123
+ async login(options: Required<LoginOptions>): Promise<LoginResult> {
124
+ // Attempt silent authentication first if enabled
125
+ // This provides better UX by avoiding unnecessary popups/redirects
126
+ if (options.silent) {
127
+ if (!options.request.account && !options.request.loginHint) {
128
+ this.getLogger().warning(
129
+ 'No account or login hint provided, please provide an account or login hint in the request',
130
+ );
131
+ }
132
+ try {
133
+ return await this.ssoSilent(options.request as SilentRequest);
134
+ } catch {
135
+ // Silent login failed - continue to interactive flow
136
+ this.getLogger().warning('Silent login failed, falling back to interactive');
137
+ }
138
+ }
139
+
140
+ // Perform interactive authentication based on specified behavior
141
+ switch (options.behavior) {
142
+ case 'popup':
143
+ // Popup flow - returns result immediately after user completes authentication
144
+ return await this.loginPopup(options.request as PopupRequest);
145
+ case 'redirect':
146
+ // Redirect flow - browser navigates to Microsoft login page
147
+ // Returns void because browser navigation interrupts execution on current page
148
+ // Result will be available via handleRedirectPromise() after app loads on new page
149
+ await this.loginRedirect(options.request as RedirectRequest);
150
+ break;
151
+ default:
152
+ throw new Error(
153
+ `Invalid behavior provided: ${options.behavior}, please provide a valid behavior, see options.behavior for more information.`,
154
+ );
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Logs out the current user and clears authentication state.
160
+ *
161
+ * This method initiates a logout flow using the redirect mechanism, which navigates
162
+ * the user to the Microsoft logout endpoint to clear session cookies and tokens.
163
+ *
164
+ * @param options - Logout configuration options
165
+ * @param options.account - Account to log out (defaults to active account if not provided)
166
+ * @param options.redirectUri - URI to redirect to after logout completes
167
+ * @returns Promise that resolves when logout redirect is initiated
168
+ *
169
+ * @remarks
170
+ * - This method always uses redirect flow for logout (more reliable than popup)
171
+ * - **Returns `void`**: The browser navigates to Microsoft's logout page, which interrupts
172
+ * execution on the current page. This is expected behavior for redirect-based logout.
173
+ * - If no account is provided, uses the currently active account
174
+ * - After redirect, user will be logged out at Microsoft's identity provider
175
+ * - Application state and local tokens are cleared during this process
176
+ *
177
+ * @example
178
+ * ```typescript
179
+ * // Basic logout with active account
180
+ * await client.logout();
181
+ *
182
+ * // Logout with custom redirect URI
183
+ * await client.logout({
184
+ * redirectUri: 'https://app.com/logged-out'
185
+ * });
186
+ * ```
187
+ */
188
+ async logout(options?: LogoutOptions): Promise<void> {
189
+ if (!options?.account) {
190
+ this.getLogger().warning(
191
+ 'No account available for logout, please provide an account in the options',
192
+ );
193
+ }
194
+
195
+ const logoutRequest: EndSessionRequest = {
196
+ account: options?.account,
197
+ postLogoutRedirectUri: options?.redirectUri,
198
+ };
199
+
200
+ // Browser will navigate to Microsoft logout page
201
+ // Returns void because navigation interrupts execution on current page
202
+ await this.logoutRedirect(logoutRequest);
203
+ }
204
+
205
+ /**
206
+ * Acquires an access token with smart silent/interactive fallback.
207
+ *
208
+ * @param options - Token acquisition configuration
209
+ * @returns Promise resolving to authentication result or null/undefined
210
+ *
211
+ * @remarks
212
+ * Token acquisition flow:
213
+ * 1. If silent=true and account available, attempt silent token acquisition from cache
214
+ * 2. On silent failure (or not attempted), use interactive method based on behavior
215
+ * 3. Interactive method based on behavior (popup or redirect)
216
+ *
217
+ * **Behavior differences:**
218
+ * - **Popup**: Opens authentication popup window and returns token result immediately
219
+ * - **Redirect**: Navigates browser to Microsoft login page. Returns `void` because the browser
220
+ * navigates to a new page. After redirect completes, handle the result via
221
+ * `handleRedirectPromise()` when the app loads on the new page.
222
+ *
223
+ * The default silent behavior is determined by presence of account in the request.
224
+ * This provides optimal UX by minimizing unnecessary user interactions.
225
+ */
226
+ async acquireToken(options: AcquireTokenOptions): Promise<AcquireTokenResult> {
227
+ const { behavior = 'redirect', silent = !!options.request?.account, request } = options;
228
+
229
+ if (!request) {
230
+ throw new Error('No request provided, please provide a request in the options');
231
+ }
232
+
233
+ if (request.scopes.length === 0) {
234
+ this.getLogger().warning(
235
+ 'No scopes provided, please provide scopes in the request option, see options.request for more information.',
236
+ );
237
+ }
238
+
239
+ // Attempt silent token acquisition first
240
+ // This fetches from cache or uses refresh token without user interaction
241
+ if (silent) {
242
+ if (request.account) {
243
+ try {
244
+ this.getLogger().verbose('Attempting to acquire token silently');
245
+ return await this.acquireTokenSilent(request as SilentRequest);
246
+ } catch {
247
+ // Silent acquisition failed - fall back to interactive
248
+ this.getLogger().warning('Silent token acquisition failed, falling back to interactive');
249
+ }
250
+ } else {
251
+ this.getLogger().warning(
252
+ 'Cannot acquire token silently, no account provided, falling back to interactive.',
253
+ );
254
+ }
255
+ }
256
+
257
+ // Perform interactive token acquisition
258
+ switch (behavior) {
259
+ case 'popup':
260
+ // Popup flow - returns token immediately after user grants permission
261
+ return await this.acquireTokenPopup(request);
262
+ case 'redirect':
263
+ // Redirect flow - browser navigates to Microsoft login page
264
+ // Returns void because browser navigation interrupts execution on current page
265
+ // Token will be available via handleRedirectPromise() after app loads on new page
266
+ await this.acquireTokenRedirect(request);
267
+ break;
268
+ default:
269
+ throw new Error(
270
+ `Invalid behavior provided: ${behavior}, please provide a valid behavior, see options.behavior for more information.`,
271
+ );
272
+ }
273
+ }
274
+ }