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 3 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
6 changes: 3 additions & 3 deletions lib/msal-node/src/client/ManagedIdentityApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions lib/msal-node/src/request/ManagedIdentityRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>;
accessTokenSha256Hash?: string;
revokedTokenSha256Hash?: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 32 additions & 16 deletions lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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,
<any>"sendGetRequestAsync"
);

const managedIdentityApplication: ManagedIdentityApplication =
new ManagedIdentityApplication({
...systemAssignedConfig,
clientCapabilities: CAE_CONSTANTS.CLIENT_CAPABILITIES,
});

let networkManagedIdentityResult: AuthenticationResult =
await managedIdentityApplication.acquireToken({
resource: MANAGED_IDENTITY_RESOURCE,
Expand All @@ -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,
Expand All @@ -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({
Expand All @@ -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
)
);
});
});

Expand Down
32 changes: 25 additions & 7 deletions lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -680,26 +681,42 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => {
<any>"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);
expect(networkManagedIdentityResult.accessToken).toEqual(
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,
});
Expand All @@ -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);
});

Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.