From 230b4c830ccaa3c413af6084ff2802fd1a27dc90 Mon Sep 17 00:00:00 2001 From: trwalke Date: Wed, 24 Mar 2021 16:46:52 -0700 Subject: [PATCH 1/4] Adding support for Service principasl on OBO --- .../Internal/Requests/RequestBase.cs | 3 +- .../ConfidentialClientIntegrationTests.cs | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index b9cae238b0..7ff90d3c48 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -190,7 +190,8 @@ protected async Task CacheTokenResponseAndCreateAuthentica if (!AuthenticationRequestParameters.IsClientCredentialRequest && AuthenticationRequestParameters.ApiId != ApiEvent.ApiIds.AcquireTokenByRefreshToken && - AuthenticationRequestParameters.AuthorityInfo.AuthorityType != AuthorityType.Adfs) + AuthenticationRequestParameters.AuthorityInfo.AuthorityType != AuthorityType.Adfs && + !(msalTokenResponse.ClientInfo is null)) { //client_info is not returned from client credential flows because there is no user present. fromServer = ClientInfo.CreateFromJson(msalTokenResponse.ClientInfo); diff --git a/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs b/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs index 56c5f6827d..8fd7f2a93c 100644 --- a/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs +++ b/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs @@ -21,6 +21,7 @@ using Microsoft.Identity.Client; using Microsoft.Identity.Client.Instance; using Microsoft.Identity.Client.Internal; +using Microsoft.Identity.Json.Utilities; using Microsoft.Identity.Test.Common; using Microsoft.Identity.Test.Common.Core.Helpers; using Microsoft.Identity.Test.Integration.Infrastructure; @@ -50,6 +51,8 @@ public class ConfidentialClientIntegrationTests private const string ArlingtonConfidentialClientIDOBO = "c0555d2d-02f2-4838-802e-3463422e571d"; private const string ArlingtonPublicClientIDOBO = "cb7faed4-b8c0-49ee-b421-f5ed16894c83"; private const string ArlingtonAuthority = "https://login.microsoftonline.us/45ff0c17-f8b5-489b-b7fd-2fedebbec0c4"; + private const string OBOClientPpeClientID = "9793041b-9078-4942-b1d2-babdc472cc0c"; + private const string OBOServicePpeClientID = "c84e9c32-0bc9-4a73-af05-9efe9982a322"; private const string PublicCloudHost = "https://login.microsoftonline.com/"; private const string ArlingtonCloudHost = "https://login.microsoftonline.us/"; @@ -693,6 +696,52 @@ public async Task ClientCreds_ClientAssertion_AAD_NoWilson_Async() } } + [TestMethod] + public async Task ClientCreds_ServicePrincipal_OBO_PPE_Async() + { + string aadAuthenticationAuthority = "https://login.windows-ppe.net/f686d426-8d16-42db-81b7-ab578e110ccd"; + X509Certificate2 cert = GetCertificate(); + IReadOnlyList scopes = new List() { OBOServicePpeClientID + "/.default" }; + IReadOnlyList scopes2 = new List() { "23d08a1e-1249-4f7c-b5a5-cb11f29b6923/.default" }; + + var confidentialSPApp = ConfidentialClientApplicationBuilder + .Create(OBOClientPpeClientID) + .WithAuthority(aadAuthenticationAuthority) + .WithCertificate(cert) + .WithTestLogging() + .Build(); + + var authenticationResult = await confidentialSPApp.AcquireTokenForClient(scopes).ExecuteAsync().ConfigureAwait(false); + + string appToken = authenticationResult.AccessToken; + + var _confidentialApp = ConfidentialClientApplicationBuilder + .Create(OBOServicePpeClientID) + .WithCertificate(cert) + .Build(); + + var userCacheRecorder = _confidentialApp.UserTokenCache.RecordAccess(); + var userAssertion = new UserAssertion(appToken); + string atHash = userAssertion.AssertionHash; + + authenticationResult = await _confidentialApp.AcquireTokenOnBehalfOf(scopes2, userAssertion) + .WithAuthority(aadAuthenticationAuthority) + .ExecuteAsync().ConfigureAwait(false); + + Assert.IsNotNull(authenticationResult); + Assert.IsNotNull(authenticationResult.AccessToken); + + authenticationResult = await _confidentialApp.AcquireTokenOnBehalfOf(scopes2, userAssertion) + .WithAuthority(aadAuthenticationAuthority) + .ExecuteAsync().ConfigureAwait(false); + + Assert.IsNotNull(authenticationResult); + Assert.IsNotNull(authenticationResult.AccessToken); + Assert.IsTrue(!userCacheRecorder.LastAfterAccessNotificationArgs.IsApplicationCache); + Assert.IsTrue(userCacheRecorder.LastAfterAccessNotificationArgs.HasTokens); + Assert.AreEqual(atHash, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey); + } + private string GetSignedClientAssertionDirectly( string issuer, // client ID string audience, // ${authority}/oauth2/v2.0/token for AAD or ${authority}/oauth2/token for ADFS From a2f7e0ce7422d7917477e5ebf3e8ce1ee4d8bc09 Mon Sep 17 00:00:00 2001 From: trwalke Date: Wed, 24 Mar 2021 17:08:18 -0700 Subject: [PATCH 2/4] Refactoring --- .../ConfidentialClientIntegrationTests.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs b/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs index 8fd7f2a93c..b800e46160 100644 --- a/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs +++ b/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs @@ -53,6 +53,8 @@ public class ConfidentialClientIntegrationTests private const string ArlingtonAuthority = "https://login.microsoftonline.us/45ff0c17-f8b5-489b-b7fd-2fedebbec0c4"; private const string OBOClientPpeClientID = "9793041b-9078-4942-b1d2-babdc472cc0c"; private const string OBOServicePpeClientID = "c84e9c32-0bc9-4a73-af05-9efe9982a322"; + private const string OBOServiceDownStreamApiClientID = "23d08a1e-1249-4f7c-b5a5-cb11f29b6923"; + private const string PPEAuthenticationAuthority = "https://login.windows-ppe.net/f686d426-8d16-42db-81b7-ab578e110ccd"; private const string PublicCloudHost = "https://login.microsoftonline.com/"; private const string ArlingtonCloudHost = "https://login.microsoftonline.us/"; @@ -699,14 +701,13 @@ public async Task ClientCreds_ClientAssertion_AAD_NoWilson_Async() [TestMethod] public async Task ClientCreds_ServicePrincipal_OBO_PPE_Async() { - string aadAuthenticationAuthority = "https://login.windows-ppe.net/f686d426-8d16-42db-81b7-ab578e110ccd"; X509Certificate2 cert = GetCertificate(); IReadOnlyList scopes = new List() { OBOServicePpeClientID + "/.default" }; - IReadOnlyList scopes2 = new List() { "23d08a1e-1249-4f7c-b5a5-cb11f29b6923/.default" }; + IReadOnlyList scopes2 = new List() { OBOServiceDownStreamApiClientID + "/.default" }; var confidentialSPApp = ConfidentialClientApplicationBuilder .Create(OBOClientPpeClientID) - .WithAuthority(aadAuthenticationAuthority) + .WithAuthority(PPEAuthenticationAuthority) .WithCertificate(cert) .WithTestLogging() .Build(); @@ -714,6 +715,8 @@ public async Task ClientCreds_ServicePrincipal_OBO_PPE_Async() var authenticationResult = await confidentialSPApp.AcquireTokenForClient(scopes).ExecuteAsync().ConfigureAwait(false); string appToken = authenticationResult.AccessToken; + var userAssertion = new UserAssertion(appToken); + string atHash = userAssertion.AssertionHash; var _confidentialApp = ConfidentialClientApplicationBuilder .Create(OBOServicePpeClientID) @@ -721,18 +724,16 @@ public async Task ClientCreds_ServicePrincipal_OBO_PPE_Async() .Build(); var userCacheRecorder = _confidentialApp.UserTokenCache.RecordAccess(); - var userAssertion = new UserAssertion(appToken); - string atHash = userAssertion.AssertionHash; authenticationResult = await _confidentialApp.AcquireTokenOnBehalfOf(scopes2, userAssertion) - .WithAuthority(aadAuthenticationAuthority) + .WithAuthority(PPEAuthenticationAuthority) .ExecuteAsync().ConfigureAwait(false); Assert.IsNotNull(authenticationResult); Assert.IsNotNull(authenticationResult.AccessToken); authenticationResult = await _confidentialApp.AcquireTokenOnBehalfOf(scopes2, userAssertion) - .WithAuthority(aadAuthenticationAuthority) + .WithAuthority(PPEAuthenticationAuthority) .ExecuteAsync().ConfigureAwait(false); Assert.IsNotNull(authenticationResult); From 72210ee623d2d05a9b2d52d7efa83764a531e5f7 Mon Sep 17 00:00:00 2001 From: trwalke Date: Thu, 25 Mar 2021 08:52:09 -0700 Subject: [PATCH 3/4] Adding additional checks in test. logging --- .../Internal/Requests/OnBehalfOfRequest.cs | 7 +++++++ .../HeadlessTests/ConfidentialClientIntegrationTests.cs | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/OnBehalfOfRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/OnBehalfOfRequest.cs index 2b19278547..9192c65dbe 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/OnBehalfOfRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/OnBehalfOfRequest.cs @@ -90,6 +90,13 @@ protected override async Task ExecuteAsync(CancellationTok private async Task FetchNewAccessTokenAsync(CancellationToken cancellationToken) { var msalTokenResponse = await SendTokenRequestAsync(GetBodyParameters(), cancellationToken).ConfigureAwait(false); + if (msalTokenResponse.ClientInfo is null && + AuthenticationRequestParameters.AuthorityInfo.AuthorityType != AuthorityType.Adfs) + { + var logger = AuthenticationRequestParameters.RequestContext.Logger; + logger.Info("This is an on behalf of request for a service principal as no client info returned in the token response."); + } + return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse).ConfigureAwait(false); } diff --git a/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs b/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs index b800e46160..e0c437ae54 100644 --- a/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs +++ b/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs @@ -731,6 +731,7 @@ public async Task ClientCreds_ServicePrincipal_OBO_PPE_Async() Assert.IsNotNull(authenticationResult); Assert.IsNotNull(authenticationResult.AccessToken); + Assert.AreEqual(TokenSource.IdentityProvider, authenticationResult.AuthenticationResultMetadata.TokenSource); authenticationResult = await _confidentialApp.AcquireTokenOnBehalfOf(scopes2, userAssertion) .WithAuthority(PPEAuthenticationAuthority) @@ -741,6 +742,7 @@ public async Task ClientCreds_ServicePrincipal_OBO_PPE_Async() Assert.IsTrue(!userCacheRecorder.LastAfterAccessNotificationArgs.IsApplicationCache); Assert.IsTrue(userCacheRecorder.LastAfterAccessNotificationArgs.HasTokens); Assert.AreEqual(atHash, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey); + Assert.AreEqual(TokenSource.Cache, authenticationResult.AuthenticationResultMetadata.TokenSource); } private string GetSignedClientAssertionDirectly( From 295bb9e7f45842f1065f271ddde6f45ad414ed4e Mon Sep 17 00:00:00 2001 From: trwalke Date: Thu, 25 Mar 2021 08:57:40 -0700 Subject: [PATCH 4/4] Adding comments --- .../HeadlessTests/ConfidentialClientIntegrationTests.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs b/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs index e0c437ae54..562d9ff6ef 100644 --- a/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs +++ b/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ConfidentialClientIntegrationTests.cs @@ -51,9 +51,10 @@ public class ConfidentialClientIntegrationTests private const string ArlingtonConfidentialClientIDOBO = "c0555d2d-02f2-4838-802e-3463422e571d"; private const string ArlingtonPublicClientIDOBO = "cb7faed4-b8c0-49ee-b421-f5ed16894c83"; private const string ArlingtonAuthority = "https://login.microsoftonline.us/45ff0c17-f8b5-489b-b7fd-2fedebbec0c4"; + //The following client ids are for applications that are within PPE private const string OBOClientPpeClientID = "9793041b-9078-4942-b1d2-babdc472cc0c"; private const string OBOServicePpeClientID = "c84e9c32-0bc9-4a73-af05-9efe9982a322"; - private const string OBOServiceDownStreamApiClientID = "23d08a1e-1249-4f7c-b5a5-cb11f29b6923"; + private const string OBOServiceDownStreamApiPpeClientID = "23d08a1e-1249-4f7c-b5a5-cb11f29b6923"; private const string PPEAuthenticationAuthority = "https://login.windows-ppe.net/f686d426-8d16-42db-81b7-ab578e110ccd"; private const string PublicCloudHost = "https://login.microsoftonline.com/"; @@ -701,9 +702,10 @@ public async Task ClientCreds_ClientAssertion_AAD_NoWilson_Async() [TestMethod] public async Task ClientCreds_ServicePrincipal_OBO_PPE_Async() { + //An explination of the OBO for service principal scenario can be found here https://aadwiki.windows-int.net/index.php?title=App_OBO_aka._Service_Principal_OBO X509Certificate2 cert = GetCertificate(); IReadOnlyList scopes = new List() { OBOServicePpeClientID + "/.default" }; - IReadOnlyList scopes2 = new List() { OBOServiceDownStreamApiClientID + "/.default" }; + IReadOnlyList scopes2 = new List() { OBOServiceDownStreamApiPpeClientID + "/.default" }; var confidentialSPApp = ConfidentialClientApplicationBuilder .Create(OBOClientPpeClientID)