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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdk/identity/identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

### Other Changes

- `AzureCliCredential`, `AzurePowerShellCredential`, and `AzureDeveloperCliCredential` now raise `CredentialUnavailableError` when `claims` are provided to `getToken`, as these credentials do not support claims challenges. The error message includes instructions for handling claims authentication scenarios. [#35493](https://github.com/Azure/azure-sdk-for-js/pull/35493)
- `AzureCliCredential`, `AzurePowerShellCredential`, and `AzureDeveloperCliCredential` now raise `CredentialUnavailableError` when `claims` are provided to `getToken`, as these credentials do not support claims challenges. The error message includes instructions for handling claims authentication scenarios. [#35493](https://github.com/Azure/azure-sdk-for-js/pull/35493) & [#35855](https://github.com/Azure/azure-sdk-for-js/pull/35855)

## 4.11.1 (2025-08-05)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ export class AzureCliCredential implements TokenCredential {
const scope = typeof scopes === "string" ? scopes : scopes[0];
const claimsValue = options.claims;
if (claimsValue && claimsValue.trim()) {
let loginCmd = `az login --claims-challenge ${claimsValue} --scope ${scope}`;
const encodedClaims = btoa(claimsValue);
let loginCmd = `az login --claims-challenge ${encodedClaims} --scope ${scope}`;

const tenantIdFromOptions = options.tenantId;
if (tenantIdFromOptions) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ export const developerCliCredentialInternals = {

let claimsSections: string[] = [];
if (claims) {
claimsSections = ["--claims", claims];
const encodedClaims = btoa(claims);
claimsSections = ["--claims", encodedClaims];
}
return new Promise((resolve, reject) => {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,8 @@ export class AzurePowerShellCredential implements TokenCredential {

const claimsValue = options.claims;
if (claimsValue && claimsValue.trim()) {
let loginCmd = `Connect-AzAccount -ClaimsChallenge ${claimsValue}`;
const encodedClaims = btoa(claimsValue);
let loginCmd = `Connect-AzAccount -ClaimsChallenge ${encodedClaims}`;

const tenantIdFromOptions = options.tenantId;
if (tenantIdFromOptions) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ describe("AzureCliCredential (internal)", function () {
it("throws an expected error when claims challenge is provided", async function () {
const credential = new AzureCliCredential();
const claimsChallenge = "fakeClaimChallenge";
const encodedClaims = btoa(claimsChallenge);
const scope = "https://service/.default";

let error: Error | null = null;
Expand All @@ -181,13 +182,14 @@ describe("AzureCliCredential (internal)", function () {
assert.equal(error?.name, "CredentialUnavailableError");
assert.equal(
error?.message,
`${azureCliPublicErrorMessages.claim} az login --claims-challenge ${claimsChallenge} --scope ${scope}`,
`${azureCliPublicErrorMessages.claim} az login --claims-challenge ${encodedClaims} --scope ${scope}`,
);
});

it("throws an expected error when claims challenge is provided with tenant", async function () {
const credential = new AzureCliCredential();
const claimsChallenge = "fakeClaimChallenge";
const encodedClaims = btoa(claimsChallenge);
const tenantId = "12345678-1234-1234-1234-123456789012";
const scope = "https://service/.default";

Expand All @@ -205,7 +207,7 @@ describe("AzureCliCredential (internal)", function () {
assert.equal(error?.name, "CredentialUnavailableError");
assert.equal(
error?.message,
`${azureCliPublicErrorMessages.claim} az login --claims-challenge ${claimsChallenge} --scope ${scope} --tenant ${tenantId}`,
`${azureCliPublicErrorMessages.claim} az login --claims-challenge ${encodedClaims} --scope ${scope} --tenant ${tenantId}`,
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,21 +154,23 @@ describe("AzureDeveloperCliCredential (internal)", function () {
stdout = '{"token": "token","expiresOn": "1900/01/01T00:00:00Z"}';
stderr = "";
const claimsChallenge = "fakeClaimChallenge";
const encodedClaims = btoa(claimsChallenge);
const scope = "https://service/.default";

const credential = new AzureDeveloperCliCredential();
const actualToken = await credential.getToken(scope, { claims: claimsChallenge });

assert.equal(actualToken!.token, "token");
assert.deepEqual(azdCommands, [
`azd auth token --output json --no-prompt --scope ${scope} --claims ${claimsChallenge}`,
`azd auth token --output json --no-prompt --scope ${scope} --claims ${encodedClaims}`,
]);
});

it("get access token with claims challenge and tenantId", async function () {
stdout = '{"token": "token","expiresOn": "1900/01/01T00:00:00Z"}';
stderr = "";
const claimsChallenge = "fakeClaimChallenge";
const encodedClaims = btoa(claimsChallenge);
const tenantId = "12345678-1234-1234-1234-123456789012";
const scope = "https://service/.default";

Expand All @@ -180,22 +182,23 @@ describe("AzureDeveloperCliCredential (internal)", function () {

assert.equal(actualToken!.token, "token");
assert.deepEqual(azdCommands, [
`azd auth token --output json --no-prompt --scope ${scope} --tenant-id ${tenantId} --claims ${claimsChallenge}`,
`azd auth token --output json --no-prompt --scope ${scope} --tenant-id ${tenantId} --claims ${encodedClaims}`,
]);
});

it("get access token with claims challenge and multiple scopes", async function () {
stdout = '{"token": "token","expiresOn": "1900/01/01T00:00:00Z"}';
stderr = "";
const claimsChallenge = "fakeClaimChallenge";
const encodedClaims = btoa(claimsChallenge);
const scopes = ["https://service/.default", "https://management.azure.com/.default"];

const credential = new AzureDeveloperCliCredential();
const actualToken = await credential.getToken(scopes, { claims: claimsChallenge });

assert.equal(actualToken!.token, "token");
assert.deepEqual(azdCommands, [
`azd auth token --output json --no-prompt --scope ${scopes[0]} --scope ${scopes[1]} --claims ${claimsChallenge}`,
`azd auth token --output json --no-prompt --scope ${scopes[0]} --scope ${scopes[1]} --claims ${encodedClaims}`,
]);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ describe("AzurePowerShellCredential", function () {
it("throws an expected error when claims challenge is provided", async function () {
const credential = new AzurePowerShellCredential();
const claimsChallenge = "urn:microsoft:req1";
const encodedClaims = btoa(claimsChallenge);

let error: Error | null = null;
try {
Expand All @@ -98,13 +99,14 @@ describe("AzurePowerShellCredential", function () {
assert.equal(error?.name, "CredentialUnavailableError");
assert.equal(
error?.message,
`${powerShellPublicErrorMessages.claim} Connect-AzAccount -ClaimsChallenge ${claimsChallenge}`,
`${powerShellPublicErrorMessages.claim} Connect-AzAccount -ClaimsChallenge ${encodedClaims}`,
);
});

it("throws an expected error when claims challenge is provided with tenant", async function () {
const credential = new AzurePowerShellCredential();
const claimsChallenge = "urn:microsoft:req1";
const encodedClaims = btoa(claimsChallenge);
const tenantId = "12345678-1234-1234-1234-123456789012";

let error: Error | null = null;
Expand All @@ -121,7 +123,7 @@ describe("AzurePowerShellCredential", function () {
assert.equal(error?.name, "CredentialUnavailableError");
assert.equal(
error?.message,
`${powerShellPublicErrorMessages.claim} Connect-AzAccount -ClaimsChallenge ${claimsChallenge} -Tenant ${tenantId}`,
`${powerShellPublicErrorMessages.claim} Connect-AzAccount -ClaimsChallenge ${encodedClaims} -Tenant ${tenantId}`,
);
});

Expand Down