@equinor/fusion-framework-module-msal 10.0.2 → 11.0.0-next.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +175 -0
  2. package/README.md +17 -368
  3. package/dist/esm/MsalConfigurator.js +176 -88
  4. package/dist/esm/MsalConfigurator.js.map +1 -1
  5. package/dist/esm/__tests__/MsalConfigurator.test.js +75 -0
  6. package/dist/esm/__tests__/MsalConfigurator.test.js.map +1 -1
  7. package/dist/esm/__tests__/create-proxy-provider.test.js +53 -0
  8. package/dist/esm/__tests__/create-proxy-provider.test.js.map +1 -0
  9. package/dist/esm/__tests__/mock/create-mock-user-from-token.test.js +39 -0
  10. package/dist/esm/__tests__/mock/create-mock-user-from-token.test.js.map +1 -0
  11. package/dist/esm/__tests__/mock/msal-mock.test.js +451 -0
  12. package/dist/esm/__tests__/mock/msal-mock.test.js.map +1 -0
  13. package/dist/esm/index.js +5 -0
  14. package/dist/esm/index.js.map +1 -1
  15. package/dist/esm/mock/MsalMockClient.js +484 -0
  16. package/dist/esm/mock/MsalMockClient.js.map +1 -0
  17. package/dist/esm/mock/MsalMockConfigurator.js +239 -0
  18. package/dist/esm/mock/MsalMockConfigurator.js.map +1 -0
  19. package/dist/esm/mock/create-mock-token.js +60 -0
  20. package/dist/esm/mock/create-mock-token.js.map +1 -0
  21. package/dist/esm/mock/create-mock-user-from-token.js +40 -0
  22. package/dist/esm/mock/create-mock-user-from-token.js.map +1 -0
  23. package/dist/esm/mock/create-msal-mock-client.js +20 -0
  24. package/dist/esm/mock/create-msal-mock-client.js.map +1 -0
  25. package/dist/esm/mock/decode-jwt-segment.js +22 -0
  26. package/dist/esm/mock/decode-jwt-segment.js.map +1 -0
  27. package/dist/esm/mock/index.js +31 -0
  28. package/dist/esm/mock/index.js.map +1 -0
  29. package/dist/esm/mock/module.js +38 -0
  30. package/dist/esm/mock/module.js.map +1 -0
  31. package/dist/esm/msal-config-schema.js +37 -0
  32. package/dist/esm/msal-config-schema.js.map +1 -0
  33. package/dist/esm/telemetry-config-schema.js +16 -0
  34. package/dist/esm/telemetry-config-schema.js.map +1 -0
  35. package/dist/esm/version.js +1 -1
  36. package/dist/esm/version.js.map +1 -1
  37. package/dist/esm/versioning/resolve-version.js +0 -1
  38. package/dist/esm/versioning/resolve-version.js.map +1 -1
  39. package/dist/tsconfig.tsbuildinfo +1 -1
  40. package/dist/types/MsalConfigurator.d.ts +96 -20
  41. package/dist/types/__tests__/create-proxy-provider.test.d.ts +1 -0
  42. package/dist/types/__tests__/mock/create-mock-user-from-token.test.d.ts +1 -0
  43. package/dist/types/__tests__/mock/msal-mock.test.d.ts +1 -0
  44. package/dist/types/index.d.ts +5 -0
  45. package/dist/types/mock/MsalMockClient.d.ts +282 -0
  46. package/dist/types/mock/MsalMockConfigurator.d.ts +186 -0
  47. package/dist/types/mock/create-mock-token.d.ts +54 -0
  48. package/dist/types/mock/create-mock-user-from-token.d.ts +25 -0
  49. package/dist/types/mock/create-msal-mock-client.d.ts +14 -0
  50. package/dist/types/mock/decode-jwt-segment.d.ts +13 -0
  51. package/dist/types/mock/index.d.ts +30 -0
  52. package/dist/types/mock/module.d.ts +35 -0
  53. package/dist/types/msal-config-schema.d.ts +64 -0
  54. package/dist/types/telemetry-config-schema.d.ts +8 -0
  55. package/dist/types/version.d.ts +1 -1
  56. package/docs/api-reference.md +85 -0
  57. package/docs/auth-code-flow.md +86 -0
  58. package/docs/migration-v2-to-v4.md +115 -0
  59. package/docs/testing.md +191 -0
  60. package/docs/troubleshooting.md +17 -0
  61. package/docs/version-management.md +67 -0
  62. package/package.json +12 -5
  63. package/src/MsalConfigurator.ts +202 -115
  64. package/src/__tests__/MsalConfigurator.test.ts +106 -0
  65. package/src/__tests__/create-proxy-provider.test.ts +77 -0
  66. package/src/__tests__/mock/create-mock-user-from-token.test.ts +46 -0
  67. package/src/__tests__/mock/msal-mock.test.ts +613 -0
  68. package/src/index.ts +6 -0
  69. package/src/mock/MsalMockClient.ts +618 -0
  70. package/src/mock/MsalMockConfigurator.ts +305 -0
  71. package/src/mock/create-mock-token.ts +92 -0
  72. package/src/mock/create-mock-user-from-token.ts +46 -0
  73. package/src/mock/create-msal-mock-client.ts +25 -0
  74. package/src/mock/decode-jwt-segment.ts +22 -0
  75. package/src/mock/index.ts +30 -0
  76. package/src/mock/module.ts +54 -0
  77. package/src/msal-config-schema.ts +81 -0
  78. package/src/telemetry-config-schema.ts +25 -0
  79. package/src/version.ts +1 -1
  80. package/src/versioning/resolve-version.ts +0 -1
  81. package/vitest.config.ts +1 -1
  82. package/.changeset/msal-auth-provider-fix.md +0 -7
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Claims that can be set on a generated mock token.
3
+ *
4
+ * @remarks
5
+ * Mirrors the subset of Entra ID claims that Fusion applications read. Any
6
+ * additional claims are passed through unchanged.
7
+ */
8
+ export interface MockTokenClaims {
9
+ /** Object ID of the signed-in user. */
10
+ oid?: string;
11
+ /** Display name of the signed-in user. */
12
+ name?: string;
13
+ /** UPN / email of the signed-in user. */
14
+ preferred_username?: string;
15
+ /** Tenant the token was issued for. */
16
+ tid?: string;
17
+ /** Audience — normally the client or resource the token is intended for. */
18
+ aud?: string;
19
+ /** Issuer. */
20
+ iss?: string;
21
+ /** Scopes granted, space-separated as in a real Entra ID token. */
22
+ scp?: string;
23
+ /** Issued-at, seconds since epoch. */
24
+ iat?: number;
25
+ /** Not-before, seconds since epoch. */
26
+ nbf?: number;
27
+ /** Expiry, seconds since epoch. */
28
+ exp?: number;
29
+ [claim: string]: unknown;
30
+ }
31
+ /**
32
+ * Creates a structurally valid, unsigned JWT for use in tests.
33
+ *
34
+ * The token has three base64url segments and decodes to the supplied claims, so
35
+ * code that splits, decodes, or inspects token claims behaves exactly as it does
36
+ * in production. The signature segment is a fixed placeholder — the token is
37
+ * **not** cryptographically valid and will be rejected by any real service.
38
+ *
39
+ * @remarks
40
+ * Timestamps default to a fixed issue time and a one-hour lifetime so repeated
41
+ * runs produce byte-identical tokens. A test that needs an expired token can set
42
+ * `exp` in the past.
43
+ *
44
+ * @param claims - Claims to embed in the token payload.
45
+ * @returns An unsigned JWT string in `header.payload.signature` form.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const token = createMockToken({ name: 'Test User', scp: 'Files.Read' });
50
+ * const [, payload] = token.split('.');
51
+ * JSON.parse(decodeJwtSegment(payload)).name; // 'Test User'
52
+ * ```
53
+ */
54
+ export declare const createMockToken: (claims?: MockTokenClaims) => string;
@@ -0,0 +1,25 @@
1
+ import type { MsalMockUser } from './MsalMockClient';
2
+ /**
3
+ * Derives a {@link MsalMockUser} from a JWT's payload claims, so a token minted
4
+ * outside this module (e.g. by a backend's own mock) can drive who the mock
5
+ * signs in as.
6
+ *
7
+ * @remarks
8
+ * Maps the standard Entra ID claims Fusion applications read — `name`,
9
+ * `preferred_username`, `oid`, `tid`, `scp` — onto the matching
10
+ * {@link MsalMockUser} fields. Identity only: it does not affect which token
11
+ * the client returns — use {@link MsalMockConfigurator.setToken} for that.
12
+ *
13
+ * @param token - A JWT (e.g. from {@link createMockToken}, or issued by an
14
+ * external mock) with a base64url-encoded payload segment.
15
+ * @returns A mock user built from the token's claims.
16
+ * @throws When the token has no payload segment (`header.payload.signature`).
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * enableMsalMock(configurator, (builder) => {
21
+ * builder.setAccount(createMockUserFromToken(token));
22
+ * });
23
+ * ```
24
+ */
25
+ export declare const createMockUserFromToken: (token: string) => MsalMockUser;
@@ -0,0 +1,14 @@
1
+ import type { IMsalClient } from '../MsalClient.interface';
2
+ import type { MsalClientConfig } from '../MsalClient';
3
+ import { type MsalMockUser } from './MsalMockClient';
4
+ /**
5
+ * Convenience helper that creates a mock client instance.
6
+ *
7
+ * @remarks
8
+ * The class form is preferred for a more familiar configuration pattern.
9
+ *
10
+ * @param config - The same client configuration the real client is built from.
11
+ * @param user - Optional user to sign in, applied after construction.
12
+ * @returns A client that resolves tokens in-process.
13
+ */
14
+ export declare const createMsalMockClient: (config: MsalClientConfig, user?: MsalMockUser) => IMsalClient;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Decodes a base64url segment of a {@link createMockToken} JWT back to its JSON string.
3
+ *
4
+ * @remarks
5
+ * Plain `atob` alone mangles non-ASCII claims: it treats its output as latin1,
6
+ * while segments are UTF-8 encoded. This reverses that encoding and also
7
+ * restores the standard base64 alphabet/padding `atob` expects.
8
+ *
9
+ * @param segment - A base64url segment, e.g. from splitting a JWT on `.`.
10
+ * @returns The decoded UTF-8 string.
11
+ */
12
+ export declare function decodeJwtSegment(segment: string): string;
13
+ export default decodeJwtSegment;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Mock MSAL for tests: real provider, real configurator, fake client.
3
+ *
4
+ * @remarks
5
+ * Substituting the client is the smallest change that removes Entra ID from a test.
6
+ * Everything above it — scope resolution, silent-first token acquisition, account
7
+ * handling, proxy providers, telemetry — is the production code path.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { enableMsalMock } from '@equinor/fusion-framework-module-msal/mock';
12
+ *
13
+ * // default mock user
14
+ * enableMsalMock(configurator);
15
+ *
16
+ * // or a specific one
17
+ * enableMsalMock(configurator, (builder) => {
18
+ * builder.setAccount({ name: 'Ada Lovelace', signedOut: true });
19
+ * });
20
+ * ```
21
+ *
22
+ * @packageDocumentation
23
+ */
24
+ export { MsalMockClient, type MsalMockUser } from './MsalMockClient';
25
+ export { createMsalMockClient } from './create-msal-mock-client';
26
+ export { MsalMockConfigurator } from './MsalMockConfigurator';
27
+ export { enableMsalMock, msalMockModule, type AuthConfigMockFn } from './module';
28
+ export { createMockToken, type MockTokenClaims } from './create-mock-token';
29
+ export { createMockUserFromToken } from './create-mock-user-from-token';
30
+ export { decodeJwtSegment } from './decode-jwt-segment';
@@ -0,0 +1,35 @@
1
+ import type { IModulesConfigurator } from '@equinor/fusion-framework-module';
2
+ import { type MsalModule } from '../module';
3
+ import { MsalMockConfigurator } from './MsalMockConfigurator';
4
+ /**
5
+ * The MSAL module with a mock client instead of a live connection to Entra ID.
6
+ *
7
+ * @remarks
8
+ * Only `configure` differs from the real module. `initialize` is the production
9
+ * one, untouched, so proxy providers, host-provider hoisting and provider
10
+ * initialization all behave exactly as they do in production — and a test
11
+ * observes the real start-up path rather than a rehearsal of it.
12
+ */
13
+ export declare const msalMockModule: MsalModule;
14
+ /**
15
+ * Configuration callback for {@link enableMsalMock}.
16
+ */
17
+ export type AuthConfigMockFn<TRef = unknown> = (configurator: MsalMockConfigurator, ref?: TRef) => void;
18
+ /**
19
+ * Enables MSAL against a mock client, so a test needs no credentials and no network.
20
+ *
21
+ * @remarks
22
+ * Registered last, this replaces whichever auth module the configurator already
23
+ * carries, so it works on a `FrameworkConfigurator` that pre-registers the real one.
24
+ *
25
+ * @param configurator - The modules configurator to register on.
26
+ * @param configure - Optional callback to override the default mock client.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * enableMsalMock(configurator, (builder) => {
31
+ * builder.setAccount({ name: 'Ada Lovelace' });
32
+ * });
33
+ * ```
34
+ */
35
+ export declare const enableMsalMock: (configurator: IModulesConfigurator<any, any>, configure?: AuthConfigMockFn) => void;
@@ -0,0 +1,64 @@
1
+ import z from 'zod';
2
+ import { CacheLookupPolicy } from '@azure/msal-browser';
3
+ import type { IMsalClient } from './MsalClient.interface';
4
+ import type { IMsalProvider } from './MsalProvider.interface';
5
+ export type { TelemetryConfig } from './telemetry-config-schema';
6
+ /**
7
+ * Zod schema for MSAL module configuration validation.
8
+ *
9
+ * @remarks
10
+ * Kept in its own module so the configuration can be extended at its source.
11
+ * The schema itself describes what reaches `MsalProvider` and strips anything
12
+ * else; keys a variant of this module needs only while the configuration is
13
+ * being built are declared on {@link MsalConfigExtension} instead.
14
+ */
15
+ export declare const MsalConfigSchema: z.ZodObject<{
16
+ client: z.ZodOptional<z.ZodCustom<IMsalClient, IMsalClient>>;
17
+ provider: z.ZodOptional<z.ZodCustom<IMsalProvider, IMsalProvider>>;
18
+ requiresAuth: z.ZodOptional<z.ZodBoolean>;
19
+ redirectUri: z.ZodOptional<z.ZodString>;
20
+ loginHint: z.ZodOptional<z.ZodString>;
21
+ authCode: z.ZodOptional<z.ZodString>;
22
+ cacheLookupPolicy: z.ZodOptional<z.ZodCustom<CacheLookupPolicy, CacheLookupPolicy>>;
23
+ version: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
24
+ telemetry: z.ZodObject<{
25
+ provider: z.ZodOptional<z.ZodCustom<import("@equinor/fusion-framework-module-telemetry").ITelemetryProvider, import("@equinor/fusion-framework-module-telemetry").ITelemetryProvider>>;
26
+ metadata: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
27
+ scope: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
28
+ }, z.core.$strip>;
29
+ }, z.core.$strip>;
30
+ /**
31
+ * Configuration a variant of this module adds to {@link MsalConfig}.
32
+ *
33
+ * @remarks
34
+ * Empty by design: production MSAL configuration is exactly the schema. This
35
+ * exists so a variant — the test double in `./mock`, for instance — can declare
36
+ * its own branch of the configuration through declaration merging:
37
+ *
38
+ * ```typescript
39
+ * declare module '@equinor/fusion-framework-module-msal' {
40
+ * interface MsalConfigExtension {
41
+ * mock?: { account?: MsalMockUser };
42
+ * }
43
+ * }
44
+ * ```
45
+ *
46
+ * That is what keeps `BaseConfigBuilder._set` honest about the added key. Its
47
+ * target is a dot-path union derived from {@link MsalConfig}, so a key the type
48
+ * does not know about can only be set by casting past the builder — and a
49
+ * generic configurator cannot help, because a dot-path union over an unresolved
50
+ * type parameter defers, taking every existing literal path down with it.
51
+ *
52
+ * The key exists to carry a declaration across the builder, not to reach the
53
+ * provider: the schema strips it during validation, so it is readable from the
54
+ * raw configuration and absent from the validated one.
55
+ */
56
+ export interface MsalConfigExtension {
57
+ }
58
+ /**
59
+ * Complete configuration object for MSAL authentication module.
60
+ *
61
+ * This type represents the full configuration including client setup, authentication
62
+ * requirements, telemetry, and version information.
63
+ */
64
+ export type MsalConfig = z.infer<typeof MsalConfigSchema> & MsalConfigExtension;
@@ -0,0 +1,8 @@
1
+ import z from 'zod';
2
+ /**
3
+ * Telemetry configuration for MSAL module.
4
+ *
5
+ * This configuration controls how authentication events are tracked and logged
6
+ * through the framework's telemetry system.
7
+ */
8
+ export type TelemetryConfig = z.infer<typeof TelemetryConfigSchema>;
@@ -1 +1 @@
1
- export declare const version = "10.0.2";
1
+ export declare const version = "11.0.0-next.1";
@@ -0,0 +1,85 @@
1
+ # MSAL API Reference
2
+
3
+ ## `enableMSAL(configurator, configure?)`
4
+
5
+ Enables the MSAL module in your Fusion Framework application.
6
+
7
+ **Parameters:**
8
+ - `configurator`: `IModulesConfigurator` - The modules configurator instance
9
+ - `configure?`: `(builder: { setClientConfig, setRequiresAuth }) => void` - Optional configuration function
10
+
11
+ **Returns:** `void`
12
+
13
+ **Example:**
14
+ ```typescript
15
+ enableMSAL(configurator, (builder) => {
16
+ builder.setClientConfig({ auth: { clientId: '...', tenantId: '...' } });
17
+ builder.setRequiresAuth(true);
18
+ });
19
+ ```
20
+
21
+ ## Type Definitions
22
+
23
+ ### `LoginOptions`
24
+
25
+ ```typescript
26
+ type LoginOptions = {
27
+ request: PopupRequest | RedirectRequest; // MSAL request object
28
+ behavior?: 'popup' | 'redirect'; // Auth method (default: 'redirect')
29
+ silent?: boolean; // Attempt silent auth first (default: true)
30
+ };
31
+ ```
32
+
33
+ ### `LogoutOptions`
34
+
35
+ ```typescript
36
+ type LogoutOptions = {
37
+ redirectUri?: string; // Redirect after logout
38
+ account?: AccountInfo; // Account to logout (defaults to active)
39
+ };
40
+ ```
41
+
42
+ ### `AcquireTokenOptions`
43
+
44
+ ```typescript
45
+ type AcquireTokenOptions = {
46
+ request: PopupRequest | RedirectRequest; // MSAL request with scopes
47
+ behavior?: 'popup' | 'redirect'; // Auth method (default: 'redirect')
48
+ silent?: boolean; // Attempt silent first (default: true if account available)
49
+ };
50
+ ```
51
+
52
+ ## `IMsalProvider`
53
+
54
+ The authentication provider interface available at `framework.auth`:
55
+
56
+ ```typescript
57
+ interface IMsalProvider {
58
+ // The MSAL PublicClientApplication instance
59
+ readonly client: IMsalClient;
60
+
61
+ // Current user account information
62
+ readonly account: AccountInfo | null;
63
+
64
+ // Initialize the MSAL provider
65
+ initialize(): Promise<void>;
66
+
67
+ // Acquire an access token for the specified scopes
68
+ acquireAccessToken(options: AcquireTokenOptionsLegacy): Promise<string | undefined>;
69
+
70
+ // Acquire full authentication result
71
+ acquireToken(options: AcquireTokenOptionsLegacy): Promise<AcquireTokenResult>;
72
+
73
+ // Login user interactively
74
+ login(options: LoginOptions): Promise<LoginResult>;
75
+
76
+ // Logout user (returns boolean)
77
+ logout(options?: LogoutOptions): Promise<boolean>;
78
+
79
+ // Handle authentication redirect (returns AuthenticationResult | null)
80
+ handleRedirect(): Promise<AuthenticationResult | null>;
81
+ }
82
+
83
+ // Note: defaultAccount and other deprecated v2 properties are available only
84
+ // when using a v2-compatible proxy via createProxyProvider()
85
+ ```
@@ -0,0 +1,86 @@
1
+ # Backend-Issued Auth Code Flow
2
+
3
+ Enable automatic sign-in using a backend-issued authorization code without interactive login prompts.
4
+
5
+ ## Overview
6
+
7
+ When your backend authenticates a user and generates a short-lived SPA auth code, the MSAL module can exchange it for tokens during initialization, eliminating double-login issues and providing seamless authentication.
8
+
9
+ ## Usage
10
+
11
+ ```typescript
12
+ import { enableMSAL } from '@equinor/fusion-framework-module-msal';
13
+
14
+ enableMSAL(configurator, (builder) => {
15
+ builder.setClientConfig({
16
+ auth: {
17
+ clientId: 'your-client-id',
18
+ tenantId: 'your-tenant-id'
19
+ }
20
+ });
21
+
22
+ // Backend injects auth code as window.MSAL_AUTH_CODE during initial first page load
23
+ // This is the most secure approach - only available on first render, cleared after use
24
+ if (typeof window !== 'undefined' && window.MSAL_AUTH_CODE) {
25
+ builder.setAuthCode(window.MSAL_AUTH_CODE);
26
+ delete (window as any).MSAL_AUTH_CODE; // Clear after consuming
27
+ }
28
+
29
+ builder.setRequiresAuth(true);
30
+ });
31
+ ```
32
+
33
+ ## How It Works
34
+
35
+ 1. Backend authenticates user and generates short-lived auth code
36
+ 2. Frontend passes auth code to MSAL: `builder.setAuthCode(authCode)`
37
+ 3. During `initialize()`: auth code exchanged for tokens (before `requiresAuth` check)
38
+ 4. Tokens cached by MSAL → user automatically signed in
39
+ 5. Falls back to standard MSAL flows on exchange failure
40
+
41
+ ## API: `setAuthCode(authCode?: string)`
42
+
43
+ Sets backend-issued auth code for token exchange during initialization.
44
+
45
+ Pass `undefined` to clear/reset a previously configured auth code.
46
+
47
+ **Returns:** configurator instance (chainable)
48
+
49
+ **Behavior:**
50
+ - Exchange happens before `requiresAuth` check
51
+ - On success: user auto-authenticated, no login prompt
52
+ - On failure: falls back to standard MSAL login
53
+ - Auth code cleared after exchange (no reuse)
54
+ - `setAuthCode(undefined)` clears configured auth code
55
+ - `setAuthCode('')` is treated as absent auth code
56
+ - `setAuthCode(' ')` is trimmed and treated as absent auth code
57
+ - No auth-code exchange is attempted when auth code is absent/cleared
58
+
59
+ **Example:**
60
+
61
+ ```typescript
62
+ // Best practice: Backend injects auth code on initial page load as window.MSAL_AUTH_CODE
63
+ if (typeof window !== 'undefined' && window.MSAL_AUTH_CODE) {
64
+ builder.setAuthCode(window.MSAL_AUTH_CODE);
65
+ delete (window as any).MSAL_AUTH_CODE; // Clear after consuming to prevent reuse
66
+ }
67
+
68
+ // Clear/reset auth code when input is missing
69
+ builder.setAuthCode(undefined);
70
+ ```
71
+
72
+ ## Security
73
+
74
+ - ✅ Auth codes: single-use, short-lived (5-10 min)
75
+ - ✅ MSAL validates tokens from Microsoft authority
76
+ - ✅ Tokens stored securely, refresh tokens auto-managed
77
+ - ⚠️ Pass codes securely: HTTPS, HTTP-only cookies, or encrypted channels
78
+
79
+ ## Troubleshooting
80
+
81
+ | Issue | Solution |
82
+ |-------|----------|
83
+ | Auth code not exchanged | Verify `setAuthCode()` called before init |
84
+ | Invalid auth code error | Confirm backend `WithSpaAuthCode` enabled, code is fresh |
85
+ | Still shows login prompt | Check auth code exchange completes before `requiresAuth` check |
86
+ | Exchange fails | Auth code may have expired; backend should generate fresh code per load |
@@ -0,0 +1,115 @@
1
+ # Migration Guide
2
+
3
+ ## MSAL v2 to v4 Migration
4
+
5
+ This package has been upgraded from MSAL Browser v2 to v4, providing the latest security improvements and features from Microsoft.
6
+
7
+ ### What Changed in v4
8
+
9
+ **New MSAL Browser v4 Features:**
10
+ - Enhanced security with improved token management
11
+ - Better performance and memory usage
12
+ - New authentication API structure with nested request objects
13
+ - Improved error handling and retry mechanisms
14
+
15
+ **Architecture Changes:**
16
+ - **Module Hoisting**: The module uses module hoisting, meaning sub-module instances proxy the parent module instance
17
+ - **Shared Authentication State**: Authentication state is shared across all module instances
18
+ - **Async Initialization**: New `initialize()` method must be called before using the provider
19
+
20
+ ### Breaking Changes
21
+
22
+ 1. **Auto-initialization via Framework**
23
+ ```typescript
24
+ // The provider initializes automatically when framework loads
25
+ const framework = await initialize(configurator);
26
+ const auth = framework.auth; // Already initialized
27
+
28
+ // Manual initialization is only needed for standalone usage
29
+ const provider = new MsalProvider(config);
30
+ await provider.initialize();
31
+ ```
32
+
33
+ 2. **API Method Signature Updates**
34
+ - `logout()` now returns `Promise<boolean>` instead of `Promise<void>`
35
+ - `handleRedirect()` now returns `Promise<AuthenticationResult | null>` instead of `Promise<void>`
36
+ - Methods now expect nested request objects (v4 format)
37
+
38
+ 3. **Account Property Changes**
39
+ - Use `account` property (returns `AccountInfo | null`) - v4 native
40
+ - `defaultAccount` is deprecated and only available via v2 proxy layer
41
+ - Migration: Replace `defaultAccount` with `account` throughout your code
42
+
43
+ ### Migration Steps
44
+
45
+ 1. **Update Token Acquisition** (Recommended)
46
+ ```typescript
47
+ // Before (v2 format - still works via proxy)
48
+ const token = await framework.auth.acquireAccessToken({
49
+ scopes: ['api.read']
50
+ });
51
+
52
+ // After (v4 format - recommended)
53
+ const token = await framework.auth.acquireAccessToken({
54
+ request: { scopes: ['api.read'] }
55
+ });
56
+ ```
57
+
58
+ 2. **Update Logout Handling**
59
+ ```typescript
60
+ // Before
61
+ await framework.auth.logout();
62
+
63
+ // After (check return value)
64
+ const success = await framework.auth.logout();
65
+ if (success) {
66
+ // Handle successful logout
67
+ }
68
+ ```
69
+
70
+ 3. **Update Redirect Handling**
71
+ ```typescript
72
+ // Before
73
+ await framework.auth.handleRedirect();
74
+
75
+ // After (handle result)
76
+ const result = await framework.auth.handleRedirect();
77
+ if (result?.account) {
78
+ // User authenticated successfully
79
+ console.log('Logged in as:', result.account.username);
80
+ }
81
+ ```
82
+
83
+ 4. **Update Configuration** (if needed)
84
+ ```typescript
85
+ // Ensure only the root module configures MSAL
86
+ enableMSAL(configurator, (builder) => {
87
+ builder.setClientConfig({
88
+ auth: {
89
+ clientId: 'your-client-id',
90
+ tenantId: 'your-tenant-id',
91
+ redirectUri: 'https://your-app.com/callback'
92
+ }
93
+ });
94
+ builder.setRequiresAuth(true);
95
+ });
96
+ ```
97
+
98
+ 5. **Remove Duplicate Configurations**: Remove MSAL configuration from child modules
99
+
100
+ ### Backward Compatibility
101
+
102
+ The module includes a **v2 proxy layer** that automatically converts v2 API calls to v4 format. This means:
103
+ - ✅ Existing code continues to work without changes
104
+ - ✅ Legacy format `{ scopes: [] }` is still supported
105
+ - ✅ Deprecated v2 properties like `defaultAccount` are available via v2 proxy (with deprecation warnings)
106
+ - ⚠️ New v4 features require using v4 format
107
+
108
+ ### Benefits of Migration
109
+
110
+ - **Better Security**: Latest MSAL v4 security improvements and token handling
111
+ - **Improved Performance**: Faster token acquisition, better caching, reduced memory usage
112
+ - **Enhanced Error Handling**: More robust error recovery and retry mechanisms
113
+ - **Future-Proof**: Access to latest Microsoft authentication features and updates
114
+ - **Shared State**: Improved authentication state management across app scopes via module hoisting
115
+ - **Better Developer Experience**: Cleaner API, better TypeScript support, comprehensive documentation