Microsoft Authentication Library (MSAL) integration module for Fusion Framework
npm install @equinor/fusion-framework-module-msal@equinor/fusion-framework-module-msal provides secure Azure AD authentication for browser applications using Microsoft's MSAL (Microsoft Authentication Library). Perfect for web applications, SPAs, and React apps that need to authenticate with Microsoft services.
> Version: This package now uses MSAL Browser v4, providing the latest security improvements and features from Microsoft.
- Single Sign-On (SSO) support for Microsoft Azure AD and Azure AD B2C
- Token Management with automatic refresh and secure caching
- Module Hoisting for shared authentication state across application scopes
- Silent Authentication for seamless user experience
- Popup & Redirect Flows for different authentication scenarios
- Zero Configuration with sensible defaults and optional customization
- MSAL v4 Compatibility with v2 proxy layer for backward compatibility
``bash`
pnpm add @equinor/fusion-framework-module-msal
`typescript
import { enableMSAL, initialize, type IMsalProvider } from '@equinor/fusion-framework-module-msal';
import { ModulesConfigurator } from '@equinor/fusion-framework-module';
// 1. Configure the module
const configurator = new ModulesConfigurator();
enableMSAL(configurator, (builder) => {
builder.setClientConfig({
auth: {
clientId: 'your-client-id',
tenantId: 'your-tenant-id',
redirectUri: 'https://your-app.com/callback'
}
});
// With requiresAuth=true, the module will attempt automatic login during initialization
// and await a valid authenticated account before initialization completes
builder.setRequiresAuth(true);
});
// 2. Initialize the framework (auto-initializes auth provider)
const framework = await initialize(configurator);
const auth: IMsalProvider = framework.auth;
// 3. Optional: Handle authentication redirect manually (auto-called during initialization)
const redirectResult = await auth.handleRedirect();
if (redirectResult?.account) {
console.log('Authenticated:', redirectResult.account.username);
}
// 4. Use authentication
// Option A: Token acquisition (v4 format - recommended)
const token = await auth.acquireAccessToken({
request: { scopes: ['api://your-app-id/.default'] }
});
// Option B: Legacy format (still supported via v2 proxy)
const legacyToken = await auth.acquireAccessToken({
scopes: ['api://your-app-id/.default']
});
// Option C: Silent authentication with fallback
try {
const result = await auth.login({
request: { scopes: ['api://your-app-id/.default'] },
silent: true // Attempts SSO first
});
} catch {
// Fallback to interactive if silent fails
await auth.login({
request: { scopes: ['api://your-app-id/.default'] },
behavior: 'popup'
});
}
`
> [!IMPORTANT]
> The @equinor/fusion-framework-app enables this package by default, so applications using the app package do not need to enable this module manually.
| Setting | Description | Required |
|---------|-------------|----------|
| auth.clientId | Azure AD application client ID | ✅ |auth.tenantId
| | Azure AD tenant ID | ✅ |auth.redirectUri
| | Authentication callback URL | Optional |
| Setting | Description | Default |
|---------|-------------|---------|
| requiresAuth | Auto-authenticate on initialization | false |version
| | Force specific MSAL version | Latest |
`bashRequired
AZURE_CLIENT_ID=your-client-id
AZURE_TENANT_ID=your-tenant-id
API Reference
$3
Enables the MSAL module in your Fusion Framework application.
Parameters:
-
configurator: IModulesConfigurator - The modules configurator instance
- configure?: (builder: { setClientConfig, setRequiresAuth }) => void - Optional configuration functionReturns:
voidExample:
`typescript
enableMSAL(configurator, (builder) => {
builder.setClientConfig({ auth: { clientId: '...', tenantId: '...' } });
builder.setRequiresAuth(true);
});
`$3
####
LoginOptions`typescript
type LoginOptions = {
request: PopupRequest | RedirectRequest; // MSAL request object
behavior?: 'popup' | 'redirect'; // Auth method (default: 'redirect')
silent?: boolean; // Attempt silent auth first (default: true)
};
`####
LogoutOptions`typescript
type LogoutOptions = {
redirectUri?: string; // Redirect after logout
account?: AccountInfo; // Account to logout (defaults to active)
};
`####
AcquireTokenOptions`typescript
type AcquireTokenOptions = {
request: PopupRequest | RedirectRequest; // MSAL request with scopes
behavior?: 'popup' | 'redirect'; // Auth method (default: 'redirect')
silent?: boolean; // Attempt silent first (default: true if account available)
};
`$3
The authentication provider interface available at
framework.auth:`typescript
interface IMsalProvider {
// The MSAL PublicClientApplication instance
readonly client: IMsalClient;
// Current user account information
readonly account: AccountInfo | null;
// Initialize the MSAL provider
initialize(): Promise;
// Acquire an access token for the specified scopes
acquireAccessToken(options: AcquireTokenOptionsLegacy): Promise;
// Acquire full authentication result
acquireToken(options: AcquireTokenOptionsLegacy): Promise;
// Login user interactively
login(options: LoginOptions): Promise;
// Logout user (returns boolean)
logout(options?: LogoutOptions): Promise;
// Handle authentication redirect (returns AuthenticationResult | null)
handleRedirect(): Promise;
}// Note: defaultAccount and other deprecated v2 properties are available only
// when using a v2-compatible proxy via createProxyProvider()
`
Module Hoisting
The module implements a hoisting pattern where the authentication provider is created once at the root level and shared across all sub-modules. This ensures consistent authentication state throughout your application while maintaining security and performance.
> [!IMPORTANT]
> Configure the auth module only in the root Fusion Framework instance - Sub-instances will automatically inherit the authentication configuration from the parent.
Migration Guide
$3
This package has been upgraded from MSAL Browser v2 to v4, providing the latest security improvements and features from Microsoft.
#### What Changed in v4
New MSAL Browser v4 Features:
- Enhanced security with improved token management
- Better performance and memory usage
- New authentication API structure with nested request objects
- Improved error handling and retry mechanisms
Architecture Changes:
- Module Hoisting: The module uses module hoisting, meaning sub-module instances proxy the parent module instance
- Shared Authentication State: Authentication state is shared across all module instances
- Async Initialization: New
initialize() method must be called before using the provider#### Breaking Changes
1. Auto-initialization via Framework
`typescript
// The provider initializes automatically when framework loads
const framework = await initialize(configurator);
const auth = framework.auth; // Already initialized
// Manual initialization is only needed for standalone usage
const provider = new MsalProvider(config);
await provider.initialize();
`2. API Method Signature Updates
-
logout() now returns Promise instead of Promise
- handleRedirect() now returns Promise instead of Promise
- Methods now expect nested request objects (v4 format)3. Account Property Changes
- Use
account property (returns AccountInfo | null) - v4 native
- defaultAccount is deprecated and only available via v2 proxy layer
- Migration: Replace defaultAccount with account throughout your code#### Migration Steps
1. Update Token Acquisition (Recommended)
`typescript
// Before (v2 format - still works via proxy)
const token = await framework.auth.acquireAccessToken({
scopes: ['api.read']
});
// After (v4 format - recommended)
const token = await framework.auth.acquireAccessToken({
request: { scopes: ['api.read'] }
});
`2. Update Logout Handling
`typescript
// Before
await framework.auth.logout();
// After (check return value)
const success = await framework.auth.logout();
if (success) {
// Handle successful logout
}
`3. Update Redirect Handling
`typescript
// Before
await framework.auth.handleRedirect();
// After (handle result)
const result = await framework.auth.handleRedirect();
if (result?.account) {
// User authenticated successfully
console.log('Logged in as:', result.account.username);
}
`4. Update Configuration (if needed)
`typescript
// Ensure only the root module configures MSAL
enableMSAL(configurator, (builder) => {
builder.setClientConfig({
auth: {
clientId: 'your-client-id',
tenantId: 'your-tenant-id',
redirectUri: 'https://your-app.com/callback'
}
});
builder.setRequiresAuth(true);
});
`5. Remove Duplicate Configurations: Remove MSAL configuration from child modules
#### Backward Compatibility
The module includes a v2 proxy layer that automatically converts v2 API calls to v4 format. This means:
- ✅ Existing code continues to work without changes
- ✅ Legacy format
{ scopes: [] } is still supported
- ✅ Deprecated v2 properties like defaultAccount are available via v2 proxy (with deprecation warnings)
- ⚠️ New v4 features require using v4 format#### Benefits of Migration
- Better Security: Latest MSAL v4 security improvements and token handling
- Improved Performance: Faster token acquisition, better caching, reduced memory usage
- Enhanced Error Handling: More robust error recovery and retry mechanisms
- Future-Proof: Access to latest Microsoft authentication features and updates
- Shared State: Improved authentication state management across app scopes via module hoisting
- Better Developer Experience: Cleaner API, better TypeScript support, comprehensive documentation
Troubleshooting
$3
| Issue | Solution |
|-------|----------|
| Authentication Loop | Ensure redirect URIs match your application's routing |
| Token Acquisition Fails | Check that required scopes are properly configured |
| Module Not Found | Ensure the module is properly configured and framework is initialized |
| Multiple MSAL Instances | Remove duplicate configurations from child modules |
| Redirect Returns Void | For redirect flows, use
handleRedirect() after navigation completes |
| Token Empty/Undefined | Verify user is authenticated and scopes are correct |$3
- 📖 MSAL Cookbook - Complete working examples
- 🐛 Report Issues - Bug reports and feature requests
Version Management
The MSAL module includes built-in version checking to ensure compatibility between different MSAL library versions.
$3
`typescript
import { resolveVersion, VersionError } from '@equinor/fusion-framework-module-msal/versioning';// Resolve and validate a version
const result = resolveVersion('2.0.0');
console.log(result.isLatest); // false
console.log(result.satisfiesLatest); // true
console.log(result.enumVersion); // MsalModuleVersion.V2
`$3
- Major Version Incompatibility: Throws
VersionError if requested major version is greater than latest
- Minor Version Mismatch: Logs warning but allows execution
- Patch Differences: Ignored for compatibility
- Invalid Versions: Throws VersionError with descriptive message$3
####
resolveVersion(version: string | SemVer): ResolvedVersionResolves and validates a version string against the latest available MSAL version.
Parameters:
-
version - Version string or SemVer object to resolveReturns:
ResolvedVersion object containing:
- wantedVersion: SemVer - The parsed requested version
- latestVersion: SemVer - The latest available version
- isLatest: boolean - Whether the version is exactly the latest
- satisfiesLatest: boolean - Whether the major version matches latest
- enumVersion: MsalModuleVersion - Corresponding enum versionThrows:
VersionError for invalid or incompatible versions####
VersionErrorError class for version-related issues with the following types:
-
InvalidVersion - Requested version is not a valid semver
- InvalidLatestVersion - Latest version parsing failed (build issue)
- MajorIncompatibility - Major version is greater than latest
- MinorMismatch - Minor version differs (warning only)
- PatchDifference - Patch version differs (info only)
- IncompatibleVersion - General incompatibility$3
`typescript
import { resolveVersion, VersionError } from '@equinor/fusion-framework-module-msal/versioning';try {
const result = resolveVersion('3.0.0'); // Assuming latest is 2.x
} catch (error) {
if (error instanceof VersionError) {
console.error('Version error:', error.message);
console.error('Requested:', error.requestedVersion);
console.error('Latest:', error.latestVersion);
console.error('Type:', error.type);
}
}
``