Skip to content

Commit cac074c

Browse files
trwalketrwalkebgavrilMS
authored
Update MSAL to send "fmi-bearer" client assertion type based on the client id (#5160)
* Update MSAL to send "fmi-bearer" client assertion type based on the provided client id * Removing extra logic * Fix for client assertion. * Add test --------- Co-authored-by: trwalke <[email protected]> Co-authored-by: Bogdan Gavril <[email protected]>
1 parent 2d67d95 commit cac074c

File tree

5 files changed

+99
-8
lines changed

5 files changed

+99
-8
lines changed

src/client/Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ public Task AddConfidentialClientParametersAsync(
6565

6666
string assertion = jwtToken.Sign(Certificate, requestParameters.SendX5C, useSha2);
6767

68-
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertionType, OAuth2AssertionType.JwtBearer);
68+
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertionType,
69+
string.Equals(clientId, Constants.FmiUrnClientId) ?
70+
OAuth2AssertionType.FmiBearer : OAuth2AssertionType.JwtBearer);
6971
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertion, assertion);
7072
}
7173
else

src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionDelegateClientCredential.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,14 @@ public async Task AddConfidentialClientParametersAsync(
3838
string tokenEndpoint,
3939
CancellationToken cancellationToken)
4040
{
41+
string assertionType = requestParameters.AppConfig.ClientId == Constants.FmiUrnClientId ?
42+
OAuth2AssertionType.FmiBearer :
43+
OAuth2AssertionType.JwtBearer;
44+
string signedAssertion;
4145
if (_signedAssertionDelegate != null)
4246
{
4347
// If no "AssertionRequestOptions" delegate is supplied
44-
string signedAssertion = await _signedAssertionDelegate(cancellationToken).ConfigureAwait(false);
45-
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertionType, OAuth2AssertionType.JwtBearer);
46-
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertion, signedAssertion);
48+
signedAssertion = await _signedAssertionDelegate(cancellationToken).ConfigureAwait(false);
4749
}
4850
else
4951
{
@@ -66,13 +68,14 @@ public async Task AddConfidentialClientParametersAsync(
6668

6769
//Set claims
6870
assertionOptions.Claims = requestParameters.Claims;
69-
71+
7072
// Delegate that uses AssertionRequestOptions
71-
string signedAssertion = await _signedAssertionWithInfoDelegate(assertionOptions).ConfigureAwait(false);
73+
signedAssertion = await _signedAssertionWithInfoDelegate(assertionOptions).ConfigureAwait(false);
7274

73-
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertionType, OAuth2AssertionType.JwtBearer);
74-
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertion, signedAssertion);
7575
}
76+
77+
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertionType, assertionType);
78+
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertion, signedAssertion);
7679
}
7780
}
7881
}

src/client/Microsoft.Identity.Client/Internal/Constants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ internal static class Constants
5353
public const int CallerSdkIdMaxLength = 10;
5454
public const int CallerSdkVersionMaxLength = 20;
5555

56+
public const string FmiUrnClientId = "urn:microsoft:identity:fmi";
57+
5658
public static string FormatEnterpriseRegistrationOnPremiseUri(string domain)
5759
{
5860
return $"https://enterpriseregistration.{domain}/enrollmentserver/contract";

src/client/Microsoft.Identity.Client/OAuth2/OAuthConstants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ internal static class OAuth2ResponseType
6666
internal static class OAuth2AssertionType
6767
{
6868
public const string JwtBearer = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
69+
public const string FmiBearer = "urn:ietf:params:oauth:client-assertion-type:fmi-bearer";
6970
}
7071

7172
internal static class OAuth2RequestedTokenUse

tests/Microsoft.Identity.Test.Unit/PublicApiTests/FmiTests.cs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,88 @@ public async Task FmiEnsureWithFmiPathFunctions()
5858
Assert.AreEqual(fmiClientId, app.AppTokenCacheInternal.Accessor.GetAllAccessTokens().First().ClientId);
5959
}
6060
}
61+
62+
[TestMethod]
63+
[DataRow("urn:microsoft:identity:fmi", "Cert")]
64+
[DataRow("urn:microsoft:identity:fmi", "SignedAssertionDelegate")]
65+
[DataRow(TestConstants.ClientId, "Cert")]
66+
[DataRow(TestConstants.ClientId, "SignedAssertionDelegate")]
67+
[DataRow("urn:microsoft:identity:fmi", "SignedAssertionDelegate2")]
68+
[DataRow(TestConstants.ClientId, "SignedAssertionDelegate2")]
69+
public async Task FmiEnsureWithFmiPathUsesCorrectClientAssertion(string clientId, string clientAssertionType)
70+
{
71+
using (var httpManager = new MockHttpManager())
72+
{
73+
var jwtClientAssertionType = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
74+
var fmiClientAssertionType = "urn:ietf:params:oauth:client-assertion-type:fmi-bearer";
75+
76+
var certificate = CertHelper.GetOrCreateTestCert();
77+
var builder = ConfidentialClientApplicationBuilder.Create(clientId)
78+
.WithAuthority(new Uri(ClientApplicationBase.DefaultAuthority), true)
79+
.WithRedirectUri(TestConstants.RedirectUri);
80+
81+
switch (clientAssertionType)
82+
{
83+
case "Cert":
84+
builder.WithCertificate(certificate);
85+
break;
86+
case "SignedAssertionDelegate":
87+
builder.WithClientAssertion(() => { return TestConstants.DefaultClientAssertion; });
88+
break;
89+
case "SignedAssertionDelegate2":
90+
Func<AssertionRequestOptions, Task<string>> func = (AssertionRequestOptions options) =>
91+
{
92+
return Task.FromResult(TestConstants.DefaultClientAssertion);
93+
};
94+
builder.WithClientAssertion(func);
95+
break;
96+
default:
97+
throw new NotImplementedException();
98+
}
99+
100+
var app = builder.WithHttpManager(httpManager)
101+
.WithExperimentalFeatures()
102+
.BuildConcrete();
103+
104+
var appCacheAccess = app.AppTokenCache.RecordAccess();
105+
106+
httpManager.AddInstanceDiscoveryMockHandler();
107+
108+
if (string.Equals(clientId, TestConstants.ClientId))
109+
{
110+
httpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(
111+
expectedPostData: new Dictionary<string, string>()
112+
{ { OAuth2Parameter.ClientAssertionType, jwtClientAssertionType } });
113+
}
114+
else
115+
{
116+
httpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(
117+
expectedPostData: new Dictionary<string, string>()
118+
{ { OAuth2Parameter.ClientAssertionType, fmiClientAssertionType } });
119+
}
120+
121+
//Ensure that the FMI path is set correctly and token is retrieved
122+
var result = await app.AcquireTokenForClient(TestConstants.s_scope.ToArray())
123+
.WithFmiPath("fmiPath")
124+
.ExecuteAsync(CancellationToken.None)
125+
.ConfigureAwait(false);
126+
127+
Assert.IsNotNull(result);
128+
Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
129+
Assert.AreEqual("header.payload.signature", result.AccessToken);
130+
Assert.AreEqual(clientId, app.AppTokenCacheInternal.Accessor.GetAllAccessTokens().First().ClientId);
131+
132+
//Assert token can be acquired from cache
133+
result = await app.AcquireTokenForClient(TestConstants.s_scope.ToArray())
134+
.WithFmiPath("fmiPath")
135+
.ExecuteAsync(CancellationToken.None)
136+
.ConfigureAwait(false);
137+
138+
Assert.IsNotNull(result);
139+
Assert.AreEqual(TokenSource.Cache, result.AuthenticationResultMetadata.TokenSource);
140+
Assert.AreEqual("header.payload.signature", result.AccessToken);
141+
Assert.AreEqual(clientId, app.AppTokenCacheInternal.Accessor.GetAllAccessTokens().First().ClientId);
142+
}
143+
}
61144
}
62145
}

0 commit comments

Comments
 (0)