Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Adding support for Service principasl on OBO #2506

Merged
merged 4 commits into from
Mar 25, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ protected override async Task<AuthenticationResult> ExecuteAsync(CancellationTok
private async Task<AuthenticationResult> 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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ protected async Task<AuthenticationResult> CacheTokenResponseAndCreateAuthentica

if (!AuthenticationRequestParameters.IsClientCredentialRequest &&
AuthenticationRequestParameters.ApiId != ApiEvent.ApiIds.AcquireTokenByRefreshToken &&
AuthenticationRequestParameters.AuthorityInfo.AuthorityType != AuthorityType.Adfs)
AuthenticationRequestParameters.AuthorityInfo.AuthorityType != AuthorityType.Adfs &&
!(msalTokenResponse.ClientInfo is null))
trwalke marked this conversation as resolved.
Show resolved Hide resolved
{
//client_info is not returned from client credential flows because there is no user present.
fromServer = ClientInfo.CreateFromJson(msalTokenResponse.ClientInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -50,6 +51,11 @@ 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://#.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 OBOServiceDownStreamApiPpeClientID = "23d08a1e-1249-4f7c-b5a5-cb11f29b6923";
private const string PPEAuthenticationAuthority = "https://#.windows-ppe.net/f686d426-8d16-42db-81b7-ab578e110ccd";

private const string PublicCloudHost = "https://#.microsoftonline.com/";
private const string ArlingtonCloudHost = "https://#.microsoftonline.us/";
Expand Down Expand Up @@ -693,6 +699,54 @@ 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<string> scopes = new List<string>() { OBOServicePpeClientID + "/.default" };
IReadOnlyList<string> scopes2 = new List<string>() { OBOServiceDownStreamApiPpeClientID + "/.default" };

var confidentialSPApp = ConfidentialClientApplicationBuilder
.Create(OBOClientPpeClientID)
.WithAuthority(PPEAuthenticationAuthority)
.WithCertificate(cert)
.WithTestLogging()
.Build();

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)
.WithCertificate(cert)
.Build();

var userCacheRecorder = _confidentialApp.UserTokenCache.RecordAccess();

authenticationResult = await _confidentialApp.AcquireTokenOnBehalfOf(scopes2, userAssertion)
.WithAuthority(PPEAuthenticationAuthority)
.ExecuteAsync().ConfigureAwait(false);

Assert.IsNotNull(authenticationResult);
Assert.IsNotNull(authenticationResult.AccessToken);
Assert.AreEqual(TokenSource.IdentityProvider, authenticationResult.AuthenticationResultMetadata.TokenSource);

authenticationResult = await _confidentialApp.AcquireTokenOnBehalfOf(scopes2, userAssertion)
.WithAuthority(PPEAuthenticationAuthority)
.ExecuteAsync().ConfigureAwait(false);

Assert.IsNotNull(authenticationResult);
trwalke marked this conversation as resolved.
Show resolved Hide resolved
Assert.IsNotNull(authenticationResult.AccessToken);
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(
string issuer, // client ID
string audience, // ${authority}/oauth2/v2.0/token for AAD or ${authority}/oauth2/token for ADFS
Expand Down