From 920bac326c5e8f22164ac5426327c677b6da2f41 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Wed, 2 Apr 2025 15:08:00 -0400 Subject: [PATCH 01/11] Initial Implementation --- lib/msal-node/apiReview/msal-node.api.md | 1 + .../src/client/ManagedIdentityApplication.ts | 28 +++++++++++--- .../ManagedIdentitySources/AppService.ts | 18 +++++---- .../client/ManagedIdentitySources/AzureArc.ts | 20 ++++++---- .../BaseManagedIdentitySource.ts | 38 ++++++++++++++++++- .../ManagedIdentitySources/CloudShell.ts | 12 ++++-- .../src/client/ManagedIdentitySources/Imds.ts | 15 +++++--- .../ManagedIdentitySources/MachineLearning.ts | 19 ++++++---- .../ManagedIdentitySources/ServiceFabric.ts | 16 +++++--- lib/msal-node/src/config/Configuration.ts | 6 ++- .../src/request/ManagedIdentityRequest.ts | 5 ++- lib/msal-node/src/utils/Constants.ts | 30 ++++++++++++--- 12 files changed, 155 insertions(+), 53 deletions(-) diff --git a/lib/msal-node/apiReview/msal-node.api.md b/lib/msal-node/apiReview/msal-node.api.md index beba4484ed..1bbe9769f9 100644 --- a/lib/msal-node/apiReview/msal-node.api.md +++ b/lib/msal-node/apiReview/msal-node.api.md @@ -397,6 +397,7 @@ export class ManagedIdentityApplication { // @public (undocumented) export type ManagedIdentityConfiguration = { + clientCapabilities?: Array; managedIdentityIdParams?: ManagedIdentityIdParams; system?: NodeSystemOptions; }; diff --git a/lib/msal-node/src/client/ManagedIdentityApplication.ts b/lib/msal-node/src/client/ManagedIdentityApplication.ts index 6f2388335a..7586f08f44 100644 --- a/lib/msal-node/src/client/ManagedIdentityApplication.ts +++ b/lib/msal-node/src/client/ManagedIdentityApplication.ts @@ -141,12 +141,11 @@ export class ManagedIdentityApplication { ], authority: this.fakeAuthority.canonicalAuthority, correlationId: this.cryptoProvider.createNewGuid(), + claims: managedIdentityRequestParams.claims, + clientCapabilities: this.config.clientCapabilities, }; - if ( - managedIdentityRequestParams.claims || - managedIdentityRequest.forceRefresh - ) { + if (managedIdentityRequest.forceRefresh) { // make a network call to the managed identity source return this.managedIdentityClient.sendManagedIdentityTokenRequest( managedIdentityRequest, @@ -165,6 +164,25 @@ export class ManagedIdentityApplication { ); if (cachedAuthenticationResult) { + /* + * Check if claims are present in the managed identity request. + * If they are, hash the access token and add it to the request. + */ + if (managedIdentityRequest.claims) { + const accessTokenSha256Hash: string = + await this.cryptoProvider.hashString( + cachedAuthenticationResult.accessToken + ); + managedIdentityRequest.accessTokenSha256Hash = + accessTokenSha256Hash; + + return this.managedIdentityClient.sendManagedIdentityTokenRequest( + managedIdentityRequest, + this.config.managedIdentityId, + this.fakeAuthority + ); + } + // if the token is not expired but must be refreshed; get a new one in the background if (lastCacheOutcome === CacheOutcome.PROACTIVELY_REFRESHED) { this.logger.info( @@ -173,7 +191,7 @@ export class ManagedIdentityApplication { // make a network call to the managed identity source; refresh the access token in the background const refreshAccessToken = true; - await this.managedIdentityClient.sendManagedIdentityTokenRequest( + return this.managedIdentityClient.sendManagedIdentityTokenRequest( managedIdentityRequest, this.config.managedIdentityId, this.fakeAuthority, diff --git a/lib/msal-node/src/client/ManagedIdentitySources/AppService.ts b/lib/msal-node/src/client/ManagedIdentitySources/AppService.ts index 9eccbf287c..e474c9eefd 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/AppService.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/AppService.ts @@ -7,12 +7,11 @@ import { INetworkModule, Logger } from "@azure/msal-common/node"; import { BaseManagedIdentitySource } from "./BaseManagedIdentitySource.js"; import { HttpMethod, - APP_SERVICE_SECRET_HEADER_NAME, - API_VERSION_QUERY_PARAMETER_NAME, - RESOURCE_BODY_OR_QUERY_PARAMETER_NAME, ManagedIdentityEnvironmentVariableNames, ManagedIdentitySourceNames, ManagedIdentityIdType, + ManagedIdentityQueryParameters, + ManagedIdentityHeaders, } from "../../utils/Constants.js"; import { CryptoProvider } from "../../crypto/CryptoProvider.js"; import { ManagedIdentityRequestParameters } from "../../config/ManagedIdentityRequestParameters.js"; @@ -20,7 +19,7 @@ import { ManagedIdentityId } from "../../config/ManagedIdentityId.js"; import { NodeStorage } from "../../cache/NodeStorage.js"; // MSI Constants. Docs for MSI are available here https://docs.microsoft.com/azure/app-service/overview-managed-identity -const APP_SERVICE_MSI_API_VERSION: string = "2019-08-01"; +const APP_SERVICE_MSI_API_VERSION: string = "2025-03-30"; /** * Original source of code: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/src/AppServiceManagedIdentitySource.cs @@ -50,6 +49,10 @@ export class AppService extends BaseManagedIdentitySource { this.identityHeader = identityHeader; } + public getSourceName(): ManagedIdentitySourceNames { + return ManagedIdentitySourceNames.APP_SERVICE; + } + public static getEnvironmentVariables(): Array { const identityEndpoint: string | undefined = process.env[ @@ -114,11 +117,12 @@ export class AppService extends BaseManagedIdentitySource { this.identityEndpoint ); - request.headers[APP_SERVICE_SECRET_HEADER_NAME] = this.identityHeader; + request.headers[ManagedIdentityHeaders.APP_SERVICE_SECRET_HEADER_NAME] = + this.identityHeader; - request.queryParameters[API_VERSION_QUERY_PARAMETER_NAME] = + request.queryParameters[ManagedIdentityQueryParameters.API_VERSION] = APP_SERVICE_MSI_API_VERSION; - request.queryParameters[RESOURCE_BODY_OR_QUERY_PARAMETER_NAME] = + request.queryParameters[ManagedIdentityQueryParameters.RESOURCE] = resource; if ( diff --git a/lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts b/lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts index 643f714e64..e337b07dd5 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts @@ -22,15 +22,13 @@ import { createManagedIdentityError, } from "../../error/ManagedIdentityError.js"; import { - API_VERSION_QUERY_PARAMETER_NAME, - AUTHORIZATION_HEADER_NAME, AZURE_ARC_SECRET_FILE_MAX_SIZE_BYTES, HttpMethod, - METADATA_HEADER_NAME, ManagedIdentityEnvironmentVariableNames, + ManagedIdentityHeaders, ManagedIdentityIdType, + ManagedIdentityQueryParameters, ManagedIdentitySourceNames, - RESOURCE_BODY_OR_QUERY_PARAMETER_NAME, } from "../../utils/Constants.js"; import { NodeStorage } from "../../cache/NodeStorage.js"; import { @@ -88,6 +86,10 @@ export class AzureArc extends BaseManagedIdentitySource { this.identityEndpoint = identityEndpoint; } + public getSourceName(): ManagedIdentitySourceNames { + return ManagedIdentitySourceNames.AZURE_ARC; + } + public static getEnvironmentVariables(): Array { let identityEndpoint: string | undefined = process.env[ @@ -201,11 +203,11 @@ export class AzureArc extends BaseManagedIdentitySource { this.identityEndpoint.replace("localhost", "127.0.0.1") ); - request.headers[METADATA_HEADER_NAME] = "true"; + request.headers[ManagedIdentityHeaders.METADATA_HEADER_NAME] = "true"; - request.queryParameters[API_VERSION_QUERY_PARAMETER_NAME] = + request.queryParameters[ManagedIdentityQueryParameters.API_VERSION] = ARC_API_VERSION; - request.queryParameters[RESOURCE_BODY_OR_QUERY_PARAMETER_NAME] = + request.queryParameters[ManagedIdentityQueryParameters.RESOURCE] = resource; // bodyParameters calculated in BaseManagedIdentity.acquireTokenWithManagedIdentity @@ -303,7 +305,9 @@ export class AzureArc extends BaseManagedIdentitySource { this.logger.info( `[Managed Identity] Adding authorization header to the request.` ); - networkRequest.headers[AUTHORIZATION_HEADER_NAME] = authHeaderValue; + networkRequest.headers[ + ManagedIdentityHeaders.AUTHORIZATION_HEADER_NAME + ] = authHeaderValue; try { retryResponse = diff --git a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts index 1dd93e5865..5a7114202a 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts @@ -24,7 +24,12 @@ import { ManagedIdentityId } from "../../config/ManagedIdentityId.js"; import { ManagedIdentityRequestParameters } from "../../config/ManagedIdentityRequestParameters.js"; import { CryptoProvider } from "../../crypto/CryptoProvider.js"; import { ManagedIdentityRequest } from "../../request/ManagedIdentityRequest.js"; -import { HttpMethod, ManagedIdentityIdType } from "../../utils/Constants.js"; +import { + HttpMethod, + ManagedIdentityIdType, + ManagedIdentityQueryParameters, + ManagedIdentitySourceNames, +} from "../../utils/Constants.js"; import { ManagedIdentityTokenResponse } from "../../response/ManagedIdentityTokenResponse.js"; import { NodeStorage } from "../../cache/NodeStorage.js"; import { @@ -73,6 +78,8 @@ export abstract class BaseManagedIdentitySource { managedIdentityId: ManagedIdentityId ): ManagedIdentityRequestParameters; + abstract getSourceName(): ManagedIdentitySourceNames; + public async getServerTokenResponseAsync( response: NetworkResponse, // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -146,6 +153,35 @@ export abstract class BaseManagedIdentitySource { managedIdentityId ); + const sourceName: ManagedIdentitySourceNames = this.getSourceName(); + if ( + (sourceName === ManagedIdentitySourceNames.APP_SERVICE || + sourceName === ManagedIdentitySourceNames.SERVICE_FABRIC) && + managedIdentityRequest.claims && + managedIdentityRequest.accessTokenSha256Hash + ) { + this.logger.info( + `[Managed Identity] The following claims are present in the request: ${managedIdentityRequest.claims}` + ); + + networkRequest.queryParameters[ + ManagedIdentityQueryParameters.SHA256_TOKEN_TO_REFRESH + ] = managedIdentityRequest.accessTokenSha256Hash; + } + + if (managedIdentityRequest.clientCapabilities?.length) { + const clientCapabilities: string = + managedIdentityRequest.clientCapabilities.toString(); + + this.logger.info( + `[Managed Identity] The following claims are present in the request: ${clientCapabilities}` + ); + + networkRequest.queryParameters[ + ManagedIdentityQueryParameters.XMS_CC + ] = clientCapabilities; + } + const headers: Record = networkRequest.headers; headers[HeaderNames.CONTENT_TYPE] = Constants.URL_FORM_CONTENT_TYPE; diff --git a/lib/msal-node/src/client/ManagedIdentitySources/CloudShell.ts b/lib/msal-node/src/client/ManagedIdentitySources/CloudShell.ts index 55d99cabfd..7440138a17 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/CloudShell.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/CloudShell.ts @@ -10,11 +10,11 @@ import { NodeStorage } from "../../cache/NodeStorage.js"; import { CryptoProvider } from "../../crypto/CryptoProvider.js"; import { HttpMethod, - METADATA_HEADER_NAME, ManagedIdentityEnvironmentVariableNames, + ManagedIdentityHeaders, ManagedIdentityIdType, + ManagedIdentityQueryParameters, ManagedIdentitySourceNames, - RESOURCE_BODY_OR_QUERY_PARAMETER_NAME, } from "../../utils/Constants.js"; import { ManagedIdentityErrorCodes, @@ -47,6 +47,10 @@ export class CloudShell extends BaseManagedIdentitySource { this.msiEndpoint = msiEndpoint; } + public getSourceName(): ManagedIdentitySourceNames { + return ManagedIdentitySourceNames.CLOUD_SHELL; + } + public static getEnvironmentVariables(): Array { const msiEndpoint: string | undefined = process.env[ManagedIdentityEnvironmentVariableNames.MSI_ENDPOINT]; @@ -109,9 +113,9 @@ export class CloudShell extends BaseManagedIdentitySource { this.msiEndpoint ); - request.headers[METADATA_HEADER_NAME] = "true"; + request.headers[ManagedIdentityHeaders.METADATA_HEADER_NAME] = "true"; - request.bodyParameters[RESOURCE_BODY_OR_QUERY_PARAMETER_NAME] = + request.bodyParameters[ManagedIdentityQueryParameters.RESOURCE] = resource; return request; diff --git a/lib/msal-node/src/client/ManagedIdentitySources/Imds.ts b/lib/msal-node/src/client/ManagedIdentitySources/Imds.ts index fd97abceed..b2affffa3e 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/Imds.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/Imds.ts @@ -9,13 +9,12 @@ import { ManagedIdentityRequestParameters } from "../../config/ManagedIdentityRe import { BaseManagedIdentitySource } from "./BaseManagedIdentitySource.js"; import { CryptoProvider } from "../../crypto/CryptoProvider.js"; import { - API_VERSION_QUERY_PARAMETER_NAME, HttpMethod, - METADATA_HEADER_NAME, ManagedIdentityEnvironmentVariableNames, + ManagedIdentityHeaders, ManagedIdentityIdType, + ManagedIdentityQueryParameters, ManagedIdentitySourceNames, - RESOURCE_BODY_OR_QUERY_PARAMETER_NAME, } from "../../utils/Constants.js"; import { NodeStorage } from "../../cache/NodeStorage.js"; import { ImdsRetryPolicy } from "../../retry/ImdsRetryPolicy.js"; @@ -49,6 +48,10 @@ export class Imds extends BaseManagedIdentitySource { this.identityEndpoint = identityEndpoint; } + public getSourceName(): ManagedIdentitySourceNames { + return ManagedIdentitySourceNames.IMDS; + } + public static tryCreate( logger: Logger, nodeStorage: NodeStorage, @@ -112,11 +115,11 @@ export class Imds extends BaseManagedIdentitySource { this.identityEndpoint ); - request.headers[METADATA_HEADER_NAME] = "true"; + request.headers[ManagedIdentityHeaders.METADATA_HEADER_NAME] = "true"; - request.queryParameters[API_VERSION_QUERY_PARAMETER_NAME] = + request.queryParameters[ManagedIdentityQueryParameters.API_VERSION] = IMDS_API_VERSION; - request.queryParameters[RESOURCE_BODY_OR_QUERY_PARAMETER_NAME] = + request.queryParameters[ManagedIdentityQueryParameters.RESOURCE] = resource; if ( diff --git a/lib/msal-node/src/client/ManagedIdentitySources/MachineLearning.ts b/lib/msal-node/src/client/ManagedIdentitySources/MachineLearning.ts index cd144fa5f1..1e732f99a9 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/MachineLearning.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/MachineLearning.ts @@ -7,13 +7,11 @@ import { INetworkModule, Logger } from "@azure/msal-common/node"; import { BaseManagedIdentitySource } from "./BaseManagedIdentitySource.js"; import { HttpMethod, - API_VERSION_QUERY_PARAMETER_NAME, - RESOURCE_BODY_OR_QUERY_PARAMETER_NAME, ManagedIdentityEnvironmentVariableNames, ManagedIdentitySourceNames, ManagedIdentityIdType, - METADATA_HEADER_NAME, - ML_AND_SF_SECRET_HEADER_NAME, + ManagedIdentityQueryParameters, + ManagedIdentityHeaders, } from "../../utils/Constants.js"; import { CryptoProvider } from "../../crypto/CryptoProvider.js"; import { ManagedIdentityRequestParameters } from "../../config/ManagedIdentityRequestParameters.js"; @@ -47,6 +45,10 @@ export class MachineLearning extends BaseManagedIdentitySource { this.secret = secret; } + public getSourceName(): ManagedIdentitySourceNames { + return ManagedIdentitySourceNames.MACHINE_LEARNING; + } + public static getEnvironmentVariables(): Array { const msiEndpoint: string | undefined = process.env[ManagedIdentityEnvironmentVariableNames.MSI_ENDPOINT]; @@ -107,12 +109,13 @@ export class MachineLearning extends BaseManagedIdentitySource { this.msiEndpoint ); - request.headers[METADATA_HEADER_NAME] = "true"; - request.headers[ML_AND_SF_SECRET_HEADER_NAME] = this.secret; + request.headers[ManagedIdentityHeaders.METADATA_HEADER_NAME] = "true"; + request.headers[ManagedIdentityHeaders.ML_AND_SF_SECRET_HEADER_NAME] = + this.secret; - request.queryParameters[API_VERSION_QUERY_PARAMETER_NAME] = + request.queryParameters[ManagedIdentityQueryParameters.API_VERSION] = MACHINE_LEARNING_MSI_API_VERSION; - request.queryParameters[RESOURCE_BODY_OR_QUERY_PARAMETER_NAME] = + request.queryParameters[ManagedIdentityQueryParameters.RESOURCE] = resource; if ( diff --git a/lib/msal-node/src/client/ManagedIdentitySources/ServiceFabric.ts b/lib/msal-node/src/client/ManagedIdentitySources/ServiceFabric.ts index 94bc0b8f5a..d63a26343b 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/ServiceFabric.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/ServiceFabric.ts @@ -10,13 +10,12 @@ import { BaseManagedIdentitySource } from "./BaseManagedIdentitySource.js"; import { NodeStorage } from "../../cache/NodeStorage.js"; import { CryptoProvider } from "../../crypto/CryptoProvider.js"; import { - API_VERSION_QUERY_PARAMETER_NAME, HttpMethod, ManagedIdentityEnvironmentVariableNames, ManagedIdentityIdType, ManagedIdentitySourceNames, - RESOURCE_BODY_OR_QUERY_PARAMETER_NAME, - ML_AND_SF_SECRET_HEADER_NAME, + ManagedIdentityQueryParameters, + ManagedIdentityHeaders, } from "../../utils/Constants.js"; // MSI Constants. Docs for MSI are available here https://docs.microsoft.com/azure/app-service/overview-managed-identity @@ -50,6 +49,10 @@ export class ServiceFabric extends BaseManagedIdentitySource { this.identityHeader = identityHeader; } + public getSourceName(): ManagedIdentitySourceNames { + return ManagedIdentitySourceNames.SERVICE_FABRIC; + } + public static getEnvironmentVariables(): Array { const identityEndpoint: string | undefined = process.env[ @@ -131,11 +134,12 @@ export class ServiceFabric extends BaseManagedIdentitySource { this.identityEndpoint ); - request.headers[ML_AND_SF_SECRET_HEADER_NAME] = this.identityHeader; + request.headers[ManagedIdentityHeaders.ML_AND_SF_SECRET_HEADER_NAME] = + this.identityHeader; - request.queryParameters[API_VERSION_QUERY_PARAMETER_NAME] = + request.queryParameters[ManagedIdentityQueryParameters.API_VERSION] = SERVICE_FABRIC_MSI_API_VERSION; - request.queryParameters[RESOURCE_BODY_OR_QUERY_PARAMETER_NAME] = + request.queryParameters[ManagedIdentityQueryParameters.RESOURCE] = resource; if ( diff --git a/lib/msal-node/src/config/Configuration.ts b/lib/msal-node/src/config/Configuration.ts index b7b7fa12ce..232dbef5bc 100644 --- a/lib/msal-node/src/config/Configuration.ts +++ b/lib/msal-node/src/config/Configuration.ts @@ -129,6 +129,7 @@ export type ManagedIdentityIdParams = { /** @public */ export type ManagedIdentityConfiguration = { + clientCapabilities?: Array; managedIdentityIdParams?: ManagedIdentityIdParams; system?: NodeSystemOptions; }; @@ -240,14 +241,16 @@ export function buildAppConfiguration({ /** @internal */ export type ManagedIdentityNodeConfiguration = { + clientCapabilities?: Array; + disableInternalRetries: boolean; managedIdentityId: ManagedIdentityId; system: Required< Pick >; - disableInternalRetries: boolean; }; export function buildManagedIdentityConfiguration({ + clientCapabilities, managedIdentityIdParams, system, }: ManagedIdentityConfiguration): ManagedIdentityNodeConfiguration { @@ -271,6 +274,7 @@ export function buildManagedIdentityConfiguration({ } return { + clientCapabilities: clientCapabilities || [], managedIdentityId: managedIdentityId, system: { loggerOptions, diff --git a/lib/msal-node/src/request/ManagedIdentityRequest.ts b/lib/msal-node/src/request/ManagedIdentityRequest.ts index a99ae12b3a..343d1dd69d 100644 --- a/lib/msal-node/src/request/ManagedIdentityRequest.ts +++ b/lib/msal-node/src/request/ManagedIdentityRequest.ts @@ -12,4 +12,7 @@ import { ManagedIdentityRequestParams } from "./ManagedIdentityRequestParams.js" * - resource - resource requested to access the protected API. It should be of the form "{ResourceIdUri}" or {ResourceIdUri/.default}. For instance https://management.azure.net or, for Microsoft Graph, https://graph.microsoft.com/.default */ export type ManagedIdentityRequest = ManagedIdentityRequestParams & - CommonClientCredentialRequest; + CommonClientCredentialRequest & { + clientCapabilities?: Array; + accessTokenSha256Hash?: string; + }; diff --git a/lib/msal-node/src/utils/Constants.ts b/lib/msal-node/src/utils/Constants.ts index a094e561bc..0571b1bc03 100644 --- a/lib/msal-node/src/utils/Constants.ts +++ b/lib/msal-node/src/utils/Constants.ts @@ -8,16 +8,34 @@ import { DefaultManagedIdentityRetryPolicy } from "../retry/DefaultManagedIdenti import { ImdsRetryPolicy } from "../retry/ImdsRetryPolicy.js"; // MSI Constants. Docs for MSI are available here https://docs.microsoft.com/azure/app-service/overview-managed-identity -export const AUTHORIZATION_HEADER_NAME: string = "Authorization"; -export const METADATA_HEADER_NAME: string = "Metadata"; -export const APP_SERVICE_SECRET_HEADER_NAME: string = "X-IDENTITY-HEADER"; -export const ML_AND_SF_SECRET_HEADER_NAME: string = "secret"; -export const API_VERSION_QUERY_PARAMETER_NAME: string = "api-version"; -export const RESOURCE_BODY_OR_QUERY_PARAMETER_NAME: string = "resource"; export const DEFAULT_MANAGED_IDENTITY_ID = "system_assigned_managed_identity"; export const MANAGED_IDENTITY_DEFAULT_TENANT = "managed_identity"; export const DEFAULT_AUTHORITY_FOR_MANAGED_IDENTITY = `https://login.microsoftonline.com/${MANAGED_IDENTITY_DEFAULT_TENANT}/`; +/** + * Managed Identity Headers - used in network requests + */ +export const ManagedIdentityHeaders = { + AUTHORIZATION_HEADER_NAME: "Authorization", + METADATA_HEADER_NAME: "Metadata", + APP_SERVICE_SECRET_HEADER_NAME: "X-IDENTITY-HEADER", + ML_AND_SF_SECRET_HEADER_NAME: "secret", +} as const; +export type ManagedIdentityHeaders = + (typeof ManagedIdentityHeaders)[keyof typeof ManagedIdentityHeaders]; + +/** + * Managed Identity Query Parameters - used in network requests + */ +export const ManagedIdentityQueryParameters = { + API_VERSION: "api-version", + RESOURCE: "resource", + SHA256_TOKEN_TO_REFRESH: "token_sha256_to_refresh", + XMS_CC: "xms_cc", +} as const; +export type ManagedIdentityQueryParameters = + (typeof ManagedIdentityQueryParameters)[keyof typeof ManagedIdentityQueryParameters]; + /** * Managed Identity Environment Variable Names */ From 73ffd228d87b70465d02ef0ec946037675e76c5e Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Wed, 2 Apr 2025 15:15:51 -0400 Subject: [PATCH 02/11] Change files --- ...ure-msal-node-870e01a0-ed8b-4e52-8367-c05200d113c5.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@azure-msal-node-870e01a0-ed8b-4e52-8367-c05200d113c5.json diff --git a/change/@azure-msal-node-870e01a0-ed8b-4e52-8367-c05200d113c5.json b/change/@azure-msal-node-870e01a0-ed8b-4e52-8367-c05200d113c5.json new file mode 100644 index 0000000000..f17e8ac771 --- /dev/null +++ b/change/@azure-msal-node-870e01a0-ed8b-4e52-8367-c05200d113c5.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Added token revocation functionality to Managed Identity's App Service and Service Fabric Sources #7679", + "packageName": "@azure/msal-node", + "email": "rginsburg@microsoft.com", + "dependentChangeType": "patch" +} From f4f002a5804955e1a9937f0f454755bd0ff9732e Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Wed, 2 Apr 2025 18:23:34 -0400 Subject: [PATCH 03/11] Improvements --- .../src/client/ManagedIdentityApplication.ts | 31 ++++++++++++++----- .../ManagedIdentitySources/AppService.ts | 4 --- .../client/ManagedIdentitySources/AzureArc.ts | 4 --- .../BaseManagedIdentitySource.ts | 13 ++------ .../ManagedIdentitySources/CloudShell.ts | 4 --- .../src/client/ManagedIdentitySources/Imds.ts | 4 --- .../ManagedIdentitySources/MachineLearning.ts | 4 --- .../ManagedIdentitySources/ServiceFabric.ts | 4 --- 8 files changed, 26 insertions(+), 42 deletions(-) diff --git a/lib/msal-node/src/client/ManagedIdentityApplication.ts b/lib/msal-node/src/client/ManagedIdentityApplication.ts index 7586f08f44..65fe91edab 100644 --- a/lib/msal-node/src/client/ManagedIdentityApplication.ts +++ b/lib/msal-node/src/client/ManagedIdentityApplication.ts @@ -36,6 +36,12 @@ import { ManagedIdentitySourceNames, } from "../utils/Constants.js"; +const SOURCES_THAT_SUPPORT_TOKEN_REVOCATION: Array = + [ + ManagedIdentitySourceNames.APP_SERVICE, + ManagedIdentitySourceNames.SERVICE_FABRIC, + ]; + /** * Class to initialize a managed identity and identify the service * @public @@ -166,15 +172,26 @@ export class ManagedIdentityApplication { if (cachedAuthenticationResult) { /* * Check if claims are present in the managed identity request. - * If they are, hash the access token and add it to the request. + * If so, the cached token will not be used. */ if (managedIdentityRequest.claims) { - const accessTokenSha256Hash: string = - await this.cryptoProvider.hashString( - cachedAuthenticationResult.accessToken - ); - managedIdentityRequest.accessTokenSha256Hash = - accessTokenSha256Hash; + const sourceName: ManagedIdentitySourceNames = + this.managedIdentityClient.getManagedIdentitySource(); + + /* + * Check if the Managed Identity source supports token revocation. + * If so, hash the cached access token and add it to the request. + */ + if ( + SOURCES_THAT_SUPPORT_TOKEN_REVOCATION.includes(sourceName) + ) { + const accessTokenSha256Hash: string = + await this.cryptoProvider.hashString( + cachedAuthenticationResult.accessToken + ); + managedIdentityRequest.accessTokenSha256Hash = + accessTokenSha256Hash; + } return this.managedIdentityClient.sendManagedIdentityTokenRequest( managedIdentityRequest, diff --git a/lib/msal-node/src/client/ManagedIdentitySources/AppService.ts b/lib/msal-node/src/client/ManagedIdentitySources/AppService.ts index e474c9eefd..d59eb5ac05 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/AppService.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/AppService.ts @@ -49,10 +49,6 @@ export class AppService extends BaseManagedIdentitySource { this.identityHeader = identityHeader; } - public getSourceName(): ManagedIdentitySourceNames { - return ManagedIdentitySourceNames.APP_SERVICE; - } - public static getEnvironmentVariables(): Array { const identityEndpoint: string | undefined = process.env[ diff --git a/lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts b/lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts index e337b07dd5..ece2c5379b 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts @@ -86,10 +86,6 @@ export class AzureArc extends BaseManagedIdentitySource { this.identityEndpoint = identityEndpoint; } - public getSourceName(): ManagedIdentitySourceNames { - return ManagedIdentitySourceNames.AZURE_ARC; - } - public static getEnvironmentVariables(): Array { let identityEndpoint: string | undefined = process.env[ diff --git a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts index 5a7114202a..67147a8512 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts @@ -28,7 +28,6 @@ import { HttpMethod, ManagedIdentityIdType, ManagedIdentityQueryParameters, - ManagedIdentitySourceNames, } from "../../utils/Constants.js"; import { ManagedIdentityTokenResponse } from "../../response/ManagedIdentityTokenResponse.js"; import { NodeStorage } from "../../cache/NodeStorage.js"; @@ -78,8 +77,6 @@ export abstract class BaseManagedIdentitySource { managedIdentityId: ManagedIdentityId ): ManagedIdentityRequestParameters; - abstract getSourceName(): ManagedIdentitySourceNames; - public async getServerTokenResponseAsync( response: NetworkResponse, // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -153,13 +150,7 @@ export abstract class BaseManagedIdentitySource { managedIdentityId ); - const sourceName: ManagedIdentitySourceNames = this.getSourceName(); - if ( - (sourceName === ManagedIdentitySourceNames.APP_SERVICE || - sourceName === ManagedIdentitySourceNames.SERVICE_FABRIC) && - managedIdentityRequest.claims && - managedIdentityRequest.accessTokenSha256Hash - ) { + if (managedIdentityRequest.accessTokenSha256Hash) { this.logger.info( `[Managed Identity] The following claims are present in the request: ${managedIdentityRequest.claims}` ); @@ -174,7 +165,7 @@ export abstract class BaseManagedIdentitySource { managedIdentityRequest.clientCapabilities.toString(); this.logger.info( - `[Managed Identity] The following claims are present in the request: ${clientCapabilities}` + `[Managed Identity] The following client capabilities are present in the request: ${clientCapabilities}` ); networkRequest.queryParameters[ diff --git a/lib/msal-node/src/client/ManagedIdentitySources/CloudShell.ts b/lib/msal-node/src/client/ManagedIdentitySources/CloudShell.ts index 7440138a17..38db56978f 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/CloudShell.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/CloudShell.ts @@ -47,10 +47,6 @@ export class CloudShell extends BaseManagedIdentitySource { this.msiEndpoint = msiEndpoint; } - public getSourceName(): ManagedIdentitySourceNames { - return ManagedIdentitySourceNames.CLOUD_SHELL; - } - public static getEnvironmentVariables(): Array { const msiEndpoint: string | undefined = process.env[ManagedIdentityEnvironmentVariableNames.MSI_ENDPOINT]; diff --git a/lib/msal-node/src/client/ManagedIdentitySources/Imds.ts b/lib/msal-node/src/client/ManagedIdentitySources/Imds.ts index b2affffa3e..c24021d0d9 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/Imds.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/Imds.ts @@ -48,10 +48,6 @@ export class Imds extends BaseManagedIdentitySource { this.identityEndpoint = identityEndpoint; } - public getSourceName(): ManagedIdentitySourceNames { - return ManagedIdentitySourceNames.IMDS; - } - public static tryCreate( logger: Logger, nodeStorage: NodeStorage, diff --git a/lib/msal-node/src/client/ManagedIdentitySources/MachineLearning.ts b/lib/msal-node/src/client/ManagedIdentitySources/MachineLearning.ts index 1e732f99a9..c41f5796e0 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/MachineLearning.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/MachineLearning.ts @@ -45,10 +45,6 @@ export class MachineLearning extends BaseManagedIdentitySource { this.secret = secret; } - public getSourceName(): ManagedIdentitySourceNames { - return ManagedIdentitySourceNames.MACHINE_LEARNING; - } - public static getEnvironmentVariables(): Array { const msiEndpoint: string | undefined = process.env[ManagedIdentityEnvironmentVariableNames.MSI_ENDPOINT]; diff --git a/lib/msal-node/src/client/ManagedIdentitySources/ServiceFabric.ts b/lib/msal-node/src/client/ManagedIdentitySources/ServiceFabric.ts index d63a26343b..9b2af42cb4 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/ServiceFabric.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/ServiceFabric.ts @@ -49,10 +49,6 @@ export class ServiceFabric extends BaseManagedIdentitySource { this.identityHeader = identityHeader; } - public getSourceName(): ManagedIdentitySourceNames { - return ManagedIdentitySourceNames.SERVICE_FABRIC; - } - public static getEnvironmentVariables(): Array { const identityEndpoint: string | undefined = process.env[ From 468de01ff687c3d5e43e0b0cfe00346fe75242e5 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Wed, 2 Apr 2025 18:47:57 -0400 Subject: [PATCH 04/11] Improvements --- .../src/client/ManagedIdentityApplication.ts | 91 ++++++++++++------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/lib/msal-node/src/client/ManagedIdentityApplication.ts b/lib/msal-node/src/client/ManagedIdentityApplication.ts index 65fe91edab..0cb24701a1 100644 --- a/lib/msal-node/src/client/ManagedIdentityApplication.ts +++ b/lib/msal-node/src/client/ManagedIdentityApplication.ts @@ -35,6 +35,7 @@ import { DEFAULT_AUTHORITY_FOR_MANAGED_IDENTITY, ManagedIdentitySourceNames, } from "../utils/Constants.js"; +import { ManagedIdentityId } from "../config/ManagedIdentityId.js"; const SOURCES_THAT_SUPPORT_TOKEN_REVOCATION: Array = [ @@ -152,8 +153,7 @@ export class ManagedIdentityApplication { }; if (managedIdentityRequest.forceRefresh) { - // make a network call to the managed identity source - return this.managedIdentityClient.sendManagedIdentityTokenRequest( + return this.acquireTokenFromManagedIdentity( managedIdentityRequest, this.config.managedIdentityId, this.fakeAuthority @@ -169,46 +169,47 @@ export class ManagedIdentityApplication { ManagedIdentityApplication.nodeStorage as NodeStorage ); - if (cachedAuthenticationResult) { + /* + * Check if claims are present in the managed identity request. + * If so, the cached token will not be used. + */ + if (managedIdentityRequest.claims) { + const sourceName: ManagedIdentitySourceNames = + this.managedIdentityClient.getManagedIdentitySource(); + /* - * Check if claims are present in the managed identity request. - * If so, the cached token will not be used. + * Check if there is a cached token and if the Managed Identity source supports token revocation. + * If so, hash the cached access token and add it to the request. */ - if (managedIdentityRequest.claims) { - const sourceName: ManagedIdentitySourceNames = - this.managedIdentityClient.getManagedIdentitySource(); - - /* - * Check if the Managed Identity source supports token revocation. - * If so, hash the cached access token and add it to the request. - */ - if ( - SOURCES_THAT_SUPPORT_TOKEN_REVOCATION.includes(sourceName) - ) { - const accessTokenSha256Hash: string = - await this.cryptoProvider.hashString( - cachedAuthenticationResult.accessToken - ); - managedIdentityRequest.accessTokenSha256Hash = - accessTokenSha256Hash; - } - - return this.managedIdentityClient.sendManagedIdentityTokenRequest( - managedIdentityRequest, - this.config.managedIdentityId, - this.fakeAuthority - ); + if ( + cachedAuthenticationResult && + SOURCES_THAT_SUPPORT_TOKEN_REVOCATION.includes(sourceName) + ) { + const accessTokenSha256Hash: string = + await this.cryptoProvider.hashString( + cachedAuthenticationResult.accessToken + ); + managedIdentityRequest.accessTokenSha256Hash = + accessTokenSha256Hash; } + return this.acquireTokenFromManagedIdentity( + managedIdentityRequest, + this.config.managedIdentityId, + this.fakeAuthority + ); + } + + if (cachedAuthenticationResult) { // if the token is not expired but must be refreshed; get a new one in the background if (lastCacheOutcome === CacheOutcome.PROACTIVELY_REFRESHED) { this.logger.info( "ClientCredentialClient:getCachedAuthenticationResult - Cached access token's refreshOn property has been exceeded'. It's not expired, but must be refreshed." ); - // make a network call to the managed identity source; refresh the access token in the background + // force refresh; will run in the background const refreshAccessToken = true; - return this.managedIdentityClient.sendManagedIdentityTokenRequest( + await this.acquireTokenFromManagedIdentity( managedIdentityRequest, this.config.managedIdentityId, this.fakeAuthority, @@ -218,8 +219,7 @@ export class ManagedIdentityApplication { return cachedAuthenticationResult; } else { - // make a network call to the managed identity source - return this.managedIdentityClient.sendManagedIdentityTokenRequest( + return this.acquireTokenFromManagedIdentity( managedIdentityRequest, this.config.managedIdentityId, this.fakeAuthority @@ -227,6 +227,31 @@ export class ManagedIdentityApplication { } } + /** + * Acquires a token from a managed identity endpoint. + * + * @param managedIdentityRequest - The request object containing parameters for the managed identity token request. + * @param managedIdentityId - The identifier for the managed identity (e.g., client ID or resource ID). + * @param fakeAuthority - A placeholder authority used for the token request. + * @param refreshAccessToken - Optional flag indicating whether to force a refresh of the access token. + * @returns A promise that resolves to an {@link AuthenticationResult} containing the acquired token and related information. + * @throws {@link AuthenticationError} if the token acquisition fails. + */ + private async acquireTokenFromManagedIdentity( + managedIdentityRequest: ManagedIdentityRequest, + managedIdentityId: ManagedIdentityId, + fakeAuthority: Authority, + refreshAccessToken?: boolean + ): Promise { + // make a network call to the managed identity + return this.managedIdentityClient.sendManagedIdentityTokenRequest( + managedIdentityRequest, + managedIdentityId, + fakeAuthority, + refreshAccessToken + ); + } + /** * Determine the Managed Identity Source based on available environment variables. This API is consumed by Azure Identity SDK. * @returns ManagedIdentitySourceNames - The Managed Identity source's name From dcd382faf1f2118be89d1560d49111063bde857f Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Thu, 3 Apr 2025 18:07:22 -0400 Subject: [PATCH 05/11] Added unit tests --- .../ManagedIdentitySources/AppService.spec.ts | 57 +++++++++++++++++++ .../ManagedIdentitySources/Imds.spec.ts | 20 +++++-- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts index ed9679f00a..02cf6bae07 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts @@ -10,6 +10,7 @@ import { MANAGED_IDENTITY_APP_SERVICE_NETWORK_REQUEST_400_ERROR, MANAGED_IDENTITY_RESOURCE, MANAGED_IDENTITY_RESOURCE_ID, + TEST_CONFIG, } from "../../test_kit/StringConstants.js"; import { @@ -28,6 +29,7 @@ import { import { ManagedIdentityClient } from "../../../src/client/ManagedIdentityClient.js"; import { ManagedIdentityEnvironmentVariableNames, + ManagedIdentityQueryParameters, ManagedIdentitySourceNames, } from "../../../src/utils/Constants.js"; import { ManagedIdentityUserAssignedIdQueryParameterNames } from "../../../src/client/ManagedIdentitySources/BaseManagedIdentitySource.js"; @@ -176,6 +178,61 @@ describe("Acquires a token successfully via an App Service Managed Identity", () }); }); + describe("Miscellaneous", () => { + let managedIdentityApplication: ManagedIdentityApplication; + beforeEach(() => { + managedIdentityApplication = new ManagedIdentityApplication( + systemAssignedConfig + ); + expect(managedIdentityApplication.getManagedIdentitySource()).toBe( + ManagedIdentitySourceNames.APP_SERVICE + ); + }); + + test("ignores a cached token when claims are provided, and the Managed Identity does support token revocation, and ensures the token revocation query parameter token_sha256_to_refresh was included in the network request to the Managed Identity", async () => { + const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( + networkClient, + "sendGetRequestAsync" + ); + + let networkManagedIdentityResult: AuthenticationResult = + await managedIdentityApplication.acquireToken({ + resource: MANAGED_IDENTITY_RESOURCE, + }); + expect(networkManagedIdentityResult.fromCache).toBe(false); + expect(networkManagedIdentityResult.accessToken).toEqual( + DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken + ); + + const cachedManagedIdentityResult: AuthenticationResult = + await managedIdentityApplication.acquireToken({ + resource: MANAGED_IDENTITY_RESOURCE, + }); + expect(cachedManagedIdentityResult.fromCache).toBe(true); + expect(cachedManagedIdentityResult.accessToken).toEqual( + DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken + ); + + networkManagedIdentityResult = + await managedIdentityApplication.acquireToken({ + claims: TEST_CONFIG.CLAIMS, + resource: MANAGED_IDENTITY_RESOURCE, + }); + expect(networkManagedIdentityResult.fromCache).toBe(false); + expect(networkManagedIdentityResult.accessToken).toEqual( + DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken + ); + + expect(sendGetRequestAsyncSpy.mock.calls.length).toEqual(2); + const url: URLSearchParams = new URLSearchParams( + sendGetRequestAsyncSpy.mock.lastCall[0] + ); + expect( + url.has(ManagedIdentityQueryParameters.SHA256_TOKEN_TO_REFRESH) + ).toBe(true); + }); + }); + describe("Errors", () => { test("ensures that the error format is correct", async () => { const managedIdentityNetworkErrorClient400 = diff --git a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts index 935b95feb9..814120fd5a 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts @@ -34,6 +34,7 @@ import { } from "../../test_kit/ManagedIdentityTestUtils.js"; import { DEFAULT_MANAGED_IDENTITY_ID, + ManagedIdentityQueryParameters, ManagedIdentitySourceNames, } from "../../../src/utils/Constants.js"; import { @@ -673,13 +674,17 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { ); }); - test("ignores a cached token when claims are provided", async () => { + test("ignores a cached token when claims are provided and the Managed Identity does not support token revocation, and ensures the token revocation query parameter token_sha256_to_refresh was not included in the network request to the Managed Identity", async () => { + const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( + networkClient, + "sendGetRequestAsync" + ); + let networkManagedIdentityResult: AuthenticationResult = await systemAssignedManagedIdentityApplication.acquireToken({ resource: MANAGED_IDENTITY_RESOURCE, }); expect(networkManagedIdentityResult.fromCache).toBe(false); - expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); @@ -702,6 +707,14 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); + + expect(sendGetRequestAsyncSpy.mock.calls.length).toEqual(2); + const url: URLSearchParams = new URLSearchParams( + sendGetRequestAsyncSpy.mock.lastCall[0] + ); + expect( + url.has(ManagedIdentityQueryParameters.SHA256_TOKEN_TO_REFRESH) + ).toBe(false); }); test("ignores a cached token when forceRefresh is set to true", async () => { @@ -710,7 +723,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { resource: MANAGED_IDENTITY_RESOURCE, }); expect(networkManagedIdentityResult.fromCache).toBe(false); - expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); @@ -730,7 +742,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { resource: MANAGED_IDENTITY_RESOURCE, }); expect(networkManagedIdentityResult.fromCache).toBe(false); - expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); @@ -742,7 +753,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { resource: MANAGED_IDENTITY_RESOURCE, }); expect(networkManagedIdentityResult.fromCache).toBe(false); - expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); From b09a464c5968e7df8f1c042811a22e4fcc9b7218 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Thu, 3 Apr 2025 18:46:36 -0400 Subject: [PATCH 06/11] Implemented some github feedback --- lib/msal-node/src/client/ManagedIdentityApplication.ts | 6 +++--- .../ManagedIdentitySources/BaseManagedIdentitySource.ts | 4 ++-- lib/msal-node/src/request/ManagedIdentityRequest.ts | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/msal-node/src/client/ManagedIdentityApplication.ts b/lib/msal-node/src/client/ManagedIdentityApplication.ts index 0cb24701a1..9c50324d28 100644 --- a/lib/msal-node/src/client/ManagedIdentityApplication.ts +++ b/lib/msal-node/src/client/ManagedIdentityApplication.ts @@ -185,12 +185,12 @@ export class ManagedIdentityApplication { cachedAuthenticationResult && SOURCES_THAT_SUPPORT_TOKEN_REVOCATION.includes(sourceName) ) { - const accessTokenSha256Hash: string = + const revokedTokenSha256Hash: string = await this.cryptoProvider.hashString( cachedAuthenticationResult.accessToken ); - managedIdentityRequest.accessTokenSha256Hash = - accessTokenSha256Hash; + managedIdentityRequest.revokedTokenSha256Hash = + revokedTokenSha256Hash; } return this.acquireTokenFromManagedIdentity( diff --git a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts index 67147a8512..1164ceb375 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts @@ -150,14 +150,14 @@ export abstract class BaseManagedIdentitySource { managedIdentityId ); - if (managedIdentityRequest.accessTokenSha256Hash) { + if (managedIdentityRequest.revokedTokenSha256Hash) { this.logger.info( `[Managed Identity] The following claims are present in the request: ${managedIdentityRequest.claims}` ); networkRequest.queryParameters[ ManagedIdentityQueryParameters.SHA256_TOKEN_TO_REFRESH - ] = managedIdentityRequest.accessTokenSha256Hash; + ] = managedIdentityRequest.revokedTokenSha256Hash; } if (managedIdentityRequest.clientCapabilities?.length) { diff --git a/lib/msal-node/src/request/ManagedIdentityRequest.ts b/lib/msal-node/src/request/ManagedIdentityRequest.ts index 343d1dd69d..9ad7d18b74 100644 --- a/lib/msal-node/src/request/ManagedIdentityRequest.ts +++ b/lib/msal-node/src/request/ManagedIdentityRequest.ts @@ -8,11 +8,11 @@ import { ManagedIdentityRequestParams } from "./ManagedIdentityRequestParams.js" /** * ManagedIdentityRequest - * - forceRefresh - forces managed identity requests to skip the cache and make network calls if true - * - resource - resource requested to access the protected API. It should be of the form "{ResourceIdUri}" or {ResourceIdUri/.default}. For instance https://management.azure.net or, for Microsoft Graph, https://graph.microsoft.com/.default + * - clientCapabilities - an array of capabilities to be added to all network requests as part of the `xms_cc` claims request + * - revokedTokenSha256Hash - a SHA256 hash of the token to be revoked. The managed identity will revoke the token based on a SHA256 hash of the token, not the token itself. This is to prevent the token from being leaked in transit. */ export type ManagedIdentityRequest = ManagedIdentityRequestParams & CommonClientCredentialRequest & { clientCapabilities?: Array; - accessTokenSha256Hash?: string; + revokedTokenSha256Hash?: string; }; From 0f7b2b9038f2afa3715bd33013bc623eef6efb79 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Thu, 3 Apr 2025 19:38:37 -0400 Subject: [PATCH 07/11] Implemented github feedback --- .../ConfidentialClientApplication.spec.ts | 2 +- .../ManagedIdentitySources/AppService.spec.ts | 48 ++++++++++++------- .../ManagedIdentitySources/Imds.spec.ts | 32 ++++++++++--- 3 files changed, 58 insertions(+), 24 deletions(-) diff --git a/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts b/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts index 6461e73927..e4fc599f1c 100644 --- a/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts +++ b/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts @@ -137,7 +137,7 @@ describe("ConfidentialClientApplication", () => { const config: Configuration = await ClientTestUtils.createTestConfidentialClientConfiguration( - ["cp1", "cp2"], + CAE_CONSTANTS.CLIENT_CAPABILITIES, mockNetworkClient( {}, // not needed CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT diff --git a/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts index 02cf6bae07..f316f9b894 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts @@ -5,6 +5,7 @@ import { ManagedIdentityApplication } from "../../../src/client/ManagedIdentityApplication.js"; import { + CAE_CONSTANTS, DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT, DEFAULT_USER_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT, MANAGED_IDENTITY_APP_SERVICE_NETWORK_REQUEST_400_ERROR, @@ -33,13 +34,17 @@ import { ManagedIdentitySourceNames, } from "../../../src/utils/Constants.js"; import { ManagedIdentityUserAssignedIdQueryParameterNames } from "../../../src/client/ManagedIdentitySources/BaseManagedIdentitySource.js"; +import { CryptoProvider } from "../../../src/index.js"; describe("Acquires a token successfully via an App Service Managed Identity", () => { + let cryptoProvider: CryptoProvider; beforeAll(() => { process.env[ManagedIdentityEnvironmentVariableNames.IDENTITY_ENDPOINT] = "fake_IDENTITY_ENDPOINT"; process.env[ManagedIdentityEnvironmentVariableNames.IDENTITY_HEADER] = "fake_IDENTITY_HEADER"; + + cryptoProvider = new CryptoProvider(); }); afterAll(() => { @@ -179,22 +184,18 @@ describe("Acquires a token successfully via an App Service Managed Identity", () }); describe("Miscellaneous", () => { - let managedIdentityApplication: ManagedIdentityApplication; - beforeEach(() => { - managedIdentityApplication = new ManagedIdentityApplication( - systemAssignedConfig - ); - expect(managedIdentityApplication.getManagedIdentitySource()).toBe( - ManagedIdentitySourceNames.APP_SERVICE - ); - }); - - test("ignores a cached token when claims are provided, and the Managed Identity does support token revocation, and ensures the token revocation query parameter token_sha256_to_refresh was included in the network request to the Managed Identity", async () => { + test("ignores a cached token when claims are provided and the Managed Identity does support token revocation, and ensures the token revocation query parameter token_sha256_to_refresh was included in the network request to the Managed Identity", async () => { const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( networkClient, "sendGetRequestAsync" ); + const managedIdentityApplication: ManagedIdentityApplication = + new ManagedIdentityApplication({ + ...systemAssignedConfig, + clientCapabilities: CAE_CONSTANTS.CLIENT_CAPABILITIES, + }); + let networkManagedIdentityResult: AuthenticationResult = await managedIdentityApplication.acquireToken({ resource: MANAGED_IDENTITY_RESOURCE, @@ -204,6 +205,15 @@ describe("Acquires a token successfully via an App Service Managed Identity", () DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); + expect(sendGetRequestAsyncSpy.mock.calls.length).toEqual(1); + const firstNetworkRequestUrlParams: URLSearchParams = + new URLSearchParams(sendGetRequestAsyncSpy.mock.lastCall[0]); + expect( + firstNetworkRequestUrlParams.get( + ManagedIdentityQueryParameters.XMS_CC + ) + ).toEqual(CAE_CONSTANTS.CLIENT_CAPABILITIES.toString()); + const cachedManagedIdentityResult: AuthenticationResult = await managedIdentityApplication.acquireToken({ resource: MANAGED_IDENTITY_RESOURCE, @@ -212,6 +222,7 @@ describe("Acquires a token successfully via an App Service Managed Identity", () expect(cachedManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); + expect(sendGetRequestAsyncSpy.mock.calls.length).toEqual(1); networkManagedIdentityResult = await managedIdentityApplication.acquireToken({ @@ -224,12 +235,17 @@ describe("Acquires a token successfully via an App Service Managed Identity", () ); expect(sendGetRequestAsyncSpy.mock.calls.length).toEqual(2); - const url: URLSearchParams = new URLSearchParams( - sendGetRequestAsyncSpy.mock.lastCall[0] - ); + const secondNetworkRequestUrlParams: URLSearchParams = + new URLSearchParams(sendGetRequestAsyncSpy.mock.lastCall[0]); expect( - url.has(ManagedIdentityQueryParameters.SHA256_TOKEN_TO_REFRESH) - ).toBe(true); + secondNetworkRequestUrlParams.get( + ManagedIdentityQueryParameters.SHA256_TOKEN_TO_REFRESH + ) + ).toEqual( + await cryptoProvider.hashString( + DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken + ) + ); }); }); diff --git a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts index 814120fd5a..ec78cfccc1 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts @@ -6,6 +6,7 @@ import { ManagedIdentityApplication } from "../../../src/client/ManagedIdentityApplication.js"; import { ManagedIdentityConfiguration } from "../../../src/config/Configuration.js"; import { + CAE_CONSTANTS, DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT, DEFAULT_USER_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT, IMDS_EXPONENTIAL_STRATEGY_MAX_RETRIES_IN_MS, @@ -680,8 +681,14 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { "sendGetRequestAsync" ); + const managedIdentityApplication: ManagedIdentityApplication = + new ManagedIdentityApplication({ + ...systemAssignedConfig, + clientCapabilities: CAE_CONSTANTS.CLIENT_CAPABILITIES, + }); + let networkManagedIdentityResult: AuthenticationResult = - await systemAssignedManagedIdentityApplication.acquireToken({ + await managedIdentityApplication.acquireToken({ resource: MANAGED_IDENTITY_RESOURCE, }); expect(networkManagedIdentityResult.fromCache).toBe(false); @@ -689,17 +696,27 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); + expect(sendGetRequestAsyncSpy.mock.calls.length).toEqual(1); + const firstNetworkRequestUrlParams: URLSearchParams = + new URLSearchParams(sendGetRequestAsyncSpy.mock.lastCall[0]); + expect( + firstNetworkRequestUrlParams.get( + ManagedIdentityQueryParameters.XMS_CC + ) + ).toEqual(CAE_CONSTANTS.CLIENT_CAPABILITIES.toString()); + const cachedManagedIdentityResult: AuthenticationResult = - await systemAssignedManagedIdentityApplication.acquireToken({ + await managedIdentityApplication.acquireToken({ resource: MANAGED_IDENTITY_RESOURCE, }); expect(cachedManagedIdentityResult.fromCache).toBe(true); expect(cachedManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); + expect(sendGetRequestAsyncSpy.mock.calls.length).toEqual(1); networkManagedIdentityResult = - await systemAssignedManagedIdentityApplication.acquireToken({ + await managedIdentityApplication.acquireToken({ claims: TEST_CONFIG.CLAIMS, resource: MANAGED_IDENTITY_RESOURCE, }); @@ -709,11 +726,12 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { ); expect(sendGetRequestAsyncSpy.mock.calls.length).toEqual(2); - const url: URLSearchParams = new URLSearchParams( - sendGetRequestAsyncSpy.mock.lastCall[0] - ); + const secondNetworkRequestUrlParams: URLSearchParams = + new URLSearchParams(sendGetRequestAsyncSpy.mock.lastCall[0]); expect( - url.has(ManagedIdentityQueryParameters.SHA256_TOKEN_TO_REFRESH) + secondNetworkRequestUrlParams.has( + ManagedIdentityQueryParameters.SHA256_TOKEN_TO_REFRESH + ) ).toBe(false); }); From b32f45e354be67b1016136bf37ab2a57f801f9b3 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Thu, 3 Apr 2025 19:48:33 -0400 Subject: [PATCH 08/11] package-lock --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e4b0e557bc..5e02d4d91c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44259,9 +44259,9 @@ } }, "node_modules/vite": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.11.tgz", - "integrity": "sha512-4mVdhLkZ0vpqZLGJhNm+X1n7juqXApEMGlUXcOQawA45UmpxivOYaMBkI/Js3FlBsNA8hCgEnX5X04moFitSGw==", + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.12.tgz", + "integrity": "sha512-qrMwavANtSz91nDy3zEiUHMtL09x0mniQsSMvDkNxuCBM1W5vriJ22hEmwTth6DhLSWsZnHBT0yHFAQXt6efGA==", "dev": true, "license": "MIT", "peer": true, From 604194d33895c2983bcdee4d63f52ef1c3871736 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Fri, 4 Apr 2025 15:11:40 -0400 Subject: [PATCH 09/11] Defined sha256 hash for test access token --- .../client/ManagedIdentitySources/AppService.spec.ts | 11 ++--------- lib/msal-node/test/test_kit/StringConstants.ts | 4 ++++ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts index f316f9b894..a43dda131d 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts @@ -6,6 +6,7 @@ import { ManagedIdentityApplication } from "../../../src/client/ManagedIdentityApplication.js"; import { CAE_CONSTANTS, + DEFAULT_MANAGED_IDENTITY_AUTHENTICATION_RESULT_ACCESS_TOKEN_SHA256_HASH, DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT, DEFAULT_USER_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT, MANAGED_IDENTITY_APP_SERVICE_NETWORK_REQUEST_400_ERROR, @@ -34,17 +35,13 @@ import { ManagedIdentitySourceNames, } from "../../../src/utils/Constants.js"; import { ManagedIdentityUserAssignedIdQueryParameterNames } from "../../../src/client/ManagedIdentitySources/BaseManagedIdentitySource.js"; -import { CryptoProvider } from "../../../src/index.js"; describe("Acquires a token successfully via an App Service Managed Identity", () => { - let cryptoProvider: CryptoProvider; beforeAll(() => { process.env[ManagedIdentityEnvironmentVariableNames.IDENTITY_ENDPOINT] = "fake_IDENTITY_ENDPOINT"; process.env[ManagedIdentityEnvironmentVariableNames.IDENTITY_HEADER] = "fake_IDENTITY_HEADER"; - - cryptoProvider = new CryptoProvider(); }); afterAll(() => { @@ -133,8 +130,6 @@ describe("Acquires a token successfully via an App Service Managed Identity", () ManagedIdentityUserAssignedIdQueryParameterNames.MANAGED_IDENTITY_RESOURCE_ID_NON_IMDS ) ).toEqual(MANAGED_IDENTITY_RESOURCE_ID); - - jest.restoreAllMocks(); }); }); @@ -242,9 +237,7 @@ describe("Acquires a token successfully via an App Service Managed Identity", () ManagedIdentityQueryParameters.SHA256_TOKEN_TO_REFRESH ) ).toEqual( - await cryptoProvider.hashString( - DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken - ) + DEFAULT_MANAGED_IDENTITY_AUTHENTICATION_RESULT_ACCESS_TOKEN_SHA256_HASH ); }); }); diff --git a/lib/msal-node/test/test_kit/StringConstants.ts b/lib/msal-node/test/test_kit/StringConstants.ts index bd003a7b47..401d858a49 100644 --- a/lib/msal-node/test/test_kit/StringConstants.ts +++ b/lib/msal-node/test/test_kit/StringConstants.ts @@ -446,6 +446,10 @@ export const getCacheKey = (resource?: string): string => { return `-${Constants.DEFAULT_AUTHORITY_HOST}-accesstoken-${resourceHelper}-managed_identity-${MANAGED_IDENTITY_RESOURCE_BASE}--`; }; +// SHA256 hash of the DEFAULT_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken +export const DEFAULT_MANAGED_IDENTITY_AUTHENTICATION_RESULT_ACCESS_TOKEN_SHA256_HASH = + "2WeMMslunTWMTmHVsjAHTATwN0BUEXQNTo2RIwZjQa8"; + export const DEFAULT_MANAGED_IDENTITY_AUTHENTICATION_RESULT: Omit< AuthenticationResult, "correlationId" From 3405bce9fc901ec9db3a18323cbb83c383b02a07 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Mon, 7 Apr 2025 11:53:04 -0400 Subject: [PATCH 10/11] Switched to hex encoding, instead of base64, for the sha256 hash --- lib/msal-common/apiReview/msal-common.api.md | 11 +++++++++++ lib/msal-common/src/exports-common.ts | 1 + lib/msal-common/src/utils/Constants.ts | 5 +++++ .../src/client/ManagedIdentityApplication.ts | 16 ++++++++++------ .../ManagedIdentitySources/AppService.spec.ts | 4 ++-- lib/msal-node/test/test_kit/StringConstants.ts | 4 ++-- 6 files changed, 31 insertions(+), 10 deletions(-) diff --git a/lib/msal-common/apiReview/msal-common.api.md b/lib/msal-common/apiReview/msal-common.api.md index ec761913ed..ffcf217879 100644 --- a/lib/msal-common/apiReview/msal-common.api.md +++ b/lib/msal-common/apiReview/msal-common.api.md @@ -2215,6 +2215,17 @@ const emptyInputScopesError = "empty_input_scopes_error"; // @public (undocumented) const emptyInputScopeSet = "empty_input_scopeset"; +// Warning: (ae-missing-release-tag) "EncodingTypes" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "EncodingTypes" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const EncodingTypes: { + readonly HEX: "hex"; +}; + +// @public (undocumented) +export type EncodingTypes = (typeof EncodingTypes)[keyof typeof EncodingTypes]; + // Warning: (ae-missing-release-tag) "endpointResolutionError" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/lib/msal-common/src/exports-common.ts b/lib/msal-common/src/exports-common.ts index 5700a50c40..6bf3a78ef1 100644 --- a/lib/msal-common/src/exports-common.ts +++ b/lib/msal-common/src/exports-common.ts @@ -181,6 +181,7 @@ export { HttpStatus, DEFAULT_TOKEN_RENEWAL_OFFSET_SEC, JsonWebTokenTypes, + EncodingTypes, } from "./utils/Constants.js"; export { StringUtils } from "./utils/StringUtils.js"; export { StringDict } from "./utils/MsalTypes.js"; diff --git a/lib/msal-common/src/utils/Constants.ts b/lib/msal-common/src/utils/Constants.ts index a358f71ba5..ba2b8dd95c 100644 --- a/lib/msal-common/src/utils/Constants.ts +++ b/lib/msal-common/src/utils/Constants.ts @@ -389,3 +389,8 @@ export const ONE_DAY_IN_MS = 86400000; // Token renewal offset default in seconds export const DEFAULT_TOKEN_RENEWAL_OFFSET_SEC = 300; + +export const EncodingTypes = { + HEX: "hex", +} as const; +export type EncodingTypes = (typeof EncodingTypes)[keyof typeof EncodingTypes]; diff --git a/lib/msal-node/src/client/ManagedIdentityApplication.ts b/lib/msal-node/src/client/ManagedIdentityApplication.ts index 9c50324d28..5e9a95355d 100644 --- a/lib/msal-node/src/client/ManagedIdentityApplication.ts +++ b/lib/msal-node/src/client/ManagedIdentityApplication.ts @@ -18,6 +18,7 @@ import { AuthenticationResult, createClientConfigurationError, ClientConfigurationErrorCodes, + EncodingTypes, } from "@azure/msal-common/node"; import { ManagedIdentityConfiguration, @@ -36,6 +37,7 @@ import { ManagedIdentitySourceNames, } from "../utils/Constants.js"; import { ManagedIdentityId } from "../config/ManagedIdentityId.js"; +import { HashUtils } from "../crypto/HashUtils.js"; const SOURCES_THAT_SUPPORT_TOKEN_REVOCATION: Array = [ @@ -63,6 +65,8 @@ export class ManagedIdentityApplication { private managedIdentityClient: ManagedIdentityClient; + private hashUtils: HashUtils; + constructor(configuration?: ManagedIdentityConfiguration) { // undefined config means the managed identity is system-assigned this.config = buildManagedIdentityConfiguration(configuration || {}); @@ -121,6 +125,8 @@ export class ManagedIdentityApplication { this.cryptoProvider, this.config.disableInternalRetries ); + + this.hashUtils = new HashUtils(); } /** @@ -185,10 +191,9 @@ export class ManagedIdentityApplication { cachedAuthenticationResult && SOURCES_THAT_SUPPORT_TOKEN_REVOCATION.includes(sourceName) ) { - const revokedTokenSha256Hash: string = - await this.cryptoProvider.hashString( - cachedAuthenticationResult.accessToken - ); + const revokedTokenSha256Hash: string = this.hashUtils + .sha256(cachedAuthenticationResult.accessToken) + .toString(EncodingTypes.HEX); managedIdentityRequest.revokedTokenSha256Hash = revokedTokenSha256Hash; } @@ -234,8 +239,7 @@ export class ManagedIdentityApplication { * @param managedIdentityId - The identifier for the managed identity (e.g., client ID or resource ID). * @param fakeAuthority - A placeholder authority used for the token request. * @param refreshAccessToken - Optional flag indicating whether to force a refresh of the access token. - * @returns A promise that resolves to an {@link AuthenticationResult} containing the acquired token and related information. - * @throws {@link AuthenticationError} if the token acquisition fails. + * @returns A promise that resolves to an AuthenticationResult containing the acquired token and related information. */ private async acquireTokenFromManagedIdentity( managedIdentityRequest: ManagedIdentityRequest, diff --git a/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts index a43dda131d..ba73ac413f 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts @@ -6,7 +6,7 @@ import { ManagedIdentityApplication } from "../../../src/client/ManagedIdentityApplication.js"; import { CAE_CONSTANTS, - DEFAULT_MANAGED_IDENTITY_AUTHENTICATION_RESULT_ACCESS_TOKEN_SHA256_HASH, + DEFAULT_MANAGED_IDENTITY_AUTHENTICATION_RESULT_ACCESS_TOKEN_SHA256_HASH_IN_HEX, DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT, DEFAULT_USER_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT, MANAGED_IDENTITY_APP_SERVICE_NETWORK_REQUEST_400_ERROR, @@ -237,7 +237,7 @@ describe("Acquires a token successfully via an App Service Managed Identity", () ManagedIdentityQueryParameters.SHA256_TOKEN_TO_REFRESH ) ).toEqual( - DEFAULT_MANAGED_IDENTITY_AUTHENTICATION_RESULT_ACCESS_TOKEN_SHA256_HASH + DEFAULT_MANAGED_IDENTITY_AUTHENTICATION_RESULT_ACCESS_TOKEN_SHA256_HASH_IN_HEX ); }); }); diff --git a/lib/msal-node/test/test_kit/StringConstants.ts b/lib/msal-node/test/test_kit/StringConstants.ts index 401d858a49..85b205599e 100644 --- a/lib/msal-node/test/test_kit/StringConstants.ts +++ b/lib/msal-node/test/test_kit/StringConstants.ts @@ -447,8 +447,8 @@ export const getCacheKey = (resource?: string): string => { }; // SHA256 hash of the DEFAULT_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken -export const DEFAULT_MANAGED_IDENTITY_AUTHENTICATION_RESULT_ACCESS_TOKEN_SHA256_HASH = - "2WeMMslunTWMTmHVsjAHTATwN0BUEXQNTo2RIwZjQa8"; +export const DEFAULT_MANAGED_IDENTITY_AUTHENTICATION_RESULT_ACCESS_TOKEN_SHA256_HASH_IN_HEX = + "d9678c32c96e9d358c4e61d5b230074c04f037405411740d4e8d9123066341af"; export const DEFAULT_MANAGED_IDENTITY_AUTHENTICATION_RESULT: Omit< AuthenticationResult, From f1d096f77cd0742488ad6df9e1669e37cf544988 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Mon, 7 Apr 2025 11:53:21 -0400 Subject: [PATCH 11/11] Change files --- ...e-msal-common-1ec481cb-c572-4e49-b1e0-37fb0097e025.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@azure-msal-common-1ec481cb-c572-4e49-b1e0-37fb0097e025.json diff --git a/change/@azure-msal-common-1ec481cb-c572-4e49-b1e0-37fb0097e025.json b/change/@azure-msal-common-1ec481cb-c572-4e49-b1e0-37fb0097e025.json new file mode 100644 index 0000000000..db84f30e8e --- /dev/null +++ b/change/@azure-msal-common-1ec481cb-c572-4e49-b1e0-37fb0097e025.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Added token revocation functionality to Managed Identity's App Service and Service Fabric Sources #7679", + "packageName": "@azure/msal-common", + "email": "rginsburg@microsoft.com", + "dependentChangeType": "patch" +}