UNPKG

@azure/msal-browser

Version:
225 lines (222 loc) 15.1 kB
/*! @azure/msal-browser v5.7.0 2026-04-16 */ 'use strict'; import { Constants, invokeAsync, ProtocolMode, AuthError, invoke, AuthorizeProtocol, PerformanceEvents } from '@azure/msal-common/browser'; import { StandardInteractionClient, initializeAuthorizationRequest } from './StandardInteractionClient.mjs'; import { StandardInteractionClientInitializeAuthorizationRequest, StandardInteractionClientCreateAuthCodeClient, SilentIframeClientTokenHelper, StandardInteractionClientGetDiscoveredAuthority, GenerateEarKey, GeneratePkceCodes, SilentHandlerInitiateAuthRequest, SilentHandlerMonitorIframeForHash, RemoveHiddenIframe, DeserializeResponse, HandleResponseCode, HandleResponseEar } from '../telemetry/BrowserPerformanceEvents.mjs'; import { createBrowserAuthError } from '../error/BrowserAuthError.mjs'; import { InteractionType, BrowserConstants } from '../utils/BrowserConstants.mjs'; import { initiateEarRequest, removeHiddenIframe, initiateCodeFlowWithPost, initiateCodeRequest } from '../interaction_handler/SilentHandler.mjs'; import { preconnect, waitForBridgeResponse } from '../utils/BrowserUtils.mjs'; import { deserializeResponse } from '../response/ResponseHandler.mjs'; import { handleResponseCode, handleResponseEAR, getAuthCodeRequestUrl } from '../protocol/Authorize.mjs'; import { generatePkceCodes } from '../crypto/PkceGenerator.mjs'; import { isPlatformAuthAllowed } from '../broker/nativeBroker/PlatformAuthProvider.mjs'; import { generateEarKey } from '../crypto/BrowserCrypto.mjs'; import { initializeServerTelemetryManager, getDiscoveredAuthority } from './BaseInteractionClient.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, correlationId, platformAuthProvider) { super(config, storageImpl, browserCrypto, logger, eventHandler, navigationClient, performanceClient, correlationId, platformAuthProvider); 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) { // Check that we have some SSO data if (!request.loginHint && !request.sid && (!request.account || !request.account.username)) { this.logger.warning("1kl318", this.correlationId); } // Check the prompt value const inputRequest = { ...request }; if (inputRequest.prompt) { if (inputRequest.prompt !== Constants.PromptValue.NONE && inputRequest.prompt !== Constants.PromptValue.NO_SESSION) { this.logger.warning("0bmctg", this.correlationId); inputRequest.prompt = Constants.PromptValue.NONE; } } else { inputRequest.prompt = Constants.PromptValue.NONE; } // Create silent request const silentRequest = await invokeAsync(initializeAuthorizationRequest, StandardInteractionClientInitializeAuthorizationRequest, this.logger, this.performanceClient, this.correlationId)(inputRequest, InteractionType.Silent, this.config, this.browserCrypto, this.browserStorage, this.logger, this.performanceClient, this.correlationId); silentRequest.platformBroker = isPlatformAuthAllowed(this.config, this.logger, this.correlationId, this.platformAuthProvider, silentRequest.authenticationScheme); preconnect(silentRequest.authority); if (this.config.system.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 = initializeServerTelemetryManager(this.apiId, this.config.auth.clientId, this.correlationId, this.browserStorage, this.logger); try { // Initialize the client authClient = await invokeAsync(this.createAuthCodeClient.bind(this), 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), 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), SilentIframeClientTokenHelper, this.logger, this.performanceClient, this.correlationId)(authClient, request); } } /** * Executes EAR flow * @param request */ async executeEarFlow(request) { const { correlationId, authority, azureCloudOptions, extraQueryParameters, account, } = request; const discoveredAuthority = await invokeAsync(getDiscoveredAuthority, StandardInteractionClientGetDiscoveredAuthority, this.logger, this.performanceClient, correlationId)(this.config, this.correlationId, this.performanceClient, this.browserStorage, this.logger, authority, azureCloudOptions, extraQueryParameters, account); const earJwk = await invokeAsync(generateEarKey, GenerateEarKey, this.logger, this.performanceClient, correlationId)(); const pkceCodes = await invokeAsync(generatePkceCodes, GeneratePkceCodes, this.logger, this.performanceClient, correlationId)(this.performanceClient, this.logger, correlationId); const silentRequest = { ...request, earJwk: earJwk, codeChallenge: pkceCodes.challenge, }; const iframe = await invokeAsync(initiateEarRequest, SilentHandlerInitiateAuthRequest, this.logger, this.performanceClient, correlationId)(this.config, discoveredAuthority, silentRequest, this.logger, this.performanceClient); const responseType = this.config.auth.OIDCOptions.responseMode; let responseString; try { responseString = await invokeAsync(waitForBridgeResponse, SilentHandlerMonitorIframeForHash, this.logger, this.performanceClient, correlationId)(this.config.system.iframeBridgeTimeout, this.logger, this.browserCrypto, request, this.performanceClient, this.config.experimental); } finally { invoke(removeHiddenIframe, RemoveHiddenIframe, this.logger, this.performanceClient, correlationId)(iframe); } const serverParams = invoke(deserializeResponse, DeserializeResponse, this.logger, this.performanceClient, correlationId)(responseString, responseType, this.logger, this.correlationId); if (!serverParams.ear_jwe && serverParams.code) { // If server doesn't support EAR, they may fallback to auth code flow instead const authClient = await invokeAsync(this.createAuthCodeClient.bind(this), StandardInteractionClientCreateAuthCodeClient, this.logger, this.performanceClient, correlationId)({ serverTelemetryManager: initializeServerTelemetryManager(this.apiId, this.config.auth.clientId, correlationId, this.browserStorage, this.logger), requestAuthority: request.authority, requestAzureCloudOptions: request.azureCloudOptions, requestExtraQueryParameters: request.extraQueryParameters, account: request.account, authority: discoveredAuthority, }); return invokeAsync(handleResponseCode, HandleResponseCode, this.logger, this.performanceClient, correlationId)(silentRequest, serverParams, pkceCodes.verifier, this.apiId, this.config, authClient, this.browserStorage, this.nativeStorage, this.eventHandler, this.logger, this.performanceClient, this.platformAuthProvider); } else { return invokeAsync(handleResponseEAR, HandleResponseEar, this.logger, this.performanceClient, correlationId)(silentRequest, serverParams, this.apiId, this.config, discoveredAuthority, this.browserStorage, this.nativeStorage, this.eventHandler, this.logger, this.performanceClient, this.platformAuthProvider); } } /** * Verifies SSO capability by making an iframe request to /authorize without exchanging the code for tokens. * This is useful for verifying SSO capability in the background without the overhead of a full token exchange. * @param request - The SSO silent request * @returns true if SSO verification was successful with a valid authorization code, false otherwise */ async verifySso(request) { const inputRequest = { ...request }; if (!inputRequest.prompt) { inputRequest.prompt = Constants.PromptValue.NONE; } // Create silent request const silentRequest = await invokeAsync(initializeAuthorizationRequest, StandardInteractionClientInitializeAuthorizationRequest, this.logger, this.performanceClient, this.correlationId)(inputRequest, InteractionType.Silent, this.config, this.browserCrypto, this.browserStorage, this.logger, this.performanceClient, this.correlationId); const authClient = await invokeAsync(this.createAuthCodeClient.bind(this), StandardInteractionClientCreateAuthCodeClient, this.logger, this.performanceClient, this.correlationId)({ serverTelemetryManager: initializeServerTelemetryManager(this.apiId, this.config.auth.clientId, this.correlationId, this.browserStorage, this.logger), requestAuthority: silentRequest.authority, requestAzureCloudOptions: silentRequest.azureCloudOptions, requestExtraQueryParameters: silentRequest.extraQueryParameters, account: silentRequest.account, }); const { serverParams } = await this.silentAuthorizeHelper(authClient, silentRequest); const correlationId = silentRequest.correlationId; // Validate the response - this checks for errors and validates state AuthorizeProtocol.validateAuthorizationResponse(serverParams, silentRequest.state); // Verify a valid authorization code is present if (!serverParams.code) { this.logger.warning("0y34ti", correlationId); return false; } this.logger.verbose("0kkkcj", correlationId); return true; } /** * 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 { serverParams, pkceCodes } = await this.silentAuthorizeHelper(authClient, request); return invokeAsync(handleResponseCode, HandleResponseCode, this.logger, this.performanceClient, request.correlationId)(request, serverParams, pkceCodes.verifier, this.apiId, this.config, authClient, this.browserStorage, this.nativeStorage, this.eventHandler, this.logger, this.performanceClient, this.platformAuthProvider); } /** * Shared helper that generates PKCE codes, builds the /authorize URL, * loads it in a hidden iframe, waits for the redirect-bridge response, * and returns the deserialized server parameters along with the PKCE codes * and the request that was sent. */ async silentAuthorizeHelper(authClient, request) { const correlationId = request.correlationId; const pkceCodes = await invokeAsync(generatePkceCodes, GeneratePkceCodes, this.logger, this.performanceClient, correlationId)(this.performanceClient, this.logger, correlationId); const silentRequest = { ...request, codeChallenge: pkceCodes.challenge, }; let iframe; if (request.httpMethod === Constants.HttpMethod.POST) { iframe = await invokeAsync(initiateCodeFlowWithPost, SilentHandlerInitiateAuthRequest, this.logger, this.performanceClient, correlationId)(this.config, authClient.authority, silentRequest, this.logger, this.performanceClient); } else { // 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 iframe = await invokeAsync(initiateCodeRequest, SilentHandlerInitiateAuthRequest, this.logger, this.performanceClient, correlationId)(navigateUrl, this.performanceClient, this.logger, correlationId); } const responseType = this.config.auth.OIDCOptions.responseMode; // Wait for response from the redirect bridge. let responseString; try { responseString = await invokeAsync(waitForBridgeResponse, SilentHandlerMonitorIframeForHash, this.logger, this.performanceClient, correlationId)(this.config.system.iframeBridgeTimeout, this.logger, this.browserCrypto, request, this.performanceClient, this.config.experimental); } finally { invoke(removeHiddenIframe, RemoveHiddenIframe, this.logger, this.performanceClient, correlationId)(iframe); } const serverParams = invoke(deserializeResponse, DeserializeResponse, this.logger, this.performanceClient, correlationId)(responseString, responseType, this.logger, this.correlationId); return { serverParams, pkceCodes, silentRequest }; } } export { SilentIframeClient }; //# sourceMappingURL=SilentIframeClient.mjs.map