Skip to content

Update jennyf/b2cwebapi from latest jmprieur/webappwebapib2c (and therefore master) #286

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

Merged
merged 15 commits into from
Feb 10, 2020
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
17 changes: 6 additions & 11 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
/2-WebApp-graph-user/2-1-Call-MSGraph/.vs
/2-WebApp-graph-user/2-1-Call-MSGraph/bin
/2-WebApp-graph-user/2-1-Call-MSGraph/obj
/2-WebApp-graph-user/2-1-Call-MSGraph/TestResults
/2-WebApp-graph-user/2-2-TokenCache/.vs
/2-WebApp-graph-user/2-2-TokenCache/bin
/2-WebApp-graph-user/2-2-TokenCache/obj
Expand Down Expand Up @@ -110,22 +111,16 @@
/4-WebApp-your-API/Client/bin/Release/netcoreapp2.2
/4-WebApp-your-API/Client/obj/Debug/netcoreapp2.2
/4-WebApp-your-API/Client/obj/Release/netcoreapp2.2
/Microsoft.Identity.Web.Test/obj/Release/netcoreapp2.2
/Microsoft.Identity.Web.Test/obj/Debug/netcoreapp2.2
/5-WebApp-AuthZ/5-2-Groups/bin/Release/netcoreapp2.2
/5-WebApp-AuthZ/5-1-Roles/bin/Release/netcoreapp2.2
/Microsoft.Identity.Web.Test/obj
/Microsoft.Identity.Web.Test/bin
/5-WebApp-AuthZ/5-2-Groups/bin
/5-WebApp-AuthZ/5-1-Roles/bin
/4-WebApp-your-API/TodoListService/obj
/4-WebApp-your-API/TodoListService/bin
/4-WebApp-your-API/Client/obj
/2-WebApp-graph-user/2-4-Sovereign-Call-MSGraph/bin/Debug/netcoreapp2.2
/2-WebApp-graph-user/2-4-Sovereign-Call-MSGraph/bin
/2-WebApp-graph-user/2-4-Sovereign-Call-MSGraph/obj
/2-WebApp-graph-user/2-4-Sovereign-Call-MSGraph/bin/Release/netcoreapp2.2
/Microsoft.Identity.Web.Test/bin/Release/netcoreapp2.2
/Microsoft.Identity.Web.Test/obj
/4-WebApp-your-API/4-2-B2C/.vs
/4-WebApp-your-API/4-2-B2C/Client/obj
/4-WebApp-your-API/4-2-B2C/TodoListService/obj
/2-WebApp-graph-user/2-3-Multi-Tenant/.vs/WebApp-OpenIDConnect-DotNet
/2-WebApp-graph-user/2-3-Multi-Tenant/bin/Debug/netcoreapp2.2
/2-WebApp-graph-user/2-3-Multi-Tenant/obj
/Microsoft.Identity.Web.Test/bin/Release/netcoreapp3.0
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApp-OpenIDConnect-DotNet
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Web", "..\..\Microsoft.Identity.Web\Microsoft.Identity.Web.csproj", "{E0CEF26A-6CE6-4505-851B-6580D5564752}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CD0BB564-6C1E-4FCE-B9AB-7C637FDEE569}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Web.Test", "..\..\Microsoft.Identity.Web.Test\Microsoft.Identity.Web.Test.csproj", "{8CCEAE2A-BDF6-470C-B6DE-7FC81A74DBD7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Web.UI", "..\..\Microsoft.Identity.Web.UI\Microsoft.Identity.Web.UI.csproj", "{8CC22202-F66C-4332-A4F0-A2C09EBA08EC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Web.Test", "..\..\Microsoft.Identity.Web.Test\Microsoft.Identity.Web.Test.csproj", "{0EEC3E2E-69D0-4A7F-98D6-4386330F4965}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -24,14 +30,14 @@ Global
{E0CEF26A-6CE6-4505-851B-6580D5564752}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E0CEF26A-6CE6-4505-851B-6580D5564752}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E0CEF26A-6CE6-4505-851B-6580D5564752}.Release|Any CPU.Build.0 = Release|Any CPU
{8CCEAE2A-BDF6-470C-B6DE-7FC81A74DBD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CCEAE2A-BDF6-470C-B6DE-7FC81A74DBD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CCEAE2A-BDF6-470C-B6DE-7FC81A74DBD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CCEAE2A-BDF6-470C-B6DE-7FC81A74DBD7}.Release|Any CPU.Build.0 = Release|Any CPU
{8CC22202-F66C-4332-A4F0-A2C09EBA08EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CC22202-F66C-4332-A4F0-A2C09EBA08EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CC22202-F66C-4332-A4F0-A2C09EBA08EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CC22202-F66C-4332-A4F0-A2C09EBA08EC}.Release|Any CPU.Build.0 = Release|Any CPU
{0EEC3E2E-69D0-4A7F-98D6-4386330F4965}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0EEC3E2E-69D0-4A7F-98D6-4386330F4965}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0EEC3E2E-69D0-4A7F-98D6-4386330F4965}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0EEC3E2E-69D0-4A7F-98D6-4386330F4965}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Graph" Version="1.14.0" />
<PackageReference Include="Microsoft.Graph" Version="1.21.0" />
</ItemGroup>

<ItemGroup>
Expand Down
5 changes: 5 additions & 0 deletions 2-WebApp-graph-user/2-2-TokenCache/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ Note: if you had used the automation to setup your application mentioned in [Ste

Starting from the [previous phase of the tutorial](../../2-WebApp-graph-user/2-1-Call-MSGraph), the code was incrementally updated with the following steps:

### Reference Microsoft.Extensions.Caching.SqlServer

This sample proposes a distributed SQL token cache. To use it, you'll need to add a reference to the `Microsoft.Extensions.Caching.SqlServer` NuGet package

### Update the `Startup.cs` file to enable Token caching using Sql database.

```CSharp
Expand Down Expand Up @@ -205,6 +209,7 @@ The files `MSALAppSqlTokenCacheProvider.cs` and `MSALPerUserSqlTokenCacheProvide
## Next steps

- Learn how to enable distributed caches in [token cache serialization](../2.2.%20token%20cache%20serialization)
- Learn more about the [Distributed SQL Server Cache](https://docs.microsoft.com/aspnet/core/performance/caching/distributed#distributed-sql-server-cache)
- Learn how the same principle you've just learnt can be used to call:
- [several Microsoft APIs](../../3-WebApp-multi-APIs), which will enable you to learn how incremental consent and conditional access is managed in your Web App
- 3rd party, or even [your own Web API](../../4-WebApp-your-API), which will enable you to learn about custom scopes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ public async Task<UserGroupsAndDirectoryRoles> GetCurrentUserGroupsAndRolesAsync
public async Task<List<Group>> GetMyMemberOfGroupsAsync(string accessToken)
{
List<Group> groups = new List<Group>();

PrepareAuthenticatedClient(accessToken);
// Get groups the current user is a direct member of.
IUserMemberOfCollectionWithReferencesPage memberOfGroups = await graphServiceClient.Me.MemberOf.Request().GetAsync();
if (memberOfGroups?.Count > 0)
Expand Down Expand Up @@ -378,4 +378,4 @@ await Task.Run(() =>
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.AspNetCore.Authentication.JwtBearer;
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;
using System.Linq;

namespace Microsoft.Identity.Web.Test
{
public class WebApiServiceCollectionExtensionsTests
{
[Fact]
public void TestAuthority()
{
// Arrange
JwtBearerOptions options = new JwtBearerOptions();

// Act and Assert
options.Authority = "https://login.microsoftonline.com/common";
WebApiServiceCollectionExtensions.EnsureAuthorityIsV2_0(options);
Assert.Equal("https://login.microsoftonline.com/common/v2.0", options.Authority);

options.Authority = "https://login.microsoftonline.us/organizations";
WebApiServiceCollectionExtensions.EnsureAuthorityIsV2_0(options);
Assert.Equal("https://login.microsoftonline.us/organizations/v2.0", options.Authority);

options.Authority = "https://login.microsoftonline.com/common/";
WebApiServiceCollectionExtensions.EnsureAuthorityIsV2_0(options);
Assert.Equal("https://login.microsoftonline.com/common/v2.0", options.Authority);

options.Authority = "https://login.microsoftonline.com/common/v2.0";
WebApiServiceCollectionExtensions.EnsureAuthorityIsV2_0(options);
Assert.Equal("https://login.microsoftonline.com/common/v2.0", options.Authority);


options.Authority = "https://login.microsoftonline.com/common/v2.0";
WebApiServiceCollectionExtensions.EnsureAuthorityIsV2_0(options);
Assert.Equal("https://login.microsoftonline.com/common/v2.0", options.Authority);

}

[Fact]
public void TestAudience()
{
JwtBearerOptions options = new JwtBearerOptions();

// Act and Assert
options.Audience = "https://localhost";
WebApiServiceCollectionExtensions.EnsureValidAudiencesContainsApiGuidIfGuidProvided(options);
Assert.True(options.TokenValidationParameters.ValidAudiences.Count() == 1);
Assert.True(options.TokenValidationParameters.ValidAudiences.First() == "https://localhost");

options.Audience = "api://1EE5A092-0DFD-42B6-88E5-C517C0141321";
WebApiServiceCollectionExtensions.EnsureValidAudiencesContainsApiGuidIfGuidProvided(options);
Assert.True(options.TokenValidationParameters.ValidAudiences.Count() == 1);
Assert.True(options.TokenValidationParameters.ValidAudiences.First() == "api://1EE5A092-0DFD-42B6-88E5-C517C0141321");

options.Audience = "1EE5A092-0DFD-42B6-88E5-C517C0141321";
WebApiServiceCollectionExtensions.EnsureValidAudiencesContainsApiGuidIfGuidProvided(options);
Assert.True(options.TokenValidationParameters.ValidAudiences.Count() == 2);
Assert.Contains("api://1EE5A092-0DFD-42B6-88E5-C517C0141321", options.TokenValidationParameters.ValidAudiences);
Assert.Contains("1EE5A092-0DFD-42B6-88E5-C517C0141321", options.TokenValidationParameters.ValidAudiences);

}
}
}
20 changes: 13 additions & 7 deletions Microsoft.Identity.Web/AuthorizeForScopesAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public override void OnException(ExceptionContext context)
{
string[] incrementalConsentScopes = new string[] { };
MsalUiRequiredException msalUiRequiredException = context.Exception as MsalUiRequiredException;

if (msalUiRequiredException == null)
{
msalUiRequiredException = context.Exception?.InnerException as MsalUiRequiredException;
Expand All @@ -56,6 +57,9 @@ public override void OnException(ExceptionContext context)
{
if (CanBeSolvedByReSignInOfUser(msalUiRequiredException))
{
// Do not re-use the attribute param Scopes. For more info: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/issues/273
string[] scopes = null;

// the users cannot provide both scopes and ScopeKeySection at the same time
if (!string.IsNullOrWhiteSpace(ScopeKeySection) && Scopes != null && Scopes.Length > 0)
{
Expand All @@ -73,15 +77,17 @@ public override void OnException(ExceptionContext context)
throw new InvalidOperationException($"The {nameof(ScopeKeySection)} is provided but the IConfiguration instance is not present in the services collection");
}

incrementalConsentScopes = new string[] { configuration.GetValue<string>(ScopeKeySection) };
}

if (Scopes != null && Scopes.Length > 0 && incrementalConsentScopes != null && incrementalConsentScopes.Length > 0)
{
throw new InvalidOperationException("no scopes provided here...");
scopes = new string[] { configuration.GetValue<string>(ScopeKeySection) };
if (Scopes != null && Scopes.Length > 0 && scopes != null && scopes.Length > 0)
{
throw new InvalidOperationException("no scopes provided in scopes...");
}
}
else
scopes = Scopes;

var properties = BuildAuthenticationPropertiesForIncrementalConsent(incrementalConsentScopes, msalUiRequiredException, context.HttpContext);
var properties = BuildAuthenticationPropertiesForIncrementalConsent(scopes, msalUiRequiredException, context.HttpContext);
context.Result = new ChallengeResult(properties);
}
}
Expand Down
7 changes: 4 additions & 3 deletions Microsoft.Identity.Web/Microsoft.Identity.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</ItemGroup>

<ItemGroup Label="Build Tools" Condition="$([MSBuild]::IsOsPlatform('Windows'))">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-18618-05" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>


Expand All @@ -55,7 +55,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.8.0" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.Abstractions" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.8.1" />
</ItemGroup>
</Project>
10 changes: 6 additions & 4 deletions Microsoft.Identity.Web/TokenAcquisition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -310,22 +310,24 @@ private IConfidentialClientApplication BuildConfidentialClientApplication()
request.PathBase,
microsoftIdentityOptions.CallbackPath.Value ?? string.Empty);

string authority = string.Empty;
if (!applicationOptions.Instance.EndsWith("/"))
applicationOptions.Instance += "/";

string authority ;
IConfidentialClientApplication app = null;

if (microsoftIdentityOptions.IsB2C)
{
authority = $"{applicationOptions.Instance.TrimEnd('/')}/tfp/{microsoftIdentityOptions.Domain}/{microsoftIdentityOptions.DefaultUserFlow}";
authority = $"{applicationOptions.Instance}tfp/{microsoftIdentityOptions.Domain}/{microsoftIdentityOptions.DefaultUserFlow}";
app = ConfidentialClientApplicationBuilder
.CreateWithApplicationOptions(applicationOptions)
.WithRedirectUri(currentUri)
.WithB2CAuthority(authority)
.Build();
}

else
{
authority = $"{applicationOptions.Instance.TrimEnd('/')}/{applicationOptions.TenantId}/";
authority = $"{applicationOptions.Instance}{applicationOptions.TenantId}/";
app = ConfidentialClientApplicationBuilder
.CreateWithApplicationOptions(applicationOptions)
.WithRedirectUri(currentUri)
Expand Down
52 changes: 33 additions & 19 deletions Microsoft.Identity.Web/WebApiServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,12 @@ public static AuthenticationBuilder AddProtectedWebApi(
if (string.IsNullOrWhiteSpace(options.Authority))
options.Authority = AuthorityHelpers.BuildAuthority(microsoftIdentityOptions);

if (!AuthorityHelpers.IsV2Authority(options.Authority))
options.Authority += "/v2.0";
// This is an Microsoft identity platform Web API
EnsureAuthorityIsV2_0(options);

// The valid audience could be given as Client Id or as Uri.
// If it does not start with 'api://', this variant is added to the list of valid audiences.
EnsureValidAudiencesContainsApiGuidIfGuidProvided(options);

options.TokenValidationParameters.ValidAudiences = GetValidAudiences(options, microsoftIdentityOptions);

Expand Down Expand Up @@ -244,26 +248,36 @@ public static IServiceCollection AddProtectedWebApiCallsProtectedWebApi(
return services;
}

internal static List<string> GetValidAudiences(
JwtBearerOptions options,
MicrosoftIdentityOptions msIdentityOptions)
/// <summary>
/// Ensures that the authority is a v2.0 authority
/// </summary>
/// <param name="options">Jwt bearer options read from the config file
/// or set by the developper, for which we want to ensure the authority
/// is a v2.0 authority</param>
internal static void EnsureAuthorityIsV2_0(JwtBearerOptions options)
{
var validAudiences = new List<string>();
var authority = options.Authority.Trim().TrimEnd('/');
if (!authority.EndsWith("v2.0"))
authority += "/v2.0";
options.Authority = authority;
}

// The valid audiences are both the Client ID (options.Audience) and api://{ClientID}
// If the developer doesn't set the Audience on JwtBearerOptions, use ClientId from MicrosoftIdentityOptions
if (!string.IsNullOrEmpty(options.Audience))
{
validAudiences.Add(options.Audience);

/// <summary>
/// Ensure that if the audience is a GUID, api://{audience} is also added
/// as a valid audience (this is the default App ID URL in the app registration
/// portal)
/// </summary>
/// <param name="options">Jwt bearer options for which to ensure that
/// api://GUID is a valid audience</param>
internal static void EnsureValidAudiencesContainsApiGuidIfGuidProvided(JwtBearerOptions options)
{
var validAudiences = new List<string> { options.Audience };
if (!options.Audience.StartsWith("api://", StringComparison.OrdinalIgnoreCase)
&& Guid.TryParse(options.Audience, out _))
validAudiences.Add($"api://{options.Audience}");
}
else
{
validAudiences.Add(msIdentityOptions.ClientId);
validAudiences.Add($"api://{msIdentityOptions.ClientId}");
}

return validAudiences;
options.TokenValidationParameters.ValidAudiences = validAudiences;
}
}
}
}