Skip to content

Commit ae13ec2

Browse files
authored
Fix ConfidentialClient's AcquireTokenSilent and AcquireTokenOnBehalfOf claims logic (#43513)
1 parent 1835c8f commit ae13ec2

File tree

9 files changed

+112
-15
lines changed

9 files changed

+112
-15
lines changed

sdk/identity/Azure.Identity/CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Release History
22

3-
## 1.12.0-beta.1 (2024-04-17)
3+
## 1.11.2 (2024-04-19)
44

5-
### Other Changes
6-
- An experimental overload `Authenticate` method on `InteractiveBrowserCredential` now supports the experimental `PopTokenRequestContext` parameter.
5+
### Bugs Fixed
6+
- Fixed an issue which caused claims to be incorrectly added to confidential client credentials such as `DeviceCodeCredential` [#43468](https://github.com/Azure/azure-sdk-for-net/issues/43468)
77

88
## 1.11.1 (2024-04-16)
99

sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,23 +261,19 @@ public static partial class IdentityModelFactory
261261
public static Azure.Identity.AuthenticationRecord AuthenticationRecord(string username, string authority, string homeAccountId, string tenantId, string clientId) { throw null; }
262262
public static Azure.Identity.DeviceCodeInfo DeviceCodeInfo(string userCode, string deviceCode, System.Uri verificationUri, System.DateTimeOffset expiresOn, string message, string clientId, System.Collections.Generic.IReadOnlyCollection<string> scopes) { throw null; }
263263
}
264-
public partial class InteractiveBrowserCredential : Azure.Core.TokenCredential, Azure.Core.ISupportsProofOfPossession
264+
public partial class InteractiveBrowserCredential : Azure.Core.TokenCredential
265265
{
266266
public InteractiveBrowserCredential() { }
267267
public InteractiveBrowserCredential(Azure.Identity.InteractiveBrowserCredentialOptions options) { }
268268
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
269269
public InteractiveBrowserCredential(string clientId) { }
270270
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
271271
public InteractiveBrowserCredential(string tenantId, string clientId, Azure.Identity.TokenCredentialOptions options = null) { }
272-
public virtual Azure.Identity.AuthenticationRecord Authenticate(Azure.Core.PopTokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
273272
public virtual Azure.Identity.AuthenticationRecord Authenticate(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
274273
public virtual Azure.Identity.AuthenticationRecord Authenticate(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
275-
public virtual System.Threading.Tasks.Task<Azure.Identity.AuthenticationRecord> AuthenticateAsync(Azure.Core.PopTokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
276274
public virtual System.Threading.Tasks.Task<Azure.Identity.AuthenticationRecord> AuthenticateAsync(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
277275
public virtual System.Threading.Tasks.Task<Azure.Identity.AuthenticationRecord> AuthenticateAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
278-
public Azure.Core.AccessToken GetToken(Azure.Core.PopTokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
279276
public override Azure.Core.AccessToken GetToken(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
280-
public System.Threading.Tasks.ValueTask<Azure.Core.AccessToken> GetTokenAsync(Azure.Core.PopTokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
281277
public override System.Threading.Tasks.ValueTask<Azure.Core.AccessToken> GetTokenAsync(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
282278
}
283279
public partial class InteractiveBrowserCredentialOptions : Azure.Identity.TokenCredentialOptions

sdk/identity/Azure.Identity/src/Azure.Identity.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<Description>This is the implementation of the Azure SDK Client Library for Azure Identity</Description>
44
<AssemblyTitle>Microsoft Azure.Identity Component</AssemblyTitle>
5-
<Version>1.12.0-beta.1</Version>
5+
<Version>1.11.2</Version>
66
<!--The ApiCompatVersion is managed automatically and should not generally be modified manually.-->
77
<ApiCompatVersion>1.11.1</ApiCompatVersion>
88
<PackageTags>Microsoft Azure Identity;$(PackageCommonTags)</PackageTags>

sdk/identity/Azure.Identity/src/Credentials/AuthorizationCodeCredential.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,15 @@ private async ValueTask<AccessToken> GetTokenImplAsync(bool async, TokenRequestC
181181
private async Task<AccessToken> AcquireTokenWithCode(bool async, TokenRequestContext requestContext, AccessToken token, string tenantId, CancellationToken cancellationToken)
182182
{
183183
AuthenticationResult result = await Client
184-
.AcquireTokenByAuthorizationCodeAsync(requestContext.Scopes, _authCode, tenantId, _redirectUri, requestContext.Claims, requestContext.IsCaeEnabled, async, cancellationToken)
184+
.AcquireTokenByAuthorizationCodeAsync(
185+
scopes: requestContext.Scopes,
186+
code: _authCode,
187+
tenantId: tenantId,
188+
redirectUri: _redirectUri,
189+
claims: requestContext.Claims,
190+
enableCae: requestContext.IsCaeEnabled,
191+
async,
192+
cancellationToken)
185193
.ConfigureAwait(false);
186194
_record = new AuthenticationRecord(result, _clientId);
187195
token = new AccessToken(result.AccessToken, result.ExpiresOn);

sdk/identity/Azure.Identity/src/MsalConfidentialClient.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public virtual async ValueTask<AuthenticationResult> AcquireTokenSilentCoreAsync
220220
};
221221
builder.WithTenantIdFromAuthority(uriBuilder.Uri);
222222
}
223-
if (string.IsNullOrEmpty(claims))
223+
if (!string.IsNullOrEmpty(claims))
224224
{
225225
builder.WithClaims(claims);
226226
}
@@ -239,7 +239,7 @@ public virtual async ValueTask<AuthenticationResult> AcquireTokenByAuthorization
239239
bool async,
240240
CancellationToken cancellationToken)
241241
{
242-
var result = await AcquireTokenByAuthorizationCodeCoreAsync(scopes, code, tenantId, redirectUri, claims, enableCae, async, cancellationToken).ConfigureAwait(false);
242+
var result = await AcquireTokenByAuthorizationCodeCoreAsync(scopes: scopes, code: code, tenantId: tenantId, redirectUri: redirectUri, claims: claims, enableCae: enableCae, async, cancellationToken).ConfigureAwait(false);
243243
LogAccountDetails(result);
244244
return result;
245245
}
@@ -248,8 +248,8 @@ public virtual async ValueTask<AuthenticationResult> AcquireTokenByAuthorization
248248
string[] scopes,
249249
string code,
250250
string tenantId,
251-
string claims,
252251
string redirectUri,
252+
string claims,
253253
bool enableCae,
254254
bool async,
255255
CancellationToken cancellationToken)
@@ -312,6 +312,10 @@ public virtual async ValueTask<AuthenticationResult> AcquireTokenOnBehalfOfCoreA
312312
};
313313
builder.WithTenantIdFromAuthority(uriBuilder.Uri);
314314
}
315+
if (!string.IsNullOrEmpty(claims))
316+
{
317+
builder.WithClaims(claims);
318+
}
315319
return await builder
316320
.ExecuteAsync(async, cancellationToken)
317321
.ConfigureAwait(false);

sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public override TokenCredential GetTokenCredential(CommonCredentialTestConfig co
3333
DisableInstanceDiscovery = config.DisableInstanceDiscovery,
3434
AdditionallyAllowedTenants = config.AdditionallyAllowedTenants,
3535
IsUnsafeSupportLoggingEnabled = config.IsUnsafeSupportLoggingEnabled,
36+
RedirectUri = config.RedirectUri,
3637
};
3738
if (config.Transport != null)
3839
{

sdk/identity/Azure.Identity/tests/Azure.Identity.Tests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
<PackageReference Include="Azure.Storage.Blobs" />
1717
</ItemGroup>
1818
<!-- Remove before shipping GA -->
19-
<PropertyGroup>
19+
<!-- <PropertyGroup>
2020
<DefineConstants>PREVIEW_FEATURE_FLAG</DefineConstants>
21-
</PropertyGroup>
21+
</PropertyGroup> -->
2222
<!-- End remove before shipping GA -->
2323
<ItemGroup>
2424
<ProjectReference Include="$(AzureCoreTestFramework)" />

sdk/identity/Azure.Identity/tests/CredentialTestBase.cs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,92 @@ public async Task EnableCae()
355355
Assert.True(observedNoCae);
356356
}
357357

358+
[Test]
359+
public async Task ClaimsSetCorrectlyOnRequest()
360+
{
361+
// Configure the transport
362+
var token = Guid.NewGuid().ToString();
363+
var idToken = CredentialTestHelpers.CreateMsalIdToken(Guid.NewGuid().ToString(), "userName", TenantId);
364+
bool calledDiscoveryEndpoint = false;
365+
bool isPubClient = false;
366+
const string Claims = "myClaims";
367+
368+
var mockTransport = new MockTransport(req =>
369+
{
370+
calledDiscoveryEndpoint |= req.Uri.Path.Contains("discovery/instance");
371+
372+
MockResponse response = new(200);
373+
if (req.Uri.Path.EndsWith("/devicecode"))
374+
{
375+
response = CredentialTestHelpers.CreateMockMsalDeviceCodeResponse();
376+
}
377+
else if (req.Uri.Path.Contains("/userrealm/"))
378+
{
379+
response.SetContent(UserrealmResponse);
380+
}
381+
else
382+
{
383+
if (isPubClient || typeof(TCredOptions) == typeof(AuthorizationCodeCredentialOptions) || typeof(TCredOptions) == typeof(OnBehalfOfCredentialOptions))
384+
{
385+
response = CredentialTestHelpers.CreateMockMsalTokenResponse(200, token, TenantId, ExpectedUsername, ObjectId);
386+
}
387+
else
388+
{
389+
response.SetContent($"{{\"token_type\": \"Bearer\",\"expires_in\": 9999,\"ext_expires_in\": 9999,\"access_token\": \"{token}\" }}");
390+
}
391+
if (req.Content != null)
392+
{
393+
var stream = new MemoryStream();
394+
req.Content.WriteTo(stream, default);
395+
var content = new BinaryData(stream.ToArray()).ToString();
396+
var queryString = Uri.UnescapeDataString(content)
397+
.Split('&')
398+
.Select(q => q.Split('='))
399+
.ToDictionary(kvp => kvp[0], kvp => kvp[1]);
400+
bool containsClaims = queryString.TryGetValue("claims", out var claimsJson);
401+
402+
if (req.ClientRequestId == "NoClaims")
403+
{
404+
Assert.False(containsClaims, "(NoClaims) Claims should not be present. Claims=" + claimsJson);
405+
}
406+
if (req.ClientRequestId == "WithClaims")
407+
{
408+
Assert.True(containsClaims, "(WithClaims) Claims should be present");
409+
Assert.AreEqual(Claims, claimsJson, "(WithClaims) Claims should match");
410+
}
411+
}
412+
}
413+
414+
return response;
415+
});
416+
417+
var config = new CommonCredentialTestConfig()
418+
{
419+
Transport = mockTransport,
420+
TenantId = TenantId,
421+
RedirectUri = new Uri("http://localhost:8400/")
422+
};
423+
var credential = GetTokenCredential(config);
424+
if (!CredentialTestHelpers.IsMsalCredential(credential))
425+
{
426+
Assert.Ignore("EnableCAE tests do not apply to the non-MSAL credentials.");
427+
}
428+
isPubClient = CredentialTestHelpers.IsCredentialTypePubClient(credential);
429+
430+
using (HttpPipeline.CreateClientRequestIdScope("NoClaims"))
431+
{
432+
// First call to populate the account record for confidential client creds
433+
await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default), default);
434+
var actualToken = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Alternate), default);
435+
Assert.AreEqual(token, actualToken.Token);
436+
}
437+
using (HttpPipeline.CreateClientRequestIdScope("WithClaims"))
438+
{
439+
var actualToken = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Alternate2, claims: Claims), default);
440+
Assert.AreEqual(token, actualToken.Token);
441+
}
442+
}
443+
358444
[Test]
359445
public async Task TokenRequestContextClaimsPassedToMSAL()
360446
{
@@ -624,6 +710,7 @@ public class CommonCredentialTestConfig : TokenCredentialOptions, ISupportsAddit
624710
public TokenRequestContext RequestContext { get; set; }
625711
public string TenantId { get; set; }
626712
public IList<string> AdditionallyAllowedTenants { get; set; } = new List<string>();
713+
public Uri RedirectUri { get; set; }
627714
internal TenantIdResolverBase TestTentantIdResolver { get; set; }
628715
internal MockMsalConfidentialClient MockConfidentialMsalClient { get; set; }
629716
internal MockMsalPublicClient MockPublicMsalClient { get; set; }

sdk/identity/Azure.Identity/tests/Mock/MockScopes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ private MockScopes(string[] scopes)
1919
public static MockScopes Default = new MockScopes(new string[] { "https://default.mock.auth.scope/.default" });
2020

2121
public static MockScopes Alternate = new MockScopes(new string[] { "https://alternate.mock.auth.scope/.default" });
22+
public static MockScopes Alternate2 = new MockScopes(new string[] { "https://alternate2.mock.auth.scope/.default" });
2223

2324
public override string ToString()
2425
{

0 commit comments

Comments
 (0)