From d9754680195784228c481e3a5d1c91e512302188 Mon Sep 17 00:00:00 2001 From: Bogdan Gavril Date: Wed, 29 Jan 2020 14:33:51 +0000 Subject: [PATCH 01/10] Update to MSAL 4.8.1 --- Microsoft.Identity.Web/Microsoft.Identity.Web.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj b/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj index 8192f7ce..eb48a251 100644 --- a/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj +++ b/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj @@ -56,6 +56,6 @@ - + From 1ec3c3ffb38f6497f617bc39191478bf66499cb1 Mon Sep 17 00:00:00 2001 From: Tiago Brenck Date: Tue, 4 Feb 2020 15:50:03 -0800 Subject: [PATCH 02/10] Fix for issue https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/issues/273 --- .../AuthorizeForScopesAttribute.cs | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/Microsoft.Identity.Web/AuthorizeForScopesAttribute.cs b/Microsoft.Identity.Web/AuthorizeForScopesAttribute.cs index bf166972..1a4bd600 100644 --- a/Microsoft.Identity.Web/AuthorizeForScopesAttribute.cs +++ b/Microsoft.Identity.Web/AuthorizeForScopesAttribute.cs @@ -15,10 +15,8 @@ namespace Microsoft.Identity.Web { - // TODO: rename to EnsureScopesAttribute ? or MsalAuthorizeForScopesAttribute or AuthorizeForScopesAttribute - /// - /// Filter used on a controller action to trigger an incremental consent. + /// Filter used on a controller action to trigger incremental consent. /// /// /// The following controller action will trigger @@ -42,12 +40,13 @@ public class AuthorizeForScopesAttribute : ExceptionFilterAttribute public string ScopeKeySection { get; set; } /// - /// Handles the MsaUiRequiredExeception + /// Handles the MsalUiRequiredException /// /// Context provided by ASP.NET Core public override void OnException(ExceptionContext context) { MsalUiRequiredException msalUiRequiredException = context.Exception as MsalUiRequiredException; + if (msalUiRequiredException == null) { msalUiRequiredException = context.Exception?.InnerException as MsalUiRequiredException; @@ -55,8 +54,11 @@ public override void OnException(ExceptionContext context) if (msalUiRequiredException != null) { - if (CanBeSolvedByReSignInUser(msalUiRequiredException)) + 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) { @@ -74,10 +76,13 @@ 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"); } - Scopes = new string[] { configuration.GetValue(ScopeKeySection) }; + scopes = new string[] { configuration.GetValue(ScopeKeySection) }; } - var properties = BuildAuthenticationPropertiesForIncrementalConsent(Scopes, msalUiRequiredException, context.HttpContext); + else + scopes = Scopes; + + var properties = BuildAuthenticationPropertiesForIncrementalConsent(scopes, msalUiRequiredException, context.HttpContext); context.Result = new ChallengeResult(properties); } } @@ -85,7 +90,7 @@ public override void OnException(ExceptionContext context) base.OnException(context); } - private bool CanBeSolvedByReSignInUser(MsalUiRequiredException ex) + private bool CanBeSolvedByReSignInOfUser(MsalUiRequiredException ex) { // ex.ErrorCode != MsalUiRequiredException.UserNullError indicates a cache problem. // When calling an [Authenticate]-decorated controller we expect an authenticated @@ -93,26 +98,30 @@ private bool CanBeSolvedByReSignInUser(MsalUiRequiredException ex) // InMemoryCache, the cache could be empty if the server was restarted. This is why // the null_user exception is thrown. - return ex.ErrorCode.ContainsAny(new [] { MsalError.UserNullError, MsalError.InvalidGrantError }); + return ex.ErrorCode.ContainsAny(new[] { MsalError.UserNullError, MsalError.InvalidGrantError }); } /// - /// Build Authentication properties needed for an incremental consent. + /// Build Authentication properties needed for incremental consent. /// /// Scopes to request /// MsalUiRequiredException instance /// current http context in the pipeline /// AuthenticationProperties private AuthenticationProperties BuildAuthenticationPropertiesForIncrementalConsent( - string[] scopes, MsalUiRequiredException ex, HttpContext context) + string[] scopes, + MsalUiRequiredException ex, + HttpContext context) { var properties = new AuthenticationProperties(); - // Set the scopes, including the scopes that ADAL.NET / MASL.NET need for the Token cache - string[] additionalBuildInScopes = - {OidcConstants.ScopeOpenId, OidcConstants.ScopeOfflineAccess, OidcConstants.ScopeProfile}; + // Set the scopes, including the scopes that ADAL.NET / MSAL.NET need for the token cache + string[] additionalBuiltInScopes = + {OidcConstants.ScopeOpenId, + OidcConstants.ScopeOfflineAccess, + OidcConstants.ScopeProfile}; properties.SetParameter>(OpenIdConnectParameterNames.Scope, - scopes.Union(additionalBuildInScopes).ToList()); + scopes.Union(additionalBuiltInScopes).ToList()); // Attempts to set the login_hint to avoid the logged-in user to be presented with an account selection dialog var loginHint = context.User.GetLoginHint(); From fea4d697c11dcffb524ce26d8f47440af1718f4f Mon Sep 17 00:00:00 2001 From: Jean-Marc Prieur Date: Sat, 8 Feb 2020 10:13:06 -0800 Subject: [PATCH 03/10] Adding details for the SQL cache Fixes #283 283 --- 2-WebApp-graph-user/2-2-TokenCache/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/2-WebApp-graph-user/2-2-TokenCache/README.md b/2-WebApp-graph-user/2-2-TokenCache/README.md index 673195cc..d4d35460 100644 --- a/2-WebApp-graph-user/2-2-TokenCache/README.md +++ b/2-WebApp-graph-user/2-2-TokenCache/README.md @@ -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 @@ -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 From c2249d8d2fd585a1591eee725fb6ffd04f3d342f Mon Sep 17 00:00:00 2001 From: egeskov Date: Sun, 9 Feb 2020 21:29:10 +0100 Subject: [PATCH 04/10] Handle tailing slashes in configured Authority and Instance (#278) * Updated references to 3.1.1 * Handle trailing slash and 'v2.0' in configured Authority value in AddProtectedWebApi method. * Only add api:// to valid audiences if given as Client Id (not beginning with 'api://'. * Handle optional configured ending slash for Instance in `TokenAcquisition` --- .../Microsoft.Identity.Web.csproj | 8 ++++---- Microsoft.Identity.Web/TokenAcquisition.cs | 3 +++ .../WebApiServiceCollectionExtensions.cs | 18 +++++++++++------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj b/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj index eb48a251..66ca3248 100644 --- a/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj +++ b/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj @@ -34,7 +34,7 @@ - + @@ -53,9 +53,9 @@ - - - + + + diff --git a/Microsoft.Identity.Web/TokenAcquisition.cs b/Microsoft.Identity.Web/TokenAcquisition.cs index 9455cb0e..454e8260 100644 --- a/Microsoft.Identity.Web/TokenAcquisition.cs +++ b/Microsoft.Identity.Web/TokenAcquisition.cs @@ -264,6 +264,9 @@ private IConfidentialClientApplication BuildConfidentialClientApplication() request.PathBase, azureAdOptions.CallbackPath ?? string.Empty); + if (!applicationOptions.Instance.EndsWith("/")) + applicationOptions.Instance += "/"; + string authority = $"{applicationOptions.Instance}{applicationOptions.TenantId}/"; var app = ConfidentialClientApplicationBuilder diff --git a/Microsoft.Identity.Web/WebApiServiceCollectionExtensions.cs b/Microsoft.Identity.Web/WebApiServiceCollectionExtensions.cs index 3bf10433..3fa38b82 100644 --- a/Microsoft.Identity.Web/WebApiServiceCollectionExtensions.cs +++ b/Microsoft.Identity.Web/WebApiServiceCollectionExtensions.cs @@ -53,13 +53,17 @@ public static IServiceCollection AddProtectedWebApi( configuration.Bind(configSectionName, options); // This is an Microsoft identity platform Web API - options.Authority += "/v2.0"; + 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} - options.TokenValidationParameters.ValidAudiences = new string[] - { - options.Audience, $"api://{options.Audience}" - }; + // 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. + var validAudiences = new List { options.Audience }; + if (!options.Audience.StartsWith("api://", StringComparison.OrdinalIgnoreCase)) + validAudiences.Add($"api://{options.Audience}"); + + options.TokenValidationParameters.ValidAudiences = validAudiences; // Instead of using the default validation (validating against a single tenant, as we do in line of business apps), // we inject our own multi-tenant validation logic (which even accepts both v1.0 and v2.0 tokens) @@ -127,4 +131,4 @@ public static IServiceCollection AddProtectedApiCallsWebApis( return services; } } -} \ No newline at end of file +} From 8c13f7f0dcd7aca333bce4725a6e711b0f1bda71 Mon Sep 17 00:00:00 2001 From: Jean-Marc Prieur Date: Sun, 9 Feb 2020 14:50:16 -0800 Subject: [PATCH 05/10] hotfix from https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/pull/280 (the references are broken) --- .gitignore | 12 ++++++------ .../WebApp-OpenIDConnect-DotNet.csproj | 1 - Microsoft.Identity.Web/Microsoft.Identity.Web.csproj | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index c3f12d4d..658c8ec6 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,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 @@ -104,16 +105,15 @@ /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 diff --git a/2-WebApp-graph-user/2-1-Call-MSGraph/WebApp-OpenIDConnect-DotNet.csproj b/2-WebApp-graph-user/2-1-Call-MSGraph/WebApp-OpenIDConnect-DotNet.csproj index b427e5f5..d0d1fea1 100644 --- a/2-WebApp-graph-user/2-1-Call-MSGraph/WebApp-OpenIDConnect-DotNet.csproj +++ b/2-WebApp-graph-user/2-1-Call-MSGraph/WebApp-OpenIDConnect-DotNet.csproj @@ -18,7 +18,6 @@ - diff --git a/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj b/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj index 66ca3248..0a3817a0 100644 --- a/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj +++ b/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj @@ -53,8 +53,8 @@ - - + + From ec4f613e69ebe2832f16f46ff6894c9cb06f8041 Mon Sep 17 00:00:00 2001 From: Jean-Marc Prieur Date: Sun, 9 Feb 2020 15:11:40 -0800 Subject: [PATCH 06/10] hotfix the AzureAD.UI versions: a lot of projects/folders are still in 3.0.0 --- Microsoft.Identity.Web/Microsoft.Identity.Web.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj b/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj index 0a3817a0..fac799fb 100644 --- a/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj +++ b/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj @@ -53,8 +53,8 @@ - - + + From 21474e32f8679aa6b58d896475aa799c7d018f29 Mon Sep 17 00:00:00 2001 From: Alan Miers Date: Sun, 9 Feb 2020 16:06:29 -0800 Subject: [PATCH 07/10] Update MSGraphService.cs (#235) Missing AuthenticatedClient in GetMyMemberOfGroupsAsync() --- .../5-2-Groups/Services/MicrosoftGraph-Rest/MSGraphService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/5-WebApp-AuthZ/5-2-Groups/Services/MicrosoftGraph-Rest/MSGraphService.cs b/5-WebApp-AuthZ/5-2-Groups/Services/MicrosoftGraph-Rest/MSGraphService.cs index c818630e..5a879275 100644 --- a/5-WebApp-AuthZ/5-2-Groups/Services/MicrosoftGraph-Rest/MSGraphService.cs +++ b/5-WebApp-AuthZ/5-2-Groups/Services/MicrosoftGraph-Rest/MSGraphService.cs @@ -277,7 +277,7 @@ public async Task GetCurrentUserGroupsAndRolesAsync public async Task> GetMyMemberOfGroupsAsync(string accessToken) { List groups = new List(); - + PrepareAuthenticatedClient(accessToken); // Get groups the current user is a direct member of. IUserMemberOfCollectionWithReferencesPage memberOfGroups = await graphServiceClient.Me.MemberOf.Request().GetAsync(); if (memberOfGroups?.Count > 0) @@ -378,4 +378,4 @@ await Task.Run(() => } } } -} \ No newline at end of file +} From 0ab28e77c2a0a2fcff39b26fd31dc2b2f2726322 Mon Sep 17 00:00:00 2001 From: Jean-Marc Prieur Date: Sun, 9 Feb 2020 17:13:06 -0800 Subject: [PATCH 08/10] Adding unit tests to #278 (#284) * Adding unit tests to #278 and reverting the UI package upgrade as 3.1.1 has a bug * Undoing what the refactoring tools in VS have changed * Updating the AzureADxx.UI packages * Adressing PR comments --- .gitignore | 4 -- ...AspnetCoreWebApp-calls-Microsoft-Graph.sln | 11 +++ .../WebApp-OpenIDConnect-DotNet.csproj | 2 +- .../WebApiServiceCollectionExtensionsTests.cs | 69 +++++++++++++++++++ .../WebApiServiceCollectionExtensions.cs | 68 ++++++++++++------ 5 files changed, 128 insertions(+), 26 deletions(-) create mode 100644 Microsoft.Identity.Web.Test/WebApiServiceCollectionExtensionsTests.cs diff --git a/.gitignore b/.gitignore index 658c8ec6..12dbc22a 100644 --- a/.gitignore +++ b/.gitignore @@ -114,11 +114,7 @@ /4-WebApp-your-API/Client/obj /2-WebApp-graph-user/2-4-Sovereign-Call-MSGraph/bin /2-WebApp-graph-user/2-4-Sovereign-Call-MSGraph/obj -/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 diff --git a/2-WebApp-graph-user/2-1-Call-MSGraph/AspnetCoreWebApp-calls-Microsoft-Graph.sln b/2-WebApp-graph-user/2-1-Call-MSGraph/AspnetCoreWebApp-calls-Microsoft-Graph.sln index 7024cab3..83026426 100644 --- a/2-WebApp-graph-user/2-1-Call-MSGraph/AspnetCoreWebApp-calls-Microsoft-Graph.sln +++ b/2-WebApp-graph-user/2-1-Call-MSGraph/AspnetCoreWebApp-calls-Microsoft-Graph.sln @@ -7,6 +7,13 @@ 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +28,10 @@ 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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/2-WebApp-graph-user/2-1-Call-MSGraph/WebApp-OpenIDConnect-DotNet.csproj b/2-WebApp-graph-user/2-1-Call-MSGraph/WebApp-OpenIDConnect-DotNet.csproj index d0d1fea1..f20d8f43 100644 --- a/2-WebApp-graph-user/2-1-Call-MSGraph/WebApp-OpenIDConnect-DotNet.csproj +++ b/2-WebApp-graph-user/2-1-Call-MSGraph/WebApp-OpenIDConnect-DotNet.csproj @@ -18,7 +18,7 @@ - + diff --git a/Microsoft.Identity.Web.Test/WebApiServiceCollectionExtensionsTests.cs b/Microsoft.Identity.Web.Test/WebApiServiceCollectionExtensionsTests.cs new file mode 100644 index 00000000..fba6c594 --- /dev/null +++ b/Microsoft.Identity.Web.Test/WebApiServiceCollectionExtensionsTests.cs @@ -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); + + } + } +} diff --git a/Microsoft.Identity.Web/WebApiServiceCollectionExtensions.cs b/Microsoft.Identity.Web/WebApiServiceCollectionExtensions.cs index 3fa38b82..928d31e1 100644 --- a/Microsoft.Identity.Web/WebApiServiceCollectionExtensions.cs +++ b/Microsoft.Identity.Web/WebApiServiceCollectionExtensions.cs @@ -53,17 +53,11 @@ public static IServiceCollection AddProtectedWebApi( configuration.Bind(configSectionName, options); // This is an Microsoft identity platform Web API - var authority = options.Authority.Trim().TrimEnd('/'); - if (!authority.EndsWith("v2.0")) - authority += "/v2.0"; - options.Authority = authority; + 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. - var validAudiences = new List { options.Audience }; - if (!options.Audience.StartsWith("api://", StringComparison.OrdinalIgnoreCase)) - validAudiences.Add($"api://{options.Audience}"); - - options.TokenValidationParameters.ValidAudiences = validAudiences; + // 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); // Instead of using the default validation (validating against a single tenant, as we do in line of business apps), // we inject our own multi-tenant validation logic (which even accepts both v1.0 and v2.0 tokens) @@ -80,17 +74,17 @@ public static IServiceCollection AddProtectedWebApi( options.Events = new JwtBearerEvents(); options.Events.OnTokenValidated = async context => - { - // This check is required to ensure that the Web API only accepts tokens from tenants where it has been consented and provisioned. - if (!context.Principal.Claims.Any(x => x.Type == ClaimConstants.Scope) - && !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Scp) - && !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Roles)) - { - throw new UnauthorizedAccessException("Neither scope or roles claim was found in the bearer token."); - } - - await Task.FromResult(0); - }; + { + // This check is required to ensure that the Web API only accepts tokens from tenants where it has been consented and provisioned. + if (!context.Principal.Claims.Any(x => x.Type == ClaimConstants.Scope) + && !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Scp) + && !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Roles)) + { + throw new UnauthorizedAccessException("Neither scope or roles claim was found in the bearer token."); + } + + await Task.FromResult(0); + }; if (subscribeToJwtBearerMiddlewareDiagnosticsEvents) { @@ -130,5 +124,37 @@ public static IServiceCollection AddProtectedApiCallsWebApis( return services; } + + /// + /// Ensures that the authority is a v2.0 authority + /// + /// 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 + internal static void EnsureAuthorityIsV2_0(JwtBearerOptions options) + { + var authority = options.Authority.Trim().TrimEnd('/'); + if (!authority.EndsWith("v2.0")) + authority += "/v2.0"; + options.Authority = authority; + } + + + /// + /// 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) + /// + /// Jwt bearer options for which to ensure that + /// api://GUID is a valid audience + internal static void EnsureValidAudiencesContainsApiGuidIfGuidProvided(JwtBearerOptions options) + { + var validAudiences = new List { options.Audience }; + if (!options.Audience.StartsWith("api://", StringComparison.OrdinalIgnoreCase) + && Guid.TryParse(options.Audience, out _)) + validAudiences.Add($"api://{options.Audience}"); + + options.TokenValidationParameters.ValidAudiences = validAudiences; + } } } From 3a51a6a406437b56ff8a3d26fe65c342514e6dfc Mon Sep 17 00:00:00 2001 From: Jean-Marc Prieur Date: Sun, 9 Feb 2020 19:43:18 -0800 Subject: [PATCH 09/10] Fixing missing references --- .../AspnetCoreWebApp-calls-Microsoft-Graph.sln | 8 ++++---- Microsoft.Identity.Web/Microsoft.Identity.Web.csproj | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/2-WebApp-graph-user/2-1-Call-MSGraph/AspnetCoreWebApp-calls-Microsoft-Graph.sln b/2-WebApp-graph-user/2-1-Call-MSGraph/AspnetCoreWebApp-calls-Microsoft-Graph.sln index 31b22097..f7f1f170 100644 --- a/2-WebApp-graph-user/2-1-Call-MSGraph/AspnetCoreWebApp-calls-Microsoft-Graph.sln +++ b/2-WebApp-graph-user/2-1-Call-MSGraph/AspnetCoreWebApp-calls-Microsoft-Graph.sln @@ -30,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 diff --git a/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj b/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj index cfe4edc0..b632d941 100644 --- a/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj +++ b/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj @@ -55,7 +55,8 @@ - + + From b23fc76247cb442de5798faff94aca0a71b9fb70 Mon Sep 17 00:00:00 2001 From: Jean-Marc Prieur Date: Sun, 9 Feb 2020 19:55:22 -0800 Subject: [PATCH 10/10] Uncommenting a needed line --- Microsoft.Identity.Web/WebAppServiceCollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Identity.Web/WebAppServiceCollectionExtensions.cs b/Microsoft.Identity.Web/WebAppServiceCollectionExtensions.cs index 6a4cf12a..ff73061b 100644 --- a/Microsoft.Identity.Web/WebAppServiceCollectionExtensions.cs +++ b/Microsoft.Identity.Web/WebAppServiceCollectionExtensions.cs @@ -76,7 +76,7 @@ public static IServiceCollection AddWebAppCallsProtectedWebApi( services.Configure(openIdConnectScheme, options => { // Response type - //options.ResponseType = OpenIdConnectResponseType.CodeIdToken; + options.ResponseType = OpenIdConnectResponseType.CodeIdToken; // This scope is needed to get a refresh token when users sign-in with their Microsoft personal accounts // It's required by MSAL.NET and automatically provided when users sign-in with work or school accounts