UNPKG

@azure/msal-browser

Version:
156 lines (153 loc) 10.4 kB
/*! @azure/msal-browser v4.12.0 2025-05-06 */ 'use strict'; import { PerformanceEvents, PromptValue, invokeAsync, ProtocolMode, AuthError, invoke } from '@azure/msal-common/browser'; import { StandardInteractionClient } from './StandardInteractionClient.mjs'; import { createBrowserAuthError } from '../error/BrowserAuthError.mjs'; import { InteractionType, BrowserConstants } from '../utils/BrowserConstants.mjs'; import { initiateEarRequest, monitorIframeForHash, initiateCodeRequest } from '../interaction_handler/SilentHandler.mjs'; import { NativeMessageHandler } from '../broker/nativeBroker/NativeMessageHandler.mjs'; import { preconnect } from '../utils/BrowserUtils.mjs'; import { deserializeResponse } from '../response/ResponseHandler.mjs'; import { handleResponseEAR, getAuthCodeRequestUrl, handleResponseCode } from '../protocol/Authorize.mjs'; import { generatePkceCodes } from '../crypto/PkceGenerator.mjs'; import { generateEarKey } from '../crypto/BrowserCrypto.mjs'; import { silentLogoutUnsupported } from '../error/BrowserAuthErrorCodes.mjs'; /* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ class SilentIframeClient extends StandardInteractionClient { constructor(config, storageImpl, browserCrypto, logger, eventHandler, navigationClient, apiId, performanceClient, nativeStorageImpl, nativeMessageHandler, correlationId) { super(config, storageImpl, browserCrypto, logger, eventHandler, navigationClient, performanceClient, nativeMessageHandler, correlationId); this.apiId = apiId; this.nativeStorage = nativeStorageImpl; } /** * Acquires a token silently by opening a hidden iframe to the /authorize endpoint with prompt=none or prompt=no_session * @param request */ async acquireToken(request) { this.performanceClient.addQueueMeasurement(PerformanceEvents.SilentIframeClientAcquireToken, request.correlationId); // Check that we have some SSO data if (!request.loginHint && !request.sid && (!request.account || !request.account.username)) { this.logger.warning("No user hint provided. The authorization server may need more information to complete this request."); } // Check the prompt value const inputRequest = { ...request }; if (inputRequest.prompt) { if (inputRequest.prompt !== PromptValue.NONE && inputRequest.prompt !== PromptValue.NO_SESSION) { this.logger.warning(`SilentIframeClient. Replacing invalid prompt ${inputRequest.prompt} with ${PromptValue.NONE}`); inputRequest.prompt = PromptValue.NONE; } } else { inputRequest.prompt = PromptValue.NONE; } // Create silent request const silentRequest = await invokeAsync(this.initializeAuthorizationRequest.bind(this), PerformanceEvents.StandardInteractionClientInitializeAuthorizationRequest, this.logger, this.performanceClient, request.correlationId)(inputRequest, InteractionType.Silent); silentRequest.platformBroker = NativeMessageHandler.isPlatformBrokerAvailable(this.config, this.logger, this.nativeMessageHandler, silentRequest.authenticationScheme); preconnect(silentRequest.authority); if (this.config.auth.protocolMode === ProtocolMode.EAR) { return this.executeEarFlow(silentRequest); } else { return this.executeCodeFlow(silentRequest); } } /** * Executes auth code + PKCE flow * @param request * @returns */ async executeCodeFlow(request) { let authClient; const serverTelemetryManager = this.initializeServerTelemetryManager(this.apiId); try { // Initialize the client authClient = await invokeAsync(this.createAuthCodeClient.bind(this), PerformanceEvents.StandardInteractionClientCreateAuthCodeClient, this.logger, this.performanceClient, request.correlationId)({ serverTelemetryManager, requestAuthority: request.authority, requestAzureCloudOptions: request.azureCloudOptions, requestExtraQueryParameters: request.extraQueryParameters, account: request.account, }); return await invokeAsync(this.silentTokenHelper.bind(this), PerformanceEvents.SilentIframeClientTokenHelper, this.logger, this.performanceClient, request.correlationId)(authClient, request); } catch (e) { if (e instanceof AuthError) { e.setCorrelationId(this.correlationId); serverTelemetryManager.cacheFailedRequest(e); } if (!authClient || !(e instanceof AuthError) || e.errorCode !== BrowserConstants.INVALID_GRANT_ERROR) { throw e; } this.performanceClient.addFields({ retryError: e.errorCode, }, this.correlationId); return await invokeAsync(this.silentTokenHelper.bind(this), PerformanceEvents.SilentIframeClientTokenHelper, this.logger, this.performanceClient, this.correlationId)(authClient, request); } } /** * Executes EAR flow * @param request */ async executeEarFlow(request) { const correlationId = request.correlationId; const discoveredAuthority = await invokeAsync(this.getDiscoveredAuthority.bind(this), PerformanceEvents.StandardInteractionClientGetDiscoveredAuthority, this.logger, this.performanceClient, correlationId)({ requestAuthority: request.authority, requestAzureCloudOptions: request.azureCloudOptions, requestExtraQueryParameters: request.extraQueryParameters, account: request.account, }); const earJwk = await invokeAsync(generateEarKey, PerformanceEvents.GenerateEarKey, this.logger, this.performanceClient, correlationId)(); const silentRequest = { ...request, earJwk: earJwk, }; const msalFrame = await invokeAsync(initiateEarRequest, PerformanceEvents.SilentHandlerInitiateAuthRequest, this.logger, this.performanceClient, correlationId)(this.config, discoveredAuthority, silentRequest, this.logger, this.performanceClient); const responseType = this.config.auth.OIDCOptions.serverResponseType; // Monitor the window for the hash. Return the string value and close the popup when the hash is received. Default timeout is 60 seconds. const responseString = await invokeAsync(monitorIframeForHash, PerformanceEvents.SilentHandlerMonitorIframeForHash, this.logger, this.performanceClient, correlationId)(msalFrame, this.config.system.iframeHashTimeout, this.config.system.pollIntervalMilliseconds, this.performanceClient, this.logger, correlationId, responseType); const serverParams = invoke(deserializeResponse, PerformanceEvents.DeserializeResponse, this.logger, this.performanceClient, correlationId)(responseString, responseType, this.logger); return invokeAsync(handleResponseEAR, PerformanceEvents.HandleResponseEar, this.logger, this.performanceClient, correlationId)(silentRequest, serverParams, this.apiId, this.config, discoveredAuthority, this.browserStorage, this.nativeStorage, this.eventHandler, this.logger, this.performanceClient, this.nativeMessageHandler); } /** * Currently Unsupported */ logout() { // Synchronous so we must reject return Promise.reject(createBrowserAuthError(silentLogoutUnsupported)); } /** * Helper which acquires an authorization code silently using a hidden iframe from given url * using the scopes requested as part of the id, and exchanges the code for a set of OAuth tokens. * @param navigateUrl * @param userRequestScopes */ async silentTokenHelper(authClient, request) { const correlationId = request.correlationId; this.performanceClient.addQueueMeasurement(PerformanceEvents.SilentIframeClientTokenHelper, correlationId); const pkceCodes = await invokeAsync(generatePkceCodes, PerformanceEvents.GeneratePkceCodes, this.logger, this.performanceClient, correlationId)(this.performanceClient, this.logger, correlationId); const silentRequest = { ...request, codeChallenge: pkceCodes.challenge, }; // Create authorize request url const navigateUrl = await invokeAsync(getAuthCodeRequestUrl, PerformanceEvents.GetAuthCodeUrl, this.logger, this.performanceClient, correlationId)(this.config, authClient.authority, silentRequest, this.logger, this.performanceClient); // Get the frame handle for the silent request const msalFrame = await invokeAsync(initiateCodeRequest, PerformanceEvents.SilentHandlerInitiateAuthRequest, this.logger, this.performanceClient, correlationId)(navigateUrl, this.performanceClient, this.logger, correlationId, this.config.system.navigateFrameWait); const responseType = this.config.auth.OIDCOptions.serverResponseType; // Monitor the window for the hash. Return the string value and close the popup when the hash is received. Default timeout is 60 seconds. const responseString = await invokeAsync(monitorIframeForHash, PerformanceEvents.SilentHandlerMonitorIframeForHash, this.logger, this.performanceClient, correlationId)(msalFrame, this.config.system.iframeHashTimeout, this.config.system.pollIntervalMilliseconds, this.performanceClient, this.logger, correlationId, responseType); const serverParams = invoke(deserializeResponse, PerformanceEvents.DeserializeResponse, this.logger, this.performanceClient, correlationId)(responseString, responseType, this.logger); return invokeAsync(handleResponseCode, PerformanceEvents.HandleResponseCode, this.logger, this.performanceClient, correlationId)(request, serverParams, pkceCodes.verifier, this.apiId, this.config, authClient, this.browserStorage, this.nativeStorage, this.eventHandler, this.logger, this.performanceClient, this.nativeMessageHandler); } } export { SilentIframeClient }; //# sourceMappingURL=SilentIframeClient.mjs.map