@azure/msal-common
Version:
Microsoft Authentication Library for js
312 lines (279 loc) • 11.1 kB
text/typescript
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
ClientConfiguration,
buildClientConfiguration,
CommonClientConfiguration,
} from "../config/ClientConfiguration.js";
import {
INetworkModule,
NetworkRequestOptions,
} from "../network/INetworkModule.js";
import { NetworkResponse } from "../network/NetworkResponse.js";
import { ICrypto } from "../crypto/ICrypto.js";
import { Authority } from "../authority/Authority.js";
import { Logger } from "../logger/Logger.js";
import { Constants, HeaderNames } from "../utils/Constants.js";
import { ServerAuthorizationTokenResponse } from "../response/ServerAuthorizationTokenResponse.js";
import { CacheManager } from "../cache/CacheManager.js";
import { ServerTelemetryManager } from "../telemetry/server/ServerTelemetryManager.js";
import { RequestThumbprint } from "../network/RequestThumbprint.js";
import { version, name } from "../packageMetadata.js";
import { CcsCredential, CcsCredentialType } from "../account/CcsCredential.js";
import { buildClientInfoFromHomeAccountId } from "../account/ClientInfo.js";
import { IPerformanceClient } from "../telemetry/performance/IPerformanceClient.js";
import * as RequestParameterBuilder from "../request/RequestParameterBuilder.js";
import * as UrlUtils from "../utils/UrlUtils.js";
import { BaseAuthRequest } from "../request/BaseAuthRequest.js";
import { createDiscoveredInstance } from "../authority/AuthorityFactory.js";
import { PerformanceEvents } from "../telemetry/performance/PerformanceEvent.js";
import { ThrottlingUtils } from "../network/ThrottlingUtils.js";
import { AuthError } from "../error/AuthError.js";
import {
ClientAuthErrorCodes,
createClientAuthError,
} from "../error/ClientAuthError.js";
import { NetworkError } from "../error/NetworkError.js";
import { invokeAsync } from "../utils/FunctionWrappers.js";
/**
* Base application class which will construct requests to send to and handle responses from the Microsoft STS using the authorization code flow.
* @internal
*/
export abstract class BaseClient {
// Logger object
public logger: Logger;
// Application config
protected config: CommonClientConfiguration;
// Crypto Interface
protected cryptoUtils: ICrypto;
// Storage Interface
protected cacheManager: CacheManager;
// Network Interface
protected networkClient: INetworkModule;
// Server Telemetry Manager
protected serverTelemetryManager: ServerTelemetryManager | null;
// Default authority object
public authority: Authority;
// Performance telemetry client
protected performanceClient?: IPerformanceClient;
protected constructor(
configuration: ClientConfiguration,
performanceClient?: IPerformanceClient
) {
// Set the configuration
this.config = buildClientConfiguration(configuration);
// Initialize the logger
this.logger = new Logger(this.config.loggerOptions, name, version);
// Initialize crypto
this.cryptoUtils = this.config.cryptoInterface;
// Initialize storage interface
this.cacheManager = this.config.storageInterface;
// Set the network interface
this.networkClient = this.config.networkInterface;
// Set TelemetryManager
this.serverTelemetryManager = this.config.serverTelemetryManager;
// set Authority
this.authority = this.config.authOptions.authority;
// set performance telemetry client
this.performanceClient = performanceClient;
}
/**
* Creates default headers for requests to token endpoint
*/
protected createTokenRequestHeaders(
ccsCred?: CcsCredential
): Record<string, string> {
const headers: Record<string, string> = {};
headers[HeaderNames.CONTENT_TYPE] = Constants.URL_FORM_CONTENT_TYPE;
if (!this.config.systemOptions.preventCorsPreflight && ccsCred) {
switch (ccsCred.type) {
case CcsCredentialType.HOME_ACCOUNT_ID:
try {
const clientInfo = buildClientInfoFromHomeAccountId(
ccsCred.credential
);
headers[
HeaderNames.CCS_HEADER
] = `Oid:${clientInfo.uid}@${clientInfo.utid}`;
} catch (e) {
this.logger.verbose(
"Could not parse home account ID for CCS Header: " +
e
);
}
break;
case CcsCredentialType.UPN:
headers[
HeaderNames.CCS_HEADER
] = `UPN: ${ccsCred.credential}`;
break;
}
}
return headers;
}
/**
* Http post to token endpoint
* @param tokenEndpoint
* @param queryString
* @param headers
* @param thumbprint
*/
protected async executePostToTokenEndpoint(
tokenEndpoint: string,
queryString: string,
headers: Record<string, string>,
thumbprint: RequestThumbprint,
correlationId: string,
queuedEvent?: string
): Promise<NetworkResponse<ServerAuthorizationTokenResponse>> {
if (queuedEvent) {
this.performanceClient?.addQueueMeasurement(
queuedEvent,
correlationId
);
}
const response =
await this.sendPostRequest<ServerAuthorizationTokenResponse>(
thumbprint,
tokenEndpoint,
{ body: queryString, headers: headers },
correlationId
);
if (
this.config.serverTelemetryManager &&
response.status < 500 &&
response.status !== 429
) {
// Telemetry data successfully logged by server, clear Telemetry cache
this.config.serverTelemetryManager.clearTelemetryCache();
}
return response;
}
/**
* Wraps sendPostRequestAsync with necessary preflight and postflight logic
* @param thumbprint - Request thumbprint for throttling
* @param tokenEndpoint - Endpoint to make the POST to
* @param options - Body and Headers to include on the POST request
* @param correlationId - CorrelationId for telemetry
*/
async sendPostRequest<T extends ServerAuthorizationTokenResponse>(
thumbprint: RequestThumbprint,
tokenEndpoint: string,
options: NetworkRequestOptions,
correlationId: string
): Promise<NetworkResponse<T>> {
ThrottlingUtils.preProcess(this.cacheManager, thumbprint);
let response;
try {
response = await invokeAsync(
this.networkClient.sendPostRequestAsync.bind(
this.networkClient
)<T>,
PerformanceEvents.NetworkClientSendPostRequestAsync,
this.logger,
this.performanceClient,
correlationId
)(tokenEndpoint, options);
const responseHeaders = response.headers || {};
this.performanceClient?.addFields(
{
refreshTokenSize: response.body.refresh_token?.length || 0,
httpVerToken:
responseHeaders[HeaderNames.X_MS_HTTP_VERSION] || "",
requestId:
responseHeaders[HeaderNames.X_MS_REQUEST_ID] || "",
},
correlationId
);
} catch (e) {
if (e instanceof NetworkError) {
const responseHeaders = e.responseHeaders;
if (responseHeaders) {
this.performanceClient?.addFields(
{
httpVerToken:
responseHeaders[
HeaderNames.X_MS_HTTP_VERSION
] || "",
requestId:
responseHeaders[HeaderNames.X_MS_REQUEST_ID] ||
"",
contentTypeHeader:
responseHeaders[HeaderNames.CONTENT_TYPE] ||
undefined,
contentLengthHeader:
responseHeaders[HeaderNames.CONTENT_LENGTH] ||
undefined,
httpStatus: e.httpStatus,
},
correlationId
);
}
throw e.error;
}
if (e instanceof AuthError) {
throw e;
} else {
throw createClientAuthError(ClientAuthErrorCodes.networkError);
}
}
ThrottlingUtils.postProcess(this.cacheManager, thumbprint, response);
return response;
}
/**
* Updates the authority object of the client. Endpoint discovery must be completed.
* @param updatedAuthority
*/
async updateAuthority(
cloudInstanceHostname: string,
correlationId: string
): Promise<void> {
this.performanceClient?.addQueueMeasurement(
PerformanceEvents.UpdateTokenEndpointAuthority,
correlationId
);
const cloudInstanceAuthorityUri = `https://${cloudInstanceHostname}/${this.authority.tenant}/`;
const cloudInstanceAuthority = await createDiscoveredInstance(
cloudInstanceAuthorityUri,
this.networkClient,
this.cacheManager,
this.authority.options,
this.logger,
correlationId,
this.performanceClient
);
this.authority = cloudInstanceAuthority;
}
/**
* Creates query string for the /token request
* @param request
*/
createTokenQueryParameters(request: BaseAuthRequest): string {
const parameters = new Map<string, string>();
if (request.embeddedClientId) {
RequestParameterBuilder.addBrokerParameters(
parameters,
this.config.authOptions.clientId,
this.config.authOptions.redirectUri
);
}
if (request.tokenQueryParameters) {
RequestParameterBuilder.addExtraQueryParameters(
parameters,
request.tokenQueryParameters
);
}
RequestParameterBuilder.addCorrelationId(
parameters,
request.correlationId
);
RequestParameterBuilder.instrumentBrokerParams(
parameters,
request.correlationId,
this.performanceClient
);
return UrlUtils.mapToQueryString(parameters);
}
}