Skip to content

Added token revocation functionality to Managed Identity's App Service and Service Fabric sources #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

Open
wants to merge 15 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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 () => {

Choose a reason for hiding this comment

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

Add test when just the capabilities are set and claims are not passed?

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 =

Choose a reason for hiding this comment

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

Good use of spies and verifying the final 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.