import { type SPEvent } from '@microsoft/sp-core-library'; import type { BeforeRedirectEventArgs, PopupEventArgs, TokenAcquisitionEventArgs } from './AadTokenProvider'; import type { IAadTokenProvider, IAadTokenProviderConfiguration, IGetTokenDataOptions, IGetTokenOptions } from './IAadTokenProvider'; import type { IAdalAuthContextManager, ISPOBOFlowParameters, ITokenData, ITokenProvider, _OBO3PTokenFunction, OBOTokenFunction } from './ITokenProvider'; /** * Deferred implementation of AAD token provider that lazy-loads MSAL authentication libraries. * * **What Makes This "Deferred"**: * Unlike {@link AadTokenProvider}, this implementation delays loading the MSAL authentication * library until the first token request. This reduces initial page load time by deferring * the download and initialization of authentication components. * * **Use Cases**: * - Authenticate users to Azure AD protected resources (Graph, PowerBI, Exchange, etc.) * - Acquire OAuth2 access tokens for APIs * - Support both first-party (Microsoft) and third-party applications * - Minimize initial bundle size by lazy-loading authentication dependencies * * **Authentication Flow**: * This provider delegates to {@link MsalBrowserTokenProvider} and supports the same flow: * 1. **Silent Token Acquisition** - Attempts to get token silently using cached credentials or SSO * 2. **NAA (Nested App Authentication)** - If in Teams/Outlook and silent fails, uses native broker * 3. **Popup Authentication** (Optional, tenant-configurable) - If NAA unavailable and popup enabled * 4. **Redirect Authentication** - If popup disabled/unavailable/cancelled * * **NAA (Nested App Authentication) Limitation**: * When running in Teams, Outlook, or mobile webviews with NAA support: * - **Events are NOT raised** - {@link DeferredAadTokenProvider.popupEvent} and {@link DeferredAadTokenProvider.onBeforeRedirectEvent} will not fire * - **Cannot cancel authentication** - No way to prevent native account picker from showing * - **Error handling only** - Can only catch errors when NAA fails * - See {@link DeferredAadTokenProvider.getToken} for details on NAA behavior * * **Library Loading**: * The provider dynamically selects and loads the appropriate MSAL version based on: * - Feature flags (MSAL 4.22.0 vs 4.7.0 vs cached from SPO) * - First-party vs third-party application ID * - Environment (Teams, Outlook, SharePoint, etc.) * * **Singleton Behavior**: * For first-party applications, this provider maintains singleton instances per service principal ID * to ensure consistent authentication state across the application. Third-party applications get * separate instances. * * **Events**: * - {@link DeferredAadTokenProvider.popupEvent} - Raised before browser popup authentication (only if popup enabled, NAA not active) * - {@link DeferredAadTokenProvider.onBeforeRedirectEvent} - Raised before full-page redirect (NAA not active) * - {@link DeferredAadTokenProvider.tokenAcquisitionEvent} - Legacy event from ADAL.js (rarely used) * * @public * @sealed */ export declare class DeferredAadTokenProvider implements IAadTokenProvider { private readonly _oboFirstPartyTokenCallback?; private readonly _oboThirdPartyTokenCallback?; /** * Static single instance per service principal of the MSAL Browser token providers (4220) */ private static _firstPartyMsalBrowserTokenProviderV4220ByAppId; private static _firstPartyMsalBrowserConfigV4220ByAppId; /** * Static single instance per service principal of the MSAL Browser from SPO token providers */ private static _firstPartyMsalCachedTokenProviderByAppId; private static _firstPartyMsalCachedConfigByAppId; /** * Static promise for loading MSAL library from external source - ensures it's only loaded once */ private static _msalLibraryLoadingPromise; /** * Event raised before performing a full-page redirect for authentication. * * **⚠️ STRONGLY DISCOURAGED**: Using this event is strongly discouraged due to race condition issues. * When multiple callers use the same provider instance (recommended pattern), event handlers from * one token fetch may inadvertently respond to token fetches from different callers, causing * unpredictable behavior. This event is maintained only for backward compatibility with existing * 3rd party code. * * **When raised**: Before initiating a full-page redirect to Azure AD for authentication when: * - Silent token acquisition fails and interaction is required * - Popup authentication is disabled via tenant configuration (default behavior) * - Popup authentication is unavailable (iframe, browser blocks) or fails * - {@link _ensureState} determines a redirect is needed to establish authentication state * * **NAA Exception**: This event is **NOT raised** when running in Teams, Outlook, or mobile * webviews with NAA support. NAA uses the host application's native broker and does not * perform browser redirects. * * **Use cases**: * - Cancel the automatic redirect and handle authentication manually * - Show custom UI or save application state before navigation * - Implement custom redirect logic or error handling * - Prevent redirect in specific scenarios (e.g., iframe contexts) * * @example * ```typescript * aadTokenProvider.onBeforeRedirectEvent.add(observer, (args: BeforeRedirectEventArgs) => { * // Save application state before redirect * saveCurrentState(); * * // Or cancel and handle manually * args.cancel(); * window.location.href = args.redirectUrl; * }); * ``` * * @eventproperty */ readonly onBeforeRedirectEvent: SPEvent; /** * Event raised when attempting popup-based authentication. * * **⚠️ STRONGLY DISCOURAGED**: Using this event is strongly discouraged due to race condition issues. * When multiple callers use the same provider instance (recommended pattern), event handlers from * one token fetch may inadvertently respond to token fetches from different callers, causing * unpredictable behavior. This event is maintained only for backward compatibility with existing * 3rd party code. * * **When Popup Authentication is Enabled**: * This event is raised when popup authentication is enabled, which can be controlled by: * - **Tenant configuration**: `Set-SPOTenant -IsEnableAppAuthPopupEnabled $true` (sp-client areas) * - **Caller/app context**: Application code setting popup preference based on codepath (odsp-next areas) * Without popup enabled (default), authentication skips popup and goes directly to redirect. * * **NAA Exception**: This event is **NOT raised** when running in Teams, Outlook, or mobile * webviews with NAA support. NAA uses the host application's native broker and bypasses the * event system entirely. * * **When raised**: Before opening an authentication popup when: * - Popup is enabled (via tenant configuration or app context, see above) * - Silent token acquisition fails with interaction_required error * - The token provider needs user consent or authentication * * **Use cases**: * - Show custom loading UI or messages while popup is opening * - Prevent popups in specific contexts (e.g., mobile devices, iframe environments) * - Coordinate with application-level popup blockers or modal managers * - Implement custom popup timing or debouncing logic * * @example * ```typescript * aadTokenProvider.popupEvent.add(observer, (args: PopupEventArgs) => { * // Show custom loading UI * showLoadingIndicator(); * * // Signal intent to show popup * args.requestPopup(); * * // Launch popup after UI updates * setTimeout(() => { * hideLoadingIndicator(); * args.showPopup(); * }, 100); * }); * ``` * * @eventproperty */ readonly popupEvent: SPEvent; /** * Manager for loading AuthenticationContext instances * @internal */ protected _authContextManager: Promise | undefined; protected _msalAuthLibrary: Promise | undefined; protected _msalAuthLibraryV4220: Promise | undefined; protected _msalAuthLibraryCached: Promise | undefined; private _firstPartyOboTokenProvider; private _thirdPartyOboTokenProvider; private _firstPartyMsalBrowserTokenProvider; private _thirdPartyMsalBrowserTokenProvider; private _firstPartyMsalBrowserTokenProviderV4220; private _thirdPartyMsalBrowserTokenProviderV4220; private _thirdPartyMsalBrowserTokenProviderCached; private _hasValidatedMsalBrowserTokenProviderV4220ByAppId; private _hasValidatedMsalCachedTokenProviderByAppId; private _tokenAcquisitionEvent; private _oboConfiguration; private readonly _defaultConfiguration; private _failedTokenRequests; /** * Compare the subset of configuration properties that impact MSAL Browser provider instantiation. * We intentionally only compare a safe subset rather than deep comparing the entire object to avoid * false negatives due to transient / per-instance values. Extend list if new properties become relevant. */ private static _areMsalBrowserRelevantConfigsEquivalent; /** * Ensure the provided configuration matches the configuration used for creating the singleton instance. * If it does not, we create a QoS monitor to log the mismatch (but continue using the existing singleton to avoid churn). */ private static _validateSingletonConfig; /** * @internal */ constructor(tokenAcquisitionEvent: SPEvent, beforeRedirectEvent: SPEvent, popupEvent: SPEvent, configuration: IAadTokenProviderConfiguration, oboConfiguration?: ISPOBOFlowParameters, _oboFirstPartyTokenCallback?: OBOTokenFunction | undefined, _oboThirdPartyTokenCallback?: _OBO3PTokenFunction | undefined); /** * Fetches an OAuth2 access token for the specified resource endpoint. * * **First Call Behavior**: * On the first call to this method, the MSAL authentication library is lazy-loaded, which may * add a small delay. Subsequent calls use the cached library instance for faster performance. * * **Authentication Flow**: * This method automatically handles the authentication flow based on tenant configuration and host environment: * 1. **Silent Token Acquisition** - Always attempts to get token silently using cached credentials or SSO * 2. **NAA (Nested App Authentication)** - If in Teams/Outlook and silent fails: * - Uses host application's native broker for seamless authentication * - **Does NOT raise {@link DeferredAadTokenProvider.popupEvent} or {@link DeferredAadTokenProvider.onBeforeRedirectEvent}** (bypasses event system) * - Shows native account picker UI (not browser popup) * - Cannot be cancelled or controlled via event handlers * 3. **Popup Authentication** (Optional, tenant-configurable) - If NAA unavailable, popup enabled, and silent fails: * - Raises {@link DeferredAadTokenProvider.popupEvent} event before opening popup * - Requires tenant admin to enable via: `Set-SPOTenant -IsEnableAppAuthPopupEnabled $true` * - If disabled (default), skips directly to redirect * 4. **Redirect Authentication** - If popup disabled/unavailable/cancelled, raises {@link DeferredAadTokenProvider.onBeforeRedirectEvent} event * * **Default Flow** (browser): Silent → Redirect * **With Popup Enabled** (browser): Silent → Popup → Redirect * **In Teams/Outlook** (NAA): Silent → Native Broker (no events) * * **Token Caching**: * Tokens are automatically cached by the provider. Do NOT cache tokens returned by this method - * the provider manages caching, expiration, and refresh automatically. * * **State Management**: * On first call, automatically calls {@link _ensureState} to establish authentication state with Azure AD. * This may trigger a redirect if the user has never authenticated. To control this timing, applications * can explicitly call {@link _ensureState} during initialization. * * **Event Handling**: * Subscribe to events to customize authentication UX: * - {@link DeferredAadTokenProvider.popupEvent} - Control popup timing, show loading UI, or prevent popups (only raised if popup enabled) * - {@link DeferredAadTokenProvider.onBeforeRedirectEvent} - Save application state before redirect or cancel automatic redirects * * **NAA (Nested App Authentication) Limitation**: * When running in Teams, Outlook, or mobile webviews with NAA support: * - **Events are NOT raised** - {@link DeferredAadTokenProvider.popupEvent} and {@link DeferredAadTokenProvider.onBeforeRedirectEvent} will not fire * - **Cannot cancel authentication** - No way to prevent native account picker from showing * - **No application control** - Host application manages authentication UI entirely * - **Seamless experience** - Users see native account picker instead of browser popup * - **Error handling only** - Can only catch errors when NAA fails (e.g., user cancels) * - To avoid authentication UI, ensure tokens are valid (silent auth succeeds) * * **Common Error Scenarios**: * - **User cancelled** - User closed popup or cancelled authentication * - **Access denied** - User or admin denied consent for the requested resource * - **Network errors** - Transient network failures (automatically retried once) * - **Interaction required** - Cached credentials expired, user must re-authenticate * * @param resourceEndpoint - The resource URL for which to obtain a token (e.g., `https://graph.microsoft.com`) * @param options - Optional configuration: `{ useCachedToken?, authenticationScheme?, claims? }` * - `useCachedToken`: If false, forces refresh even if valid cached token exists (default: true) * - `authenticationScheme`: Token type - BEARER (default) or POP (Proof of Possession) * - `claims`: CAP (Conditional Access Policy) claims for advanced scenarios * @returns Promise resolving to the access token string * * @example * ```typescript * // Basic token acquisition * const token = await aadTokenProvider.getToken('https://graph.microsoft.com'); * * // Use token with Microsoft Graph * const response = await fetch('https://graph.microsoft.com/v1.0/me', { * headers: { 'Authorization': `Bearer ${token}` } * }); * ``` * * @example * ```typescript * // Handle popup events to show custom UI * aadTokenProvider.popupEvent.add(observer, (args) => { * showLoadingSpinner('Authenticating...'); * args.requestPopup(); * setTimeout(() => args.showPopup(), 100); * }); * * const token = await aadTokenProvider.getToken('https://graph.microsoft.com'); * ``` * * @example * ```typescript * // Handle NAA (Teams/Outlook) - events won't fire, can only handle errors * try { * const token = await aadTokenProvider.getToken('https://graph.microsoft.com'); * // NAA authentication succeeded (or silent auth succeeded) * } catch (error) { * if (error.errorCode === 'user_cancelled') { * // User cancelled NAA native account picker * showMessage('Please sign in to continue'); * } * // Note: Cannot prevent NAA from showing - must handle failure * } * ``` * * @example * ```typescript * // Force token refresh * const freshToken = await aadTokenProvider.getToken('https://graph.microsoft.com', { * useCachedToken: false * }); * ``` */ getToken(resourceEndpoint: string, options?: IGetTokenOptions): Promise; getToken(resourceEndpoint: string, useCachedToken?: boolean): Promise; /** * Fetches complete token data including access token, expiration, and metadata. * * **Purpose**: * Returns the full token response object instead of just the access token string. * Use this when you need additional token metadata like expiration time, account info, * or token type. * * **When to Use**: * - Need token expiration time (`expiresOn`) to implement custom caching logic * - Need to verify token properties before using it * - Building token inspection or debugging tools * - Need account information associated with the token * * **Return Value** (`ITokenData`): * - `accessToken`: The token string to use in Authorization headers * - `expiresOn`: Token expiration timestamp (Date object) * - `account`: Account information (UPN, tenant, etc.) * - `fromCache`: Whether token was retrieved from cache * * **Authentication Flow**: * Same as {@link DeferredAadTokenProvider.getToken}: silent → NAA (if supported) → popup (if enabled) → redirect * * **Events**: * Raises the same events as {@link DeferredAadTokenProvider.getToken}: {@link DeferredAadTokenProvider.popupEvent} (if popup enabled, NAA not active) and {@link DeferredAadTokenProvider.onBeforeRedirectEvent} (NAA not active) * * **Library Loading**: * On first call, lazy-loads the MSAL authentication library. Subsequent calls use the cached library. * * @param resourceEndpoint - The resource URL for which to obtain a token * @param options - Optional configuration (same as {@link DeferredAadTokenProvider.getToken}) * @returns Promise resolving to complete token data object * * @example * ```typescript * const tokenData = await aadTokenProvider._getTokenData('https://graph.microsoft.com'); * console.log(`Token expires: ${tokenData.expiresOn}`); * console.log(`User: ${tokenData.account?.username}`); * * // Use the access token * const response = await fetch('https://graph.microsoft.com/v1.0/me', { * headers: { 'Authorization': `Bearer ${tokenData.accessToken}` } * }); * ``` * * @internal */ _getTokenData(resourceEndpoint: string, options?: IGetTokenDataOptions): Promise; _getTokenData(resourceEndpoint: string, useCachedToken?: boolean, skipLogging?: boolean): Promise; /** * Acquires a token using an alternate configuration (primarily for first-party token acquisition). * * **Primary Use Case - First-Party Token Acquisition**: * This method enables the first-party/third-party token switching pattern used throughout the codebase. * Most commonly used to acquire first-party tokens while using a third-party provider: * * ```typescript * // Get first-party token (app ID: 00000003-0000-0ff1-ce00-000000000000) * const token = await deferredProvider._getTokenInternal( * resource, * preAuthorizedConfiguration // First-party config * ); * ``` * * **When to Use**: * - **First-party token acquisition**: Get 1P tokens from a 3P provider instance (most common) * - **Internal SPFx operations**: Framework code acquiring tokens for internal services * - **Testing**: Unit tests with mock configurations * * **Why This Exists**: * - Enables acquiring tokens with different app IDs (1P ↔ 3P) from the same provider instance * - Maintains the provider's state, caching, and event handling while switching app contexts * - Allows applications to make both first-party and third-party API calls * * **Important Constraints**: * - ✅ Switch between app IDs (1P ↔ 3P) within the same tenant * - ❌ DO NOT use for different tenants (same tenant only) * - ❌ DO NOT use for different users/accounts (use separate provider instances) * - ❌ DO NOT change configuration per-request (not thread-safe, causes auth failures) * * **Authentication Flow**: * Same as {@link DeferredAadTokenProvider.getToken}: silent → NAA (if supported) → popup (if enabled) → redirect * * **Events**: * Raises the same events as {@link DeferredAadTokenProvider.getToken}: {@link DeferredAadTokenProvider.popupEvent} (if popup enabled, NAA not active) and {@link DeferredAadTokenProvider.onBeforeRedirectEvent} (NAA not active) * * **Library Loading**: * If this is the first call with a new configuration, the appropriate MSAL library will be * lazy-loaded based on the configuration's service principal ID and feature flags. * * @param resourceEndpoint - The resource URL for which to obtain a token * @param configuration - Alternate configuration (typically a first-party or pre-authorized configuration) * @param options - Optional configuration: `{ useCachedToken?, authenticationScheme?, claims? }` * @returns Promise resolving to the access token string * * @internal */ _getTokenInternal(resourceEndpoint: string, configuration: IAadTokenProviderConfiguration, options?: IGetTokenOptions): Promise; _getTokenInternal(resourceEndpoint: string, configuration: IAadTokenProviderConfiguration, useCachedToken?: boolean): Promise; /** * Establishes authentication state with Azure AD by attempting to acquire a token silently. * * **Purpose**: * Verifies that the user has authenticated with Azure AD and can acquire tokens. If the user * has never authenticated (cold start), this may trigger a full-page redirect to Azure AD. * This "warms up" the authentication system before tokens are actually needed. * * **When Called Automatically**: * The first call to {@link DeferredAadTokenProvider.getToken} automatically calls `_ensureState()` internally. Most applications * do NOT need to call this method explicitly - the automatic call is sufficient. * * **When to Call Explicitly**: * Call this method during application initialization in these scenarios: * - **Control redirect timing** - Redirect during splash screen instead of during critical user actions * - **Pre-warm authentication** - Establish auth state before user needs to access protected resources * - **Show loading UI** - Display custom UI while authentication state is being established * - **Defer authentication** - Call later to avoid redirect on initial page load * * **Library Loading**: * On first call, this method lazy-loads the MSAL authentication library before establishing state. * * **Redirect Behavior**: * If user not authenticated: * 1. Raises {@link DeferredAadTokenProvider.onBeforeRedirectEvent} event - subscribe to cancel, save state, or show UI * 2. Redirects to Azure AD login page (full-page navigation) * 3. After login, redirects back to application with auth tokens * * **Redirect Loop Prevention**: * To prevent infinite redirect loops, this method will NOT redirect if: * - A redirect occurred within the last 5 days (default) * - The redirect is cancelled via {@link DeferredAadTokenProvider.onBeforeRedirectEvent} * - Running in an iframe context (iframes cannot redirect) * * **Environment-Specific Behavior**: * - **Teams/Outlook/Mobile webviews**: Does not ensure refresh token * - **Browser**: Ensures refresh token to enable silent token renewal * * **Important Notes**: * - Does NOT raise {@link DeferredAadTokenProvider.popupEvent} - never attempts popup authentication * - Only redirects on cold start (user never authenticated) - subsequent calls succeed silently * - Calling multiple times is safe - redirect prevention ensures it won't loop * - Does NOT guarantee tokens for specific resources - only establishes baseline auth state * * @returns Promise that resolves when authentication state is confirmed or rejects if state * cannot be established (e.g., authentication required but redirect cancelled) * * @example * ```typescript * // Call during application initialization to control redirect timing * async function initializeApp() { * showSplashScreen(); * * try { * // Establish auth state - may redirect to Azure AD * await aadTokenProvider._ensureState(); * * // If we get here, user is authenticated * await loadApplicationData(); * } catch (error) { * // Authentication failed - show error or retry * showAuthenticationError(error); * } finally { * hideSplashScreen(); * } * } * ``` * * @example * ```typescript * // Save application state before redirect * aadTokenProvider.onBeforeRedirectEvent.add(observer, (args) => { * // Save any in-progress work * localStorage.setItem('unsavedWork', JSON.stringify(currentState)); * * // Allow redirect to proceed * }); * * await aadTokenProvider._ensureState(); * ``` * * @internal */ _ensureState(): Promise; /** * @internal */ _getToken(tokenProvider: ITokenProvider, resourceEndpoint: string, options: IGetTokenOptions): Promise; /** * Ensures proper monitoring is set up for token acquisition * * @internal */ _ensureTelemetry(): Promise; private _shouldEnsureRefreshToken; private _addFailedRequest; private _getTokenDataInternal; private _shouldTokenBeRequested; /** * Legacy event for token acquisition failures requiring user interaction. * * **⚠️ STRONGLY DISCOURAGED**: Using this event is strongly discouraged due to race condition issues. * When multiple callers use the same provider instance (recommended pattern), event handlers from * one token fetch may inadvertently respond to token fetches from different callers, causing * unpredictable behavior. This event is maintained only for backward compatibility with existing * 3rd party code. * * **When raised**: This event was originally used with ADAL.js for token acquisition failures. * In the current MSAL-based implementation, most interaction scenarios now use {@link DeferredAadTokenProvider.popupEvent} * and {@link DeferredAadTokenProvider.onBeforeRedirectEvent} instead. * * **Note**: This event may be deprecated in future versions. For new code, prefer subscribing to * {@link DeferredAadTokenProvider.popupEvent} and {@link DeferredAadTokenProvider.onBeforeRedirectEvent} which provide more control over the authentication flow. * * @eventproperty */ get tokenAcquisitionEvent(): SPEvent; private _shouldUseNestedAppAuth; /** * Given a token provider configuration, this method returns true if MsalBrowserTokenV3Provider should be * used. MsalBrowserTokenV3Provider should be used when: * - Either A first party client ID is requesting a token or third party client ID for which reply uri has been updated. */ private _shouldUseMsalBrowserTokenProvider; private _getAdalAuthContextManager; private _getMsalBrowserCurrentTokenProvider; private _getMsalBrowser470TokenProvider; private _getMsalBrowser4220TokenProvider; private _getMsalBrowserCachedTokenProvider; private _loadMsalLibrary; private _loadMsalLibraryImpl; private _getMsalBrowserTokenProvider; private _getOboTokenProvider; private _isFirstParty; private _isInIsolatedDomain; /** * Check for determining if the OBO Token Exchange API should be used in the current environment. */ private _shouldUseOboTokenExchange; } //# sourceMappingURL=DeferredAadTokenProvider.d.ts.map