@azure/identity
Version:
Provides credential implementations for Azure SDK libraries that can authenticate with Microsoft Entra ID
277 lines (276 loc) • 11.1 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var identityClient_exports = {};
__export(identityClient_exports, {
IdentityClient: () => IdentityClient,
getIdentityClientAuthorityHost: () => getIdentityClientAuthorityHost
});
module.exports = __toCommonJS(identityClient_exports);
var import_core_client = require("@azure/core-client");
var import_core_util = require("@azure/core-util");
var import_core_rest_pipeline = require("@azure/core-rest-pipeline");
var import_errors = require("../errors.js");
var import_identityTokenEndpoint = require("../util/identityTokenEndpoint.js");
var import_constants = require("../constants.js");
var import_tracing = require("../util/tracing.js");
var import_logging = require("../util/logging.js");
var import_utils = require("../credentials/managedIdentityCredential/utils.js");
const noCorrelationId = "noCorrelationId";
function getIdentityClientAuthorityHost(options) {
let authorityHost = options?.authorityHost;
if (import_core_util.isNode) {
authorityHost = authorityHost ?? process.env.AZURE_AUTHORITY_HOST;
}
return authorityHost ?? import_constants.DefaultAuthorityHost;
}
class IdentityClient extends import_core_client.ServiceClient {
authorityHost;
allowLoggingAccountIdentifiers;
abortControllers;
allowInsecureConnection = false;
// used for WorkloadIdentity
tokenCredentialOptions;
constructor(options) {
const packageDetails = `azsdk-js-identity/${import_constants.SDK_VERSION}`;
const userAgentPrefix = options?.userAgentOptions?.userAgentPrefix ? `${options.userAgentOptions.userAgentPrefix} ${packageDetails}` : `${packageDetails}`;
const baseUri = getIdentityClientAuthorityHost(options);
if (!baseUri.startsWith("https:")) {
throw new Error("The authorityHost address must use the 'https' protocol.");
}
super({
requestContentType: "application/json; charset=utf-8",
retryOptions: {
maxRetries: 3
},
...options,
userAgentOptions: {
userAgentPrefix
},
baseUri
});
this.authorityHost = baseUri;
this.abortControllers = /* @__PURE__ */ new Map();
this.allowLoggingAccountIdentifiers = options?.loggingOptions?.allowLoggingAccountIdentifiers;
this.tokenCredentialOptions = { ...options };
if (options?.allowInsecureConnection) {
this.allowInsecureConnection = options.allowInsecureConnection;
}
}
async sendTokenRequest(request) {
import_logging.logger.info(`IdentityClient: sending token request to [${request.url}]`);
const response = await this.sendRequest(request);
if (response.bodyAsText && (response.status === 200 || response.status === 201)) {
const parsedBody = JSON.parse(response.bodyAsText);
if (!parsedBody.access_token) {
return null;
}
this.logIdentifiers(response);
const token = {
accessToken: {
token: parsedBody.access_token,
expiresOnTimestamp: (0, import_utils.parseExpirationTimestamp)(parsedBody),
refreshAfterTimestamp: (0, import_utils.parseRefreshTimestamp)(parsedBody),
tokenType: "Bearer"
},
refreshToken: parsedBody.refresh_token
};
import_logging.logger.info(
`IdentityClient: [${request.url}] token acquired, expires on ${token.accessToken.expiresOnTimestamp}`
);
return token;
} else {
const error = new import_errors.AuthenticationError(response.status, response.bodyAsText);
import_logging.logger.warning(
`IdentityClient: authentication error. HTTP status: ${response.status}, ${error.errorResponse.errorDescription}`
);
throw error;
}
}
async refreshAccessToken(tenantId, clientId, scopes, refreshToken, clientSecret, options = {}) {
if (refreshToken === void 0) {
return null;
}
import_logging.logger.info(
`IdentityClient: refreshing access token with client ID: ${clientId}, scopes: ${scopes} started`
);
const refreshParams = {
grant_type: "refresh_token",
client_id: clientId,
refresh_token: refreshToken,
scope: scopes
};
if (clientSecret !== void 0) {
refreshParams.client_secret = clientSecret;
}
const query = new URLSearchParams(refreshParams);
return import_tracing.tracingClient.withSpan(
"IdentityClient.refreshAccessToken",
options,
async (updatedOptions) => {
try {
const urlSuffix = (0, import_identityTokenEndpoint.getIdentityTokenEndpointSuffix)(tenantId);
const request = (0, import_core_rest_pipeline.createPipelineRequest)({
url: `${this.authorityHost}/${tenantId}/${urlSuffix}`,
method: "POST",
body: query.toString(),
abortSignal: options.abortSignal,
headers: (0, import_core_rest_pipeline.createHttpHeaders)({
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
}),
tracingOptions: updatedOptions.tracingOptions
});
const response = await this.sendTokenRequest(request);
import_logging.logger.info(`IdentityClient: refreshed token for client ID: ${clientId}`);
return response;
} catch (err) {
if (err.name === import_errors.AuthenticationErrorName && err.errorResponse.error === "interaction_required") {
import_logging.logger.info(`IdentityClient: interaction required for client ID: ${clientId}`);
return null;
} else {
import_logging.logger.warning(
`IdentityClient: failed refreshing token for client ID: ${clientId}: ${err}`
);
throw err;
}
}
}
);
}
// Here is a custom layer that allows us to abort requests that go through MSAL,
// since MSAL doesn't allow us to pass options all the way through.
generateAbortSignal(correlationId) {
const controller = new AbortController();
const controllers = this.abortControllers.get(correlationId) || [];
controllers.push(controller);
this.abortControllers.set(correlationId, controllers);
const existingOnAbort = controller.signal.onabort;
controller.signal.onabort = (...params) => {
this.abortControllers.set(correlationId, void 0);
if (existingOnAbort) {
existingOnAbort.apply(controller.signal, params);
}
};
return controller.signal;
}
abortRequests(correlationId) {
const key = correlationId || noCorrelationId;
const controllers = [
...this.abortControllers.get(key) || [],
// MSAL passes no correlation ID to the get requests...
...this.abortControllers.get(noCorrelationId) || []
];
if (!controllers.length) {
return;
}
for (const controller of controllers) {
controller.abort();
}
this.abortControllers.set(key, void 0);
}
getCorrelationId(options) {
const parameter = options?.body?.split("&").map((part) => part.split("=")).find(([key]) => key === "client-request-id");
return parameter && parameter.length ? parameter[1] || noCorrelationId : noCorrelationId;
}
// The MSAL network module methods follow
async sendGetRequestAsync(url, options) {
const request = (0, import_core_rest_pipeline.createPipelineRequest)({
url,
method: "GET",
body: options?.body,
allowInsecureConnection: this.allowInsecureConnection,
headers: (0, import_core_rest_pipeline.createHttpHeaders)(options?.headers),
abortSignal: this.generateAbortSignal(noCorrelationId)
});
const response = await this.sendRequest(request);
this.logIdentifiers(response);
return {
body: response.bodyAsText ? JSON.parse(response.bodyAsText) : void 0,
headers: response.headers.toJSON(),
status: response.status
};
}
async sendPostRequestAsync(url, options) {
const request = (0, import_core_rest_pipeline.createPipelineRequest)({
url,
method: "POST",
body: options?.body,
headers: (0, import_core_rest_pipeline.createHttpHeaders)(options?.headers),
allowInsecureConnection: this.allowInsecureConnection,
// MSAL doesn't send the correlation ID on the get requests.
abortSignal: this.generateAbortSignal(this.getCorrelationId(options))
});
const response = await this.sendRequest(request);
this.logIdentifiers(response);
return {
body: response.bodyAsText ? JSON.parse(response.bodyAsText) : void 0,
headers: response.headers.toJSON(),
status: response.status
};
}
/**
*
* @internal
*/
getTokenCredentialOptions() {
return this.tokenCredentialOptions;
}
/**
* If allowLoggingAccountIdentifiers was set on the constructor options
* we try to log the account identifiers by parsing the received access token.
*
* The account identifiers we try to log are:
* - `appid`: The application or Client Identifier.
* - `upn`: User Principal Name.
* - It might not be available in some authentication scenarios.
* - If it's not available, we put a placeholder: "No User Principal Name available".
* - `tid`: Tenant Identifier.
* - `oid`: Object Identifier of the authenticated user.
*/
logIdentifiers(response) {
if (!this.allowLoggingAccountIdentifiers || !response.bodyAsText) {
return;
}
const unavailableUpn = "No User Principal Name available";
try {
const parsed = response.parsedBody || JSON.parse(response.bodyAsText);
const accessToken = parsed.access_token;
if (!accessToken) {
return;
}
const base64Metadata = accessToken.split(".")[1];
const { appid, upn, tid, oid } = JSON.parse(
Buffer.from(base64Metadata, "base64").toString("utf8")
);
import_logging.logger.info(
`[Authenticated account] Client ID: ${appid}. Tenant ID: ${tid}. User Principal Name: ${upn || unavailableUpn}. Object ID (user): ${oid}`
);
} catch (e) {
import_logging.logger.warning(
"allowLoggingAccountIdentifiers was set, but we couldn't log the account information. Error:",
e.message
);
}
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
IdentityClient,
getIdentityClientAuthorityHost
});
//# sourceMappingURL=identityClient.js.map