Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sdk/identity/identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

### Other Changes

- Refactored and cleaned up `MsalClientOptions` to eliminate nested property duplication, replaced `getIdentityClientAuthorityHost` with `getAuthorityHost`, and removed deprecated `isNode` in favor of `isNodeLike`. [#36731](https://github.com/Azure/azure-sdk-for-js/pull/36731)

## 4.14.0-beta.1 (2025-11-06)

### Features Added
Expand Down
22 changes: 3 additions & 19 deletions sdk/identity/identity/src/client/identityClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@
import type { INetworkModule, NetworkRequestOptions, NetworkResponse } from "@azure/msal-node";
import type { AccessToken, GetTokenOptions } from "@azure/core-auth";
import { ServiceClient } from "@azure/core-client";
import { isNode } from "@azure/core-util";
import type { PipelineRequest, PipelineResponse } from "@azure/core-rest-pipeline";
import { createHttpHeaders, createPipelineRequest } from "@azure/core-rest-pipeline";
import type { AbortSignalLike } from "@azure/abort-controller";
import { AuthenticationError, AuthenticationErrorName } from "../errors.js";
import { getIdentityTokenEndpointSuffix } from "../util/identityTokenEndpoint.js";
import { DefaultAuthorityHost, SDK_VERSION } from "../constants.js";
import { SDK_VERSION } from "../constants.js";
import { tracingClient } from "../util/tracing.js";
import { logger } from "../util/logging.js";
import type { TokenCredentialOptions } from "../tokenCredentialOptions.js";
Expand All @@ -19,6 +18,7 @@ import {
parseExpirationTimestamp,
parseRefreshTimestamp,
} from "../credentials/managedIdentityCredential/utils.js";
import { getAuthorityHost } from "../util/authorityHost.js";

const noCorrelationId = "noCorrelationId";

Expand All @@ -37,22 +37,6 @@ export interface TokenResponse {
refreshToken?: string;
}

/**
* @internal
*/
export function getIdentityClientAuthorityHost(options?: TokenCredentialOptions): string {
// The authorityHost can come from options or from the AZURE_AUTHORITY_HOST environment variable.
let authorityHost = options?.authorityHost;

// The AZURE_AUTHORITY_HOST environment variable can only be provided in Node.js.
if (isNode) {
authorityHost = authorityHost ?? process.env.AZURE_AUTHORITY_HOST;
}

// If the authorityHost is not provided, we use the default one from the public cloud: https://login.microsoftonline.com
return authorityHost ?? DefaultAuthorityHost;
}

/**
* The network module used by the Identity credentials.
*
Expand All @@ -74,7 +58,7 @@ export class IdentityClient extends ServiceClient implements INetworkModule {
? `${options.userAgentOptions.userAgentPrefix} ${packageDetails}`
: `${packageDetails}`;

const baseUri = getIdentityClientAuthorityHost(options);
const baseUri = getAuthorityHost(options);
if (!baseUri.startsWith("https:")) {
throw new Error("The authorityHost address must use the 'https' protocol.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ export class AuthorizationCodeCredential implements TokenCredential {
this.msalClient = createMsalClient(clientId, tenantId, {
...options,
logger,
tokenCredentialOptions: options ?? {},
});
}

Expand Down
5 changes: 2 additions & 3 deletions sdk/identity/identity/src/credentials/brokerCredential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import { tracingClient } from "../util/tracing.js";
import type { MsalClient, MsalClientOptions } from "../msal/nodeFlows/msalClient.js";
import { createMsalClient } from "../msal/nodeFlows/msalClient.js";
import { DeveloperSignOnClientId } from "../constants.js";
import { TokenCredentialOptions } from "../tokenCredentialOptions.js";
import { MultiTenantTokenCredentialOptions } from "./multiTenantTokenCredentialOptions.js";
import type { TokenCredentialOptions } from "../tokenCredentialOptions.js";
import type { MultiTenantTokenCredentialOptions } from "./multiTenantTokenCredentialOptions.js";
import { CredentialUnavailableError } from "../errors.js";

const logger = credentialLogger("BrokerCredential");
Expand Down Expand Up @@ -46,7 +46,6 @@ export class BrokerCredential implements TokenCredential {
);
const msalClientOptions: MsalClientOptions = {
...options,
tokenCredentialOptions: options,
logger,
brokerOptions: {
enabled: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,8 @@ export class ClientAssertionCredential implements TokenCredential {
this.options = options;
this.getAssertion = getAssertion;
this.msalClient = createMsalClient(clientId, tenantId, {
...options,
...this.options,
logger,
tokenCredentialOptions: this.options,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ export class ClientCertificateCredential implements TokenCredential {
this.msalClient = createMsalClient(clientId, tenantId, {
...options,
logger,
tokenCredentialOptions: options,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ export class ClientSecretCredential implements TokenCredential {
this.msalClient = createMsalClient(clientId, tenantId, {
...options,
logger,
tokenCredentialOptions: options,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ export class DeviceCodeCredential implements TokenCredential {
this.msalClient = createMsalClient(clientId, tenantId, {
...options,
logger,
tokenCredentialOptions: options || {},
});
this.disableAutomaticAuthentication = options?.disableAutomaticAuthentication;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ export class InteractiveBrowserCredential implements TokenCredential {

const msalClientOptions: MsalClientOptions = {
...options,
tokenCredentialOptions: options,
logger,
};
const ibcNodeOptions = options as InteractiveBrowserCredentialNodeOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ export class OnBehalfOfCredential implements TokenCredential {
this.msalClient = createMsalClient(clientId, this.tenantId, {
...options,
logger,
tokenCredentialOptions: options,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ export class UsernamePasswordCredential implements TokenCredential {

this.msalClient = createMsalClient(clientId, this.tenantId, {
...options,
tokenCredentialOptions: options ?? {},
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import {
import { CredentialUnavailableError } from "../errors.js";
import type { VisualStudioCodeCredentialOptions } from "./visualStudioCodeCredentialOptions.js";
import { checkTenantId } from "../util/tenantIdUtils.js";
import { createMsalClient, MsalClient } from "../msal/nodeFlows/msalClient.js";
import { createMsalClient, type MsalClient } from "../msal/nodeFlows/msalClient.js";
import { ensureScopes } from "../util/scopeUtils.js";
import { hasVSCodePlugin, vsCodeAuthRecordPath } from "../msal/nodeFlows/msalPlugins.js";
import { deserializeAuthenticationRecord } from "../msal/utils.js";
import { readFile } from "node:fs/promises";
import { AuthenticationRecord } from "../msal/types.js";
import type { AuthenticationRecord } from "../msal/types.js";

const CommonTenantId = "common";
const VSCodeClientId = "aebc6443-996d-45c2-90f0-388ff96faa56";
Expand Down
27 changes: 16 additions & 11 deletions sdk/identity/identity/src/msal/nodeFlows/msalClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
defaultLoggerCallback,
ensureValidMsalToken,
getAuthority,
getAuthorityHost,
getKnownAuthorities,
getMSALLogLevel,
handleMsalError,
Expand All @@ -30,6 +29,9 @@ import type { TokenCachePersistenceOptions } from "./tokenCachePersistenceOption
import { calculateRegionalAuthority } from "../../regionalAuthority.js";
import { getLogLevel } from "@azure/logger";
import { resolveTenantId } from "../../util/tenantIdUtils.js";
import { CommonClientOptions } from "@azure/core-client";
import { LogPolicyOptions } from "@azure/core-rest-pipeline";
import { getAuthorityHost } from "../../util/authorityHost.js";

/**
* The default logger used if no logger was passed in by the credential.
Expand Down Expand Up @@ -215,7 +217,7 @@ export interface MsalClient {
/**
* Represents the options for configuring the MsalClient.
*/
export interface MsalClientOptions {
export interface MsalClientOptions extends CommonClientOptions {
/**
* Parameters that enable WAM broker authentication in the InteractiveBrowserCredential.
*/
Expand All @@ -234,17 +236,21 @@ export interface MsalClientOptions {
/**
* A custom authority host.
*/
authorityHost?: IdentityClient["tokenCredentialOptions"]["authorityHost"];
authorityHost?: string;

/**
* Allows users to configure settings for logging policy options, allow logging account information and personally identifiable information for customer support.
*/
loggingOptions?: IdentityClient["tokenCredentialOptions"]["loggingOptions"];

/**
* The token credential options for the MsalClient.
*/
tokenCredentialOptions?: IdentityClient["tokenCredentialOptions"];
loggingOptions?: LogPolicyOptions & {
/**
* Allows logging account information once the authentication flow succeeds.
*/
allowLoggingAccountIdentifiers?: boolean;
/**
* Allows logging personally identifiable information for customer support.
*/
enableUnsafeSupportLogging?: boolean;
};

/**
* Determines whether instance discovery is disabled.
Expand Down Expand Up @@ -281,11 +287,10 @@ export function generateMsalConfiguration(
clientId,
);

// TODO: move and reuse getIdentityClientAuthorityHost
const authority = getAuthority(resolvedTenant, getAuthorityHost(msalClientOptions));

const httpClient = new IdentityClient({
...msalClientOptions.tokenCredentialOptions,
...msalClientOptions,
authorityHost: authority,
loggingOptions: msalClientOptions.loggingOptions,
});
Expand Down
20 changes: 2 additions & 18 deletions sdk/identity/identity/src/msal/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { AuthenticationRequiredError, CredentialUnavailableError } from "../erro
import type { CredentialLogger } from "../util/logging.js";
import { credentialLogger, formatError } from "../util/logging.js";
import { DefaultAuthority, DefaultAuthorityHost, DefaultTenantId } from "../constants.js";
import { randomUUID as coreRandomUUID, isNode, isNodeLike } from "@azure/core-util";
import { randomUUID as coreRandomUUID, isNodeLike } from "@azure/core-util";

import { AbortError } from "@azure/abort-controller";
import type { AzureLogLevel } from "@azure/logger";
Expand Down Expand Up @@ -52,22 +52,6 @@ export function ensureValidMsalToken(
}
}

/**
* Returns the authority host from either the options bag or the AZURE_AUTHORITY_HOST environment variable.
*
* Defaults to {@link DefaultAuthorityHost}.
* @internal
*/
export function getAuthorityHost(options?: { authorityHost?: string }): string {
let authorityHost = options?.authorityHost;

if (!authorityHost && isNodeLike) {
authorityHost = process.env.AZURE_AUTHORITY_HOST;
}

return authorityHost ?? DefaultAuthorityHost;
}

/**
* Generates a valid authority by combining a host with a tenantId.
* @internal
Expand Down Expand Up @@ -113,7 +97,7 @@ export const defaultLoggerCallback: (
logger: CredentialLogger,
platform?: "Node" | "Browser",
) => ILoggerCallback =
(credLogger: CredentialLogger, platform: "Node" | "Browser" = isNode ? "Node" : "Browser") =>
(credLogger: CredentialLogger, platform: "Node" | "Browser" = isNodeLike ? "Node" : "Browser") =>
(level, message, containsPii): void => {
if (containsPii) {
return;
Expand Down
21 changes: 21 additions & 0 deletions sdk/identity/identity/src/util/authorityHost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { isNodeLike } from "@azure/core-util";
import { DefaultAuthorityHost } from "../constants.js";

/**
* Returns the authority host from either the options bag or the AZURE_AUTHORITY_HOST environment variable.
*
* Defaults to {@link DefaultAuthorityHost}.
* @internal
*/
export function getAuthorityHost(options?: { authorityHost?: string }): string {
let authorityHost = options?.authorityHost;

if (!authorityHost && isNodeLike) {
authorityHost = process.env.AZURE_AUTHORITY_HOST;
}

return authorityHost ?? DefaultAuthorityHost;
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ describe("BrokerCredential (internal)", function () {
"test-tenant-id",
expect.objectContaining({
...options,
tokenCredentialOptions: options,
logger: expect.anything(),
brokerOptions: {
enabled: true,
Expand Down
23 changes: 12 additions & 11 deletions sdk/identity/identity/test/internal/node/identityClient.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { IdentityClient, getIdentityClientAuthorityHost } from "$internal/client/identityClient.js";
import { IdentityClient } from "$internal/client/identityClient.js";
import { getAuthorityHost } from "$internal/util/authorityHost.js";
import { IdentityTestContext } from "./httpRequests.js";
import type { IdentityTestContextInterface } from "../../httpRequestsCommon.js";
import { createResponse } from "../../httpRequestsCommon.js";
import { ClientSecretCredential } from "@azure/identity";
import { openIdConfigurationResponse, PlaybackTenantId } from "../../msalTestUtils.js";
import { isExpectedError } from "../../authTestUtils.js";
import { isNode } from "@azure/core-util";
import { isNodeLike } from "@azure/core-util";
import { describe, it, assert, beforeEach, afterEach, vi, expect } from "vitest";
import type { HttpClient } from "@azure/core-rest-pipeline";
import { createDefaultHttpClient, createHttpHeaders } from "@azure/core-rest-pipeline";
Expand All @@ -20,7 +21,7 @@ describe("IdentityClient", function () {
testContext = new IdentityTestContext({ replaceLogger: true, logLevel: "verbose" });
});
afterEach(async function () {
if (isNode) {
if (isNodeLike) {
delete process.env.AZURE_AUTHORITY_HOST;
}
await testContext.restore();
Expand All @@ -40,7 +41,7 @@ describe("IdentityClient", function () {
}),
],
});
if (isNode) {
if (isNodeLike) {
assert.strictEqual(error!.name, "CredentialUnavailableError");
} else {
// The browser version of this credential uses a legacy approach.
Expand Down Expand Up @@ -78,7 +79,7 @@ describe("IdentityClient", function () {
httpClient: mockHttpClient,
});

if (isNode) {
if (isNodeLike) {
await expect(credential.getToken(["scope"])).rejects.toThrow("This is a test error");
} else {
// The browser version of this credential uses a legacy approach.
Expand All @@ -105,13 +106,13 @@ describe("IdentityClient", function () {
);
});

it.skipIf(!isNode)("parses authority host environment variable as expected", function () {
it.skipIf(!isNodeLike)("parses authority host environment variable as expected", function () {
process.env.AZURE_AUTHORITY_HOST = "http://totallyinsecure.lol";
assert.equal(getIdentityClientAuthorityHost({}), process.env.AZURE_AUTHORITY_HOST);
assert.equal(getAuthorityHost({}), process.env.AZURE_AUTHORITY_HOST);
return;
});

it.skipIf(!isNode)(
it.skipIf(!isNodeLike)(
"throws an exception when an Env AZURE_AUTHORITY_HOST using 'http' is provided",
async function () {
process.env.AZURE_AUTHORITY_HOST = "http://totallyinsecure.lol";
Expand Down Expand Up @@ -159,7 +160,7 @@ describe("IdentityClient", function () {
httpClient: mockHttpClient,
});

if (isNode) {
if (isNodeLike) {
await expect(credential.getToken(["scope"])).rejects.toThrow(
'Response had no "expiresOn" property.',
);
Expand All @@ -171,9 +172,9 @@ describe("IdentityClient", function () {
}
});

it.skipIf(!isNode)("parses authority host environment variable as expected", function () {
it.skipIf(!isNodeLike)("parses authority host environment variable as expected", function () {
process.env.AZURE_AUTHORITY_HOST = "http://totallyinsecure.lol";
assert.equal(getIdentityClientAuthorityHost({}), process.env.AZURE_AUTHORITY_HOST);
assert.equal(getAuthorityHost({}), process.env.AZURE_AUTHORITY_HOST);
return;
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { describe, it, assert, expect, vi, beforeEach, afterEach, type MockInsta
import type { IdentityClient } from "$internal/client/identityClient.js";
import { serviceFabricErrorMessage } from "$internal/credentials/managedIdentityCredential/utils.js";
import { logger } from "@azure/identity";
import { InternalManagedIdentityCredentialOptions } from "$internal/credentials/managedIdentityCredential/options.js";
import type { InternalManagedIdentityCredentialOptions } from "$internal/credentials/managedIdentityCredential/options.js";

describe("ManagedIdentityCredential (MSAL)", function () {
let acquireTokenStub: MockInstance<
Expand Down
Loading
Loading