UNPKG

@azure/msal-browser

Version:
397 lines (376 loc) 14.6 kB
/* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ import { ServerTelemetryManager, AuthorizationCodeClient, ClientConfiguration, UrlString, CommonEndSessionRequest, ProtocolUtils, IdTokenClaims, AccountInfo, AzureCloudOptions, invokeAsync, BaseAuthRequest, StringDict, CommonAuthorizationUrlRequest, ICrypto, Logger, IPerformanceClient, Authority, } from "@azure/msal-common/browser"; import { BaseInteractionClient, getDiscoveredAuthority, getRedirectUri, } from "./BaseInteractionClient.js"; import * as BrowserPerformanceEvents from "../telemetry/BrowserPerformanceEvents.js"; import { BrowserConstants, InteractionType, } from "../utils/BrowserConstants.js"; import { version } from "../packageMetadata.js"; import { BrowserStateObject } from "../utils/BrowserProtocolUtils.js"; import { EndSessionRequest } from "../request/EndSessionRequest.js"; import * as BrowserUtils from "../utils/BrowserUtils.js"; import { RedirectRequest } from "../request/RedirectRequest.js"; import { PopupRequest } from "../request/PopupRequest.js"; import { SsoSilentRequest } from "../request/SsoSilentRequest.js"; import { createNewGuid } from "../crypto/BrowserCrypto.js"; import { BrowserConfiguration } from "../config/Configuration.js"; import { BrowserCacheManager } from "../cache/BrowserCacheManager.js"; import { initializeBaseRequest, validateRequestMethod, } from "../request/RequestHelpers.js"; /** * Defines the class structure and helper functions used by the "standard", non-brokered auth flows (popup, redirect, silent (RT), silent (iframe)) */ export abstract class StandardInteractionClient extends BaseInteractionClient { /** * Initializer for the logout request. * @param logoutRequest */ protected initializeLogoutRequest( logoutRequest?: EndSessionRequest ): CommonEndSessionRequest { this.logger.verbose( "initializeLogoutRequest called", this.correlationId ); const validLogoutRequest: CommonEndSessionRequest = { correlationId: this.correlationId, ...logoutRequest, }; /** * Set logout_hint to be login_hint from ID Token Claims if present * and logoutHint attribute wasn't manually set in logout request */ if (logoutRequest) { // If logoutHint isn't set and an account was passed in, try to extract logoutHint from ID Token Claims if (!logoutRequest.logoutHint) { if (logoutRequest.account) { const logoutHint = this.getLogoutHintFromIdTokenClaims( logoutRequest.account ); if (logoutHint) { this.logger.verbose( "Setting logoutHint to login_hint ID Token Claim value for the account provided", this.correlationId ); validLogoutRequest.logoutHint = logoutHint; } } else { this.logger.verbose( "logoutHint was not set and account was not passed into logout request, logoutHint will not be set", this.correlationId ); } } else { this.logger.verbose( "logoutHint has already been set in logoutRequest", this.correlationId ); } } else { this.logger.verbose( "logoutHint will not be set since no logout request was configured", this.correlationId ); } /* * Only set redirect uri if logout request isn't provided or the set uri isn't null. * Otherwise, use passed uri, config, or current page. */ if (!logoutRequest || logoutRequest.postLogoutRedirectUri !== null) { if (logoutRequest && logoutRequest.postLogoutRedirectUri) { this.logger.verbose( "Setting postLogoutRedirectUri to uri set on logout request", validLogoutRequest.correlationId ); validLogoutRequest.postLogoutRedirectUri = UrlString.getAbsoluteUrl( logoutRequest.postLogoutRedirectUri, BrowserUtils.getCurrentUri() ); } else if (this.config.auth.postLogoutRedirectUri === null) { this.logger.verbose( "postLogoutRedirectUri configured as null and no uri set on request, not passing post logout redirect", validLogoutRequest.correlationId ); } else if (this.config.auth.postLogoutRedirectUri) { this.logger.verbose( "Setting postLogoutRedirectUri to configured uri", validLogoutRequest.correlationId ); validLogoutRequest.postLogoutRedirectUri = UrlString.getAbsoluteUrl( this.config.auth.postLogoutRedirectUri, BrowserUtils.getCurrentUri() ); } else { this.logger.verbose( "Setting postLogoutRedirectUri to current page", validLogoutRequest.correlationId ); validLogoutRequest.postLogoutRedirectUri = UrlString.getAbsoluteUrl( BrowserUtils.getCurrentUri(), BrowserUtils.getCurrentUri() ); } } else { this.logger.verbose( "postLogoutRedirectUri passed as null, not setting post logout redirect uri", validLogoutRequest.correlationId ); } return validLogoutRequest; } /** * Parses login_hint ID Token Claim out of AccountInfo object to be used as * logout_hint in end session request. * @param account */ protected getLogoutHintFromIdTokenClaims( account: AccountInfo ): string | null { const idTokenClaims: IdTokenClaims | undefined = account.idTokenClaims; if (idTokenClaims) { if (idTokenClaims.login_hint) { return idTokenClaims.login_hint; } else { this.logger.verbose( "The ID Token Claims tied to the provided account do not contain a login_hint claim, logoutHint will not be added to logout request", this.correlationId ); } } else { this.logger.verbose( "The provided account does not contain ID Token Claims, logoutHint will not be added to logout request", this.correlationId ); } return null; } /** * Creates an Authorization Code Client with the given authority, or the default authority. * @param params { * serverTelemetryManager: ServerTelemetryManager; * authorityUrl?: string; * requestAzureCloudOptions?: AzureCloudOptions; * requestExtraQueryParameters?: StringDict; * account?: AccountInfo; * } */ protected async createAuthCodeClient(params: { serverTelemetryManager: ServerTelemetryManager; requestAuthority?: string; requestAzureCloudOptions?: AzureCloudOptions; requestExtraQueryParameters?: StringDict; account?: AccountInfo; authority?: Authority; }): Promise<AuthorizationCodeClient> { // Create auth module. const clientConfig = await invokeAsync( this.getClientConfiguration.bind(this), BrowserPerformanceEvents.StandardInteractionClientGetClientConfiguration, this.logger, this.performanceClient, this.correlationId )(params); return new AuthorizationCodeClient( clientConfig, this.performanceClient ); } /** * Creates a Client Configuration object with the given request authority, or the default authority. * @param params { * serverTelemetryManager: ServerTelemetryManager; * requestAuthority?: string; * requestAzureCloudOptions?: AzureCloudOptions; * requestExtraQueryParameters?: boolean; * account?: AccountInfo; * } */ protected async getClientConfiguration(params: { serverTelemetryManager: ServerTelemetryManager; requestAuthority?: string; requestAzureCloudOptions?: AzureCloudOptions; requestExtraQueryParameters?: StringDict; account?: AccountInfo; authority?: Authority; }): Promise<ClientConfiguration> { const { serverTelemetryManager, requestAuthority, requestAzureCloudOptions, requestExtraQueryParameters, account, } = params; const discoveredAuthority = params.authority || (await invokeAsync( getDiscoveredAuthority, BrowserPerformanceEvents.StandardInteractionClientGetDiscoveredAuthority, this.logger, this.performanceClient, this.correlationId )( this.config, this.correlationId, this.performanceClient, this.browserStorage, this.logger, requestAuthority, requestAzureCloudOptions, requestExtraQueryParameters, account )); const logger = this.config.system.loggerOptions; return { authOptions: { clientId: this.config.auth.clientId, authority: discoveredAuthority, clientCapabilities: this.config.auth.clientCapabilities, redirectUri: this.config.auth.redirectUri, isMcp: this.config.auth.isMcp, }, systemOptions: { tokenRenewalOffsetSeconds: this.config.system.tokenRenewalOffsetSeconds, preventCorsPreflight: true, }, loggerOptions: { loggerCallback: logger.loggerCallback, piiLoggingEnabled: logger.piiLoggingEnabled, logLevel: logger.logLevel, correlationId: this.correlationId, }, cryptoInterface: this.browserCrypto, networkInterface: this.networkClient, storageInterface: this.browserStorage, serverTelemetryManager: serverTelemetryManager, libraryInfo: { sku: BrowserConstants.MSAL_SKU, version: version, cpu: "", os: "", }, telemetry: this.config.telemetry, }; } } /** * Helper to initialize required request parameters for interactive APIs and ssoSilent(). * * @param request - The authentication request object (RedirectRequest, PopupRequest, or SsoSilentRequest). * @param interactionType - The type of interaction (e.g., redirect, popup, silent). * @param config - The browser configuration object. * @param browserCrypto - The cryptographic interface for browser operations. * @param browserStorage - The browser storage manager instance. * @param logger - The logger instance for logging messages. * @param performanceClient - The performance client for telemetry. * @param correlationId - The correlation ID for the request. * @returns A promise that resolves to a CommonAuthorizationUrlRequest object with initialized parameters. */ export async function initializeAuthorizationRequest( request: RedirectRequest | PopupRequest | SsoSilentRequest, interactionType: InteractionType, config: BrowserConfiguration, browserCrypto: ICrypto, browserStorage: BrowserCacheManager, logger: Logger, performanceClient: IPerformanceClient, correlationId: string ): Promise<CommonAuthorizationUrlRequest> { const redirectUri = getRedirectUri( request.redirectUri, config.auth.redirectUri, logger, correlationId ); if (new URL(redirectUri).origin !== new URL(window.location.href).origin) { logger.warning( "The origin of the redirect URI does not match the origin of the current page. This is likely to cause issues with authentication.", correlationId ); performanceClient.addFields( { isRedirectUriCrossOrigin: true }, correlationId ); } const browserState: BrowserStateObject = { interactionType: interactionType, }; const state = ProtocolUtils.setRequestState( browserCrypto, (request && request.state) || "", browserState ); const baseRequest: BaseAuthRequest = await invokeAsync( initializeBaseRequest, BrowserPerformanceEvents.InitializeBaseRequest, logger, performanceClient, correlationId )( { ...request, correlationId: correlationId }, config, performanceClient, logger, correlationId ); const interactionRequest: CommonAuthorizationUrlRequest = { ...baseRequest, redirectUri: redirectUri, state: state, nonce: request.nonce || createNewGuid(), responseMode: config.auth.OIDCOptions.responseMode, }; const validatedRequest = { ...interactionRequest, httpMethod: validateRequestMethod( interactionRequest, config.system.protocolMode ), }; // Skip active account lookup if either login hint or session id is set if (request.loginHint || request.sid) { return validatedRequest; } const account = request.account || browserStorage.getActiveAccount(correlationId); if (account) { logger.verbose("Setting validated request account", correlationId); logger.verbosePii( `Setting validated request account: '${account.homeAccountId}'`, correlationId ); validatedRequest.account = account; } return validatedRequest; }