@azure/msal-browser
Version:
Microsoft Authentication Library for js
397 lines (376 loc) • 14.6 kB
text/typescript
/*
* 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;
}