Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add API and make ROPC call #3103

Merged
merged 25 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
424d0c4
Add API and make ROPC call
neha-bhargava Oct 23, 2024
bebff84
Add silent call before attempting ROPC
neha-bhargava Oct 23, 2024
ad6d4f6
Minor updates to constants and comments
neha-bhargava Oct 23, 2024
3c20f84
Undo changes to txt files
neha-bhargava Oct 23, 2024
129d159
Merge branch 'master' into nebharg/ROPC
neha-bhargava Oct 23, 2024
a96b9d8
Address comments
neha-bhargava Oct 24, 2024
bd67c49
Merge branch 'nebharg/ROPC' of https://github.com/AzureAD/microsoft-i…
neha-bhargava Oct 24, 2024
ca9d3b6
Merge branch 'master' into nebharg/ROPC
neha-bhargava Oct 24, 2024
584929e
Merge branch 'master' into nebharg/ROPC
neha-bhargava Oct 25, 2024
5129cf1
Exclude file ClaimsConstant as it contains the constant Password
neha-bhargava Oct 25, 2024
5f5768f
Merge branch 'nebharg/ROPC' of https://github.com/AzureAD/microsoft-i…
neha-bhargava Oct 25, 2024
e1a88c3
Merge branch 'master' into nebharg/ROPC
neha-bhargava Oct 28, 2024
229b190
Address comments - Add logging and update constants
neha-bhargava Oct 28, 2024
7c80db3
Resolve warnings
neha-bhargava Oct 28, 2024
42ad9a8
Alternative to GetAccounts
neha-bhargava Oct 29, 2024
8860671
Update to only add the claim if not already present
neha-bhargava Oct 29, 2024
9a0df38
Update to use existing constants
neha-bhargava Oct 29, 2024
f7407e8
Add check before setting
neha-bhargava Oct 29, 2024
85eb1a4
Add comment to the method
neha-bhargava Oct 30, 2024
efba561
Merge branch 'master' into nebharg/ROPC
jmprieur Oct 31, 2024
fe4e4d5
fixing warnings
jmprieur Oct 31, 2024
913fdf9
Merge branch 'nebharg/ROPC' of http://github.com/AzureAD/microsoft-id…
jmprieur Oct 31, 2024
f4dfc21
Merge branch 'master' into nebharg/ROPC
neha-bhargava Nov 1, 2024
9c5b14d
Merge branch 'master' into nebharg/ROPC
neha-bhargava Nov 4, 2024
7a93b94
Address comments
neha-bhargava Nov 4, 2024
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
10 changes: 10 additions & 0 deletions src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,15 @@
/// Name Identifier ID claim: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier".
/// </summary>
public const string NameIdentifierId = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";

/// <summary>
/// Username claims for ROPC flow.
/// </summary>
public const string Username = "xms-username";

Check warning on line 106 in src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs

View workflow job for this annotation

GitHub Actions / Analyse

Check warning on line 106 in src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs

View workflow job for this annotation

GitHub Actions / Build and run unit tests

Check warning on line 106 in src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs

View workflow job for this annotation

GitHub Actions / Build and run unit tests

Check warning on line 106 in src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs

View workflow job for this annotation

GitHub Actions / Build and run unit tests

neha-bhargava marked this conversation as resolved.
Show resolved Hide resolved
neha-bhargava marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Password claims for ROPC flow.
/// </summary>
public const string Password = "xms-password";

Check warning on line 111 in src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs

View workflow job for this annotation

GitHub Actions / Analyse

Check warning on line 111 in src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs

View workflow job for this annotation

GitHub Actions / Build and run unit tests

Check warning on line 111 in src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs

View workflow job for this annotation

GitHub Actions / Build and run unit tests

Check warning on line 111 in src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs

View workflow job for this annotation

GitHub Actions / Build and run unit tests

neha-bhargava marked this conversation as resolved.
Show resolved Hide resolved
}
}
56 changes: 56 additions & 0 deletions src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,21 @@
try
{
AuthenticationResult? authenticationResult;

// If the user is not null and has claims xms-username and xms-password, perform ROPC for CCA
authenticationResult = await GetAuthenticationResultForConfidentialClientUsingRopcAsync(
neha-bhargava marked this conversation as resolved.
Show resolved Hide resolved
application,
scopes,
user,
mergedOptions,
tokenAcquisitionOptions).ConfigureAwait(false);

if (authenticationResult != null)
{
LogAuthResult(authenticationResult);
return authenticationResult;
}

// Access token will return if call is from a web API
authenticationResult = await GetAuthenticationResultForWebApiToCallDownstreamApiAsync(
application,
Expand Down Expand Up @@ -310,6 +325,47 @@
}
}

private async Task<AuthenticationResult?> GetAuthenticationResultForConfidentialClientUsingRopcAsync(IConfidentialClientApplication application, IEnumerable<string> scopes, ClaimsPrincipal? user, MergedOptions mergedOptions, TokenAcquisitionOptions? tokenAcquisitionOptions)
{
if (user != null && user.HasClaim(c => c.Type == ClaimConstants.Username) && user.HasClaim(c => c.Type == ClaimConstants.Password))
{
string username = user.FindFirst(ClaimConstants.Username)?.Value ?? string.Empty;
string password = user.FindFirst(ClaimConstants.Password)?.Value ?? string.Empty;

var accounts = await application.GetAccountsAsync().ConfigureAwait(false);

Check warning on line 335 in src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs

View workflow job for this annotation

GitHub Actions / Analyse

'IConfidentialClientApplication.GetAccountsAsync()' is obsolete: 'Use GetAccountAsync(identifier) in web apps and web APIs, and use a token cache serializer for better security and performance. See https://aka.ms/msal-net-cca-token-cache-serialization.'

Check warning on line 335 in src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs

View workflow job for this annotation

GitHub Actions / Build and run unit tests

'IConfidentialClientApplication.GetAccountsAsync()' is obsolete: 'Use GetAccountAsync(identifier) in web apps and web APIs, and use a token cache serializer for better security and performance. See https://aka.ms/msal-net-cca-token-cache-serialization.'

Check warning on line 335 in src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs

View workflow job for this annotation

GitHub Actions / Build and run unit tests

'IConfidentialClientApplication.GetAccountsAsync()' is obsolete: 'Use GetAccountAsync(identifier) in web apps and web APIs, and use a token cache serializer for better security and performance. See https://aka.ms/msal-net-cca-token-cache-serialization.'

Check warning on line 335 in src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs

View workflow job for this annotation

GitHub Actions / Build and run unit tests

'IConfidentialClientApplication.GetAccountsAsync()' is obsolete: 'Use GetAccountAsync(identifier) in web apps and web APIs, and use a token cache serializer for better security and performance. See https://aka.ms/msal-net-cca-token-cache-serialization.'

Check warning on line 335 in src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs

View workflow job for this annotation

GitHub Actions / Build and run unit tests

'IConfidentialClientApplication.GetAccountsAsync()' is obsolete: 'Use GetAccountAsync(identifier) in web apps and web APIs, and use a token cache serializer for better security and performance. See https://aka.ms/msal-net-cca-token-cache-serialization.'
jmprieur marked this conversation as resolved.
Show resolved Hide resolved
var account = accounts.FirstOrDefault(account => account.Username == username);
neha-bhargava marked this conversation as resolved.
Show resolved Hide resolved

if (account != null)
{
try
{
// Silent flow
return await application.AcquireTokenSilent(
scopes.Except(_scopesRequestedByMsal),
account)
.ExecuteAsync()
.ConfigureAwait(false);
}
catch
{
// Ignore this exception as we will retry with ROPC
neha-bhargava marked this conversation as resolved.
Show resolved Hide resolved
}
}

// ROPC flow
var authenticationResult = await ((IByUsernameAndPassword)application).AcquireTokenByUsernamePassword(
neha-bhargava marked this conversation as resolved.
Show resolved Hide resolved
scopes.Except(_scopesRequestedByMsal),
username,
password)
.ExecuteAsync()
.ConfigureAwait(false);

return authenticationResult;
}

return null;
}

private void LogAuthResult(AuthenticationResult? authenticationResult)
{
if (authenticationResult != null)
Expand Down
31 changes: 31 additions & 0 deletions src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,36 @@
}));
}
}

/// <summary>
/// Instantiate a <see cref="ClaimsPrincipal"/> from a username and password.
/// This can be used for ROPC flow for testing purposes.
/// </summary>
/// <param name="username">UPN of the user for example username@domain.</param>
/// <param name="password">Password for the user.</param>
/// <returns>A <see cref="ClaimsPrincipal"/> containing these two claims.</returns>
public static ClaimsPrincipal FromUsernamePassword(string username, string password)

Check warning on line 118 in src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs

View workflow job for this annotation

GitHub Actions / Analyse

Symbol 'FromUsernamePassword' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)
{
if (AppContextSwitches.UseClaimsIdentityType)
neha-bhargava marked this conversation as resolved.
Show resolved Hide resolved
{
#pragma warning disable RS0030 // Do not use banned APIs
return new ClaimsPrincipal(
new ClaimsIdentity(new[]
neha-bhargava marked this conversation as resolved.
Show resolved Hide resolved
{
new Claim(ClaimConstants.Username, username),
new Claim(ClaimConstants.Password, password),
}));
#pragma warning restore RS0030 // Do not use banned APIs
}
else
{
return new ClaimsPrincipal(
new CaseSensitiveClaimsIdentity(new[]
{
new Claim(ClaimConstants.Username, username),
new Claim(ClaimConstants.Password, password),
}));
}
}
}
}
34 changes: 34 additions & 0 deletions tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
using Microsoft.Extensions.Options;
using Microsoft.Graph;
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Lab.Api;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Test.Common;
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
using Microsoft.IdentityModel.Tokens;
using Xunit;
Expand Down Expand Up @@ -91,6 +93,38 @@
Assert.NotEqual(tokenAcquirerA, tokenAcquirerC);
}

[Fact]
public async Task AcquireToken_ROPC_CCAasync()
{
var tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance();
_ = tokenAcquirerFactory.Build();

var labResponse = await LabUserHelper.GetDefaultUserAsync();

ITokenAcquirer tokenAcquirer = tokenAcquirerFactory.GetTokenAcquirer(
authority: "https://login.microsoftonline.com/organizations",
clientId: "88f91eac-c606-4c67-a0e2-a5e8a186854f",
clientCredentials: new[]
{
CertificateDescription.FromStoreWithDistinguishedName("CN=LabAuth.MSIDLab.com")
});

var user = ClaimsPrincipalFactory.FromUsernamePassword(labResponse.User.Upn, labResponse.User.GetOrFetchPassword());

var result = await tokenAcquirer.GetTokenForUserAsync(
scopes: new[] { "https://graph.microsoft.com/.default" }, user: user).ConfigureAwait(false);

Check warning on line 115 in tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs

View workflow job for this annotation

GitHub Actions / Analyse

Test methods should not call ConfigureAwait(false), as it may bypass parallelization limits. Omit ConfigureAwait, or use ConfigureAwait(true) to avoid CA2007. (https://xunit.net/xunit.analyzers/rules/xUnit1030)

Assert.NotNull(result);
Assert.NotNull(result.AccessToken);

var result2 = await tokenAcquirer.GetTokenForUserAsync(
scopes: new[] { "https://graph.microsoft.com/.default" }, user: user).ConfigureAwait(false);

Check warning on line 121 in tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs

View workflow job for this annotation

GitHub Actions / Analyse

Test methods should not call ConfigureAwait(false), as it may bypass parallelization limits. Omit ConfigureAwait, or use ConfigureAwait(true) to avoid CA2007. (https://xunit.net/xunit.analyzers/rules/xUnit1030)

Assert.NotNull(result2);
Assert.NotNull(result2.AccessToken);
Assert.Equal(result.AccessToken, result2.AccessToken);
}

[Fact]
public void AcquireToken_SafeFromMultipleThreads()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net7.0; net8.0</TargetFrameworks>
Expand All @@ -7,6 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Identity.Lab.Api" Version="$(MicrosoftIdentityLabApiVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNetTestSdkVersion)" />
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonVersion)" />
<PackageReference Include="xunit" Version="$(XunitVersion)" />
Expand All @@ -23,6 +24,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Identity.Web.GraphServiceClient\Microsoft.Identity.Web.GraphServiceClient.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Identity.Web.TokenAcquisition\Microsoft.Identity.Web.TokenAcquisition.csproj" />
<ProjectReference Include="..\..\Microsoft.Identity.Web.Test.Common\Microsoft.Identity.Web.Test.Common.csproj" />
</ItemGroup>

</Project>
Loading