Skip to content

Added token revocation functionality to Managed Identity's Service Fabric source #7679

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
920bac3
Initial Implementation
Robbie-Microsoft Apr 2, 2025
73ffd22
Change files
Robbie-Microsoft Apr 2, 2025
f4f002a
Improvements
Robbie-Microsoft Apr 2, 2025
468de01
Improvements
Robbie-Microsoft Apr 2, 2025
dcd382f
Added unit tests
Robbie-Microsoft Apr 3, 2025
d8fbbca
Merge branch 'dev' into msi_v1_token_revocation
Robbie-Microsoft Apr 3, 2025
b09a464
Implemented some github feedback
Robbie-Microsoft Apr 3, 2025
0f7b2b9
Implemented github feedback
Robbie-Microsoft Apr 3, 2025
b32f45e
package-lock
Robbie-Microsoft Apr 3, 2025
604194d
Defined sha256 hash for test access token
Robbie-Microsoft Apr 4, 2025
3405bce
Switched to hex encoding, instead of base64, for the sha256 hash
Robbie-Microsoft Apr 7, 2025
f1d096f
Change files
Robbie-Microsoft Apr 7, 2025
203d5d0
Merge branch 'dev' into msi_v1_token_revocation
Robbie-Microsoft Apr 7, 2025
d9f15e6
Merge branch 'dev' into msi_v1_token_revocation
Robbie-Microsoft Apr 17, 2025
0832c02
Merge branch 'dev' into msi_v1_token_revocation
Robbie-Microsoft Apr 22, 2025
753ec2b
Merge branch 'dev' into msi_v1_token_revocation
Robbie-Microsoft May 2, 2025
aa2a42d
Merge branch 'dev' into msi_v1_token_revocation
Robbie-Microsoft May 19, 2025
0f15894
Merge branch 'dev' into msi_v1_token_revocation
Robbie-Microsoft May 21, 2025
b48caf9
Implemented Neha's feedback
Robbie-Microsoft May 21, 2025
865697d
Merge branch 'dev' into msi_v1_token_revocation
Robbie-Microsoft May 21, 2025
2295235
Removed app service
Robbie-Microsoft May 21, 2025
c34e1e4
undid code deletion
Robbie-Microsoft May 21, 2025
f9b13fb
Added back happy path test
Robbie-Microsoft May 22, 2025
e7e0af7
added additional unit test
Robbie-Microsoft May 22, 2025
4a59853
typos
Robbie-Microsoft May 22, 2025
a3434fa
Merge branch 'dev' into msi_v1_token_revocation
Robbie-Microsoft May 22, 2025
77c94d0
fixed incorrect comment
Robbie-Microsoft May 23, 2025
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
Original file line number Diff line number Diff line change
@@ -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": "[email protected]",
"dependentChangeType": "patch"
}
1 change: 1 addition & 0 deletions lib/msal-node/apiReview/msal-node.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ export class ManagedIdentityApplication {

// @public (undocumented)
export type ManagedIdentityConfiguration = {
clientCapabilities?: Array<string>;
managedIdentityIdParams?: ManagedIdentityIdParams;
system?: NodeSystemOptions;
};
Expand Down
80 changes: 70 additions & 10 deletions lib/msal-node/src/client/ManagedIdentityApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ import {
DEFAULT_AUTHORITY_FOR_MANAGED_IDENTITY,
ManagedIdentitySourceNames,
} from "../utils/Constants.js";
import { ManagedIdentityId } from "../config/ManagedIdentityId.js";

const SOURCES_THAT_SUPPORT_TOKEN_REVOCATION: Array<ManagedIdentitySourceNames> =
[
ManagedIdentitySourceNames.APP_SERVICE,
ManagedIdentitySourceNames.SERVICE_FABRIC,
];

/**
* Class to initialize a managed identity and identify the service
Expand Down Expand Up @@ -141,14 +148,12 @@ export class ManagedIdentityApplication {
],
authority: this.fakeAuthority.canonicalAuthority,
correlationId: this.cryptoProvider.createNewGuid(),
claims: managedIdentityRequestParams.claims,
clientCapabilities: this.config.clientCapabilities,
};

if (
managedIdentityRequestParams.claims ||
managedIdentityRequest.forceRefresh
) {
// make a network call to the managed identity source
return this.managedIdentityClient.sendManagedIdentityTokenRequest(
if (managedIdentityRequest.forceRefresh) {
return this.acquireTokenFromManagedIdentity(
managedIdentityRequest,
this.config.managedIdentityId,
this.fakeAuthority
Expand All @@ -164,16 +169,47 @@ export class ManagedIdentityApplication {
ManagedIdentityApplication.nodeStorage as NodeStorage
);

/*
* 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 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 (
cachedAuthenticationResult &&
SOURCES_THAT_SUPPORT_TOKEN_REVOCATION.includes(sourceName)
) {
const revokedTokenSha256Hash: string =
await this.cryptoProvider.hashString(
cachedAuthenticationResult.accessToken
);
managedIdentityRequest.revokedTokenSha256Hash =
revokedTokenSha256Hash;
}

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;
await this.managedIdentityClient.sendManagedIdentityTokenRequest(
await this.acquireTokenFromManagedIdentity(
managedIdentityRequest,
this.config.managedIdentityId,
this.fakeAuthority,
Expand All @@ -183,15 +219,39 @@ 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
);
}
}

/**
* 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<AuthenticationResult> {
// 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
Expand Down
14 changes: 7 additions & 7 deletions lib/msal-node/src/client/ManagedIdentitySources/AppService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,19 @@ 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";
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";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please do not merge this

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, remove the app service changes from this PR, and then we can merge it for Service Fabric. We will create a new PR later after app service deploys their token revocation support.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gladjohn Expected GA for App Service token revocation is 5/30? I will wait to merge until then if you can confirm.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected GA for App Service token revocation is 5/30?

5/30 is availability of the feature in test ring.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the timeline for it to be available in prod? I am expecting that we will not merge until this is available in prod.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of now PROD for SF and App Service is 6/30


/**
* Original source of code: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/src/AppServiceManagedIdentitySource.cs
Expand Down Expand Up @@ -114,11 +113,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 (
Expand Down
16 changes: 8 additions & 8 deletions lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -201,11 +199,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
Expand Down Expand Up @@ -303,7 +301,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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ 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,
} from "../../utils/Constants.js";
import { ManagedIdentityTokenResponse } from "../../response/ManagedIdentityTokenResponse.js";
import { NodeStorage } from "../../cache/NodeStorage.js";
import {
Expand Down Expand Up @@ -146,6 +150,29 @@ export abstract class BaseManagedIdentitySource {
managedIdentityId
);

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.revokedTokenSha256Hash;
}

if (managedIdentityRequest.clientCapabilities?.length) {
const clientCapabilities: string =
managedIdentityRequest.clientCapabilities.toString();

this.logger.info(
`[Managed Identity] The following client capabilities are present in the request: ${clientCapabilities}`
);

networkRequest.queryParameters[
ManagedIdentityQueryParameters.XMS_CC
] = clientCapabilities;
}

const headers: Record<string, string> = networkRequest.headers;
headers[HeaderNames.CONTENT_TYPE] = Constants.URL_FORM_CONTENT_TYPE;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -109,9 +109,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;
Expand Down
11 changes: 5 additions & 6 deletions lib/msal-node/src/client/ManagedIdentitySources/Imds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -112,11 +111,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 (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -107,12 +105,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 (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -131,11 +130,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 (
Expand Down
Loading