Explorar el Código

Merge remote-tracking branch 'Security/rybrande/masterToSrc' into rybrande/Mondo22ToMaster

Ryan Brandenburg hace 7 años
padre
commit
7aefd16eed
Se han modificado 85 ficheros con 2484 adiciones y 3266 borrados
  1. 1 0
      src/Security/.gitignore
  2. 1 5
      src/Security/Directory.Build.targets
  3. 4 6
      src/Security/README.md
  4. 21 0
      src/Security/Security.sln
  5. 53 0
      src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/AuthorizationMiddlewareBenchmark.cs
  6. 28 0
      src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/AuthorizationPolicyBenchmark.cs
  7. 24 0
      src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/Microsoft.AspNetCore.Security.Performance.csproj
  8. 1 0
      src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/Properties/AssemblyInfo.cs
  9. 16 0
      src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/readme.md
  10. 38 34
      src/Security/build/dependencies.props
  11. 1 7
      src/Security/build/repo.props
  12. 1 1
      src/Security/samples/CookiePolicySample/CookiePolicySample.csproj
  13. 1 1
      src/Security/samples/CookieSample/CookieSample.csproj
  14. 1 1
      src/Security/samples/CookieSessionSample/CookieSessionSample.csproj
  15. 1 1
      src/Security/samples/JwtBearerSample/JwtBearerSample.csproj
  16. 1 0
      src/Security/samples/JwtBearerSample/wwwroot/index.html
  17. 1 1
      src/Security/samples/OpenIdConnect.AzureAdSample/OpenIdConnect.AzureAdSample.csproj
  18. 1 1
      src/Security/samples/OpenIdConnectSample/OpenIdConnectSample.csproj
  19. 11 0
      src/Security/samples/OpenIdConnectSample/Startup.cs
  20. 1 1
      src/Security/samples/SocialSample/SocialSample.csproj
  21. 1 1
      src/Security/samples/WsFedSample/WsFedSample.csproj
  22. 2 2
      src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs
  23. 0 63
      src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationOptions.cs
  24. 1 1
      src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/Microsoft.AspNetCore.Authentication.Cookies.csproj
  25. 52 0
      src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/breakingchanges.netcore.json
  26. 1 1
      src/Security/src/Microsoft.AspNetCore.Authentication.Facebook/Microsoft.AspNetCore.Authentication.Facebook.csproj
  27. 1 1
      src/Security/src/Microsoft.AspNetCore.Authentication.Google/Microsoft.AspNetCore.Authentication.Google.csproj
  28. 1 1
      src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs
  29. 1 1
      src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/Microsoft.AspNetCore.Authentication.JwtBearer.csproj
  30. 7 0
      src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/breakingchanges.netcore.json
  31. 1 1
      src/Security/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/Microsoft.AspNetCore.Authentication.MicrosoftAccount.csproj
  32. 1 1
      src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/Microsoft.AspNetCore.Authentication.OAuth.csproj
  33. 11 1
      src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs
  34. 2 4
      src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthOptions.cs
  35. 1 1
      src/Security/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Microsoft.AspNetCore.Authentication.OpenIdConnect.csproj
  36. 12 2
      src/Security/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs
  37. 1 1
      src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/Microsoft.AspNetCore.Authentication.Twitter.csproj
  38. 6 4
      src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs
  39. 1 1
      src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterOptions.cs
  40. 1 1
      src/Security/src/Microsoft.AspNetCore.Authentication.WsFederation/Microsoft.AspNetCore.Authentication.WsFederation.csproj
  41. 1 1
      src/Security/src/Microsoft.AspNetCore.Authentication.WsFederation/WsFederationHandler.cs
  42. 0 42
      src/Security/src/Microsoft.AspNetCore.Authentication/AuthenticationServiceCollectionExtensions.cs
  43. 44 0
      src/Security/src/Microsoft.AspNetCore.Authentication/Events/AccessDeniedContext.cs
  44. 7 1
      src/Security/src/Microsoft.AspNetCore.Authentication/Events/RemoteAuthenticationEvents.cs
  45. 41 11
      src/Security/src/Microsoft.AspNetCore.Authentication/LoggingExtensions.cs
  46. 1 1
      src/Security/src/Microsoft.AspNetCore.Authentication/Microsoft.AspNetCore.Authentication.csproj
  47. 44 0
      src/Security/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs
  48. 16 0
      src/Security/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationOptions.cs
  49. 22 0
      src/Security/src/Microsoft.AspNetCore.Authentication/breakingchanges.netcore.json
  50. 29 0
      src/Security/src/Microsoft.AspNetCore.Authorization.Policy/AuthorizationAppBuilderExtensions.cs
  51. 50 0
      src/Security/src/Microsoft.AspNetCore.Authorization.Policy/AuthorizationEndpointConventionBuilderExtensions.cs
  52. 104 0
      src/Security/src/Microsoft.AspNetCore.Authorization.Policy/AuthorizationMiddleware.cs
  53. 4 2
      src/Security/src/Microsoft.AspNetCore.Authorization.Policy/Microsoft.AspNetCore.Authorization.Policy.csproj
  54. 14 2
      src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationOptions.cs
  55. 56 28
      src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationPolicy.cs
  56. 9 9
      src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationPolicyBuilder.cs
  57. 0 10
      src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizeAttribute.cs
  58. 22 1
      src/Security/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationPolicyProvider.cs
  59. 6 0
      src/Security/src/Microsoft.AspNetCore.Authorization/IAuthorizationPolicyProvider.cs
  60. 1 1
      src/Security/src/Microsoft.AspNetCore.Authorization/Microsoft.AspNetCore.Authorization.csproj
  61. 1 1
      src/Security/src/Microsoft.AspNetCore.CookiePolicy/Microsoft.AspNetCore.CookiePolicy.csproj
  62. 0 7
      src/Security/test/Directory.Build.props
  63. 5 421
      src/Security/test/Microsoft.AspNetCore.Authentication.Test/CookieTests.cs
  64. 20 467
      src/Security/test/Microsoft.AspNetCore.Authentication.Test/FacebookTests.cs
  65. 87 424
      src/Security/test/Microsoft.AspNetCore.Authentication.Test/GoogleTests.cs
  66. 15 407
      src/Security/test/Microsoft.AspNetCore.Authentication.Test/JwtBearerTests.cs
  67. 1 1
      src/Security/test/Microsoft.AspNetCore.Authentication.Test/Microsoft.AspNetCore.Authentication.Test.csproj
  68. 20 423
      src/Security/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccountTests.cs
  69. 112 430
      src/Security/test/Microsoft.AspNetCore.Authentication.Test/OAuthTests.cs
  70. 58 2
      src/Security/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectEventTests.cs
  71. 92 0
      src/Security/test/Microsoft.AspNetCore.Authentication.Test/RemoteAuthenticationTests.cs
  72. 510 0
      src/Security/test/Microsoft.AspNetCore.Authentication.Test/SharedAuthenticationTests.cs
  73. 107 420
      src/Security/test/Microsoft.AspNetCore.Authentication.Test/TwitterTests.cs
  74. 65 0
      src/Security/test/Microsoft.AspNetCore.Authorization.Test/AuthorizationAppBuilderExtensionsTests.cs
  75. 63 0
      src/Security/test/Microsoft.AspNetCore.Authorization.Test/AuthorizationEndpointConventionBuilderExtensionsTests.cs
  76. 459 0
      src/Security/test/Microsoft.AspNetCore.Authorization.Test/AuthorizationMiddlewareTests.cs
  77. 10 0
      src/Security/test/Microsoft.AspNetCore.Authorization.Test/DefaultAuthorizationServiceTests.cs
  78. 4 1
      src/Security/test/Microsoft.AspNetCore.Authorization.Test/Microsoft.AspNetCore.Authorization.Test.csproj
  79. 58 0
      src/Security/test/Microsoft.AspNetCore.Authorization.Test/TestObjects/TestAuthenticationService.cs
  80. 1 1
      src/Security/test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test.csproj
  81. 1 1
      src/Security/test/Microsoft.AspNetCore.CookiePolicy.Test/Microsoft.AspNetCore.CookiePolicy.Test.csproj
  82. 5 0
      src/Security/test/Microsoft.Owin.Security.Interop.Test/CookieInteropTests.cs
  83. 2 1
      src/Security/test/Microsoft.Owin.Security.Interop.Test/Microsoft.Owin.Security.Interop.Test.csproj
  84. 3 0
      src/Security/test/Microsoft.Owin.Security.Interop.Test/TicketInteropTests.cs
  85. 3 3
      src/Security/version.props

+ 1 - 0
src/Security/.gitignore

@@ -30,3 +30,4 @@ project.lock.json
 /.vs/
 .vscode/
 global.json
+BenchmarkDotNet.Artifacts/

+ 1 - 5
src/Security/Directory.Build.targets

@@ -1,10 +1,6 @@
 <Project>
   <PropertyGroup>
-    <RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">$(MicrosoftNETCoreApp20PackageVersion)</RuntimeFrameworkVersion>
-    <RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">$(MicrosoftNETCoreApp21PackageVersion)</RuntimeFrameworkVersion>
-    <RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.2' ">$(MicrosoftNETCoreApp22PackageVersion)</RuntimeFrameworkVersion>
+    <RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp3.0' ">$(MicrosoftNETCoreAppPackageVersion)</RuntimeFrameworkVersion>
     <NETStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard2.0' ">$(NETStandardLibrary20PackageVersion)</NETStandardImplicitPackageVersion>
-    <!-- aspnet/BuildTools#662 Don't police what version of NetCoreApp we use -->
-    <NETCoreAppMaximumVersion>99.9</NETCoreAppMaximumVersion>
   </PropertyGroup>
 </Project>

+ 4 - 6
src/Security/README.md

@@ -1,9 +1,7 @@
-ASP.NET Security
-========
+ASP.NET Security [Archived]
+===========================
 
-AppVeyor: [![AppVeyor](https://ci.appveyor.com/api/projects/status/fujhh8n956v5ohfd/branch/dev?svg=true)](https://ci.appveyor.com/project/aspnetci/Security/branch/dev)
-
-Travis:   [![Travis](https://travis-ci.org/aspnet/Security.svg?branch=dev)](https://travis-ci.org/aspnet/Security)
+**This GitHub project has been archived.** Ongoing development on this project can be found in <https://github.com/aspnet/AspNetCore>.
 
 Contains the security and authorization middlewares for ASP.NET Core.
 
@@ -14,4 +12,4 @@ A list of community projects related to authentication and security for ASP.NET
 ASP.NET Security will not include Basic Authentication middleware due to its potential insecurity and performance problems. If you host under IIS you can enable it via IIS configuration.
 
 
-This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo.
+This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [AspNetCore](https://github.com/aspnet/AspNetCore) repo.

+ 21 - 0
src/Security/Security.sln

@@ -79,6 +79,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authen
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WsFedSample", "samples\WsFedSample\WsFedSample.csproj", "{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{55052FE3-F8C2-4E6C-97B0-C02ED1DBEA62}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Security.Performance", "benchmarks\Microsoft.AspNetCore.Security.Performance\Microsoft.AspNetCore.Security.Performance.csproj", "{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -517,6 +521,22 @@ Global
 		{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x64.Build.0 = Release|Any CPU
 		{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x86.ActiveCfg = Release|Any CPU
 		{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x86.Build.0 = Release|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|x64.Build.0 = Debug|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|x86.Build.0 = Debug|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|Any CPU.Build.0 = Release|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|x64.ActiveCfg = Release|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|x64.Build.0 = Release|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|x86.ActiveCfg = Release|Any CPU
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -549,6 +569,7 @@ Global
 		{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
 		{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
 		{5EC2E398-E46A-430D-8E4B-E91C8FC3E800} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
+		{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A} = {55052FE3-F8C2-4E6C-97B0-C02ED1DBEA62}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {ABF8089E-43D0-4010-84A7-7A9DCFE49357}

+ 53 - 0
src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/AuthorizationMiddlewareBenchmark.cs

@@ -0,0 +1,53 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Security
+{
+    public class AuthorizationMiddlewareBenchmark
+    {
+        private AuthorizationMiddleware _authorizationMiddleware;
+        private DefaultHttpContext _httpContextNoEndpoint;
+        private DefaultHttpContext _httpContextHasEndpoint;
+
+        [GlobalSetup]
+        public void Setup()
+        {
+            var policyProvider = new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions()));
+            _authorizationMiddleware = new AuthorizationMiddleware((context) => Task.CompletedTask, policyProvider);
+
+            _httpContextNoEndpoint = new DefaultHttpContext();
+
+            var feature = new EndpointFeature
+            {
+                Endpoint = new Endpoint((context) => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint")
+            };
+            _httpContextHasEndpoint = new DefaultHttpContext();
+            _httpContextHasEndpoint.Features.Set<IEndpointFeature>(feature);
+        }
+
+        [Benchmark]
+        public Task Invoke_NoEndpoint_NoAuthorization()
+        {
+            return _authorizationMiddleware.Invoke(_httpContextNoEndpoint);
+        }
+
+        [Benchmark]
+        public Task Invoke_HasEndpoint_NoAuthorization()
+        {
+            return _authorizationMiddleware.Invoke(_httpContextHasEndpoint);
+        }
+
+        private class EndpointFeature : IEndpointFeature
+        {
+            public Endpoint Endpoint { get; set; }
+        }
+    }
+}

+ 28 - 0
src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/AuthorizationPolicyBenchmark.cs

@@ -0,0 +1,28 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Security
+{
+    public class AuthorizationPolicyBenchmark
+    {
+        private DefaultAuthorizationPolicyProvider _policyProvider;
+
+        [GlobalSetup]
+        public void Setup()
+        {
+            _policyProvider = new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions()));
+        }
+
+        [Benchmark]
+        public Task CombineAsync()
+        {
+            return AuthorizationPolicy.CombineAsync(_policyProvider, Array.Empty<IAuthorizeData>());
+        }
+    }
+}

+ 24 - 0
src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/Microsoft.AspNetCore.Security.Performance.csproj

@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+    <OutputType>Exe</OutputType>
+    <ServerGarbageCollection>true</ServerGarbageCollection>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <IsPackable>false</IsPackable>
+    <RootNamespace>Microsoft.AspNetCore.Security</RootNamespace>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authorization.Policy\Microsoft.AspNetCore.Authorization.Policy.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="BenchmarkDotNet" Version="$(BenchmarkDotNetPackageVersion)" />
+    <PackageReference Include="Microsoft.AspNetCore.BenchmarkRunner.Sources" PrivateAssets="All" Version="$(MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion)" />
+    <PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpPackageVersion)" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
+    <PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
+  </ItemGroup>
+
+</Project>

+ 1 - 0
src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/Properties/AssemblyInfo.cs

@@ -0,0 +1 @@
+[assembly: BenchmarkDotNet.Attributes.AspNetCoreBenchmark]

+ 16 - 0
src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/readme.md

@@ -0,0 +1,16 @@
+Compile the solution in Release mode (so binaries are available in release)
+
+To run a specific benchmark add it as parameter.
+```
+dotnet run -c Release --framework <tfm> <benchmark_name>
+```
+
+To run all benchmarks use '*' as the name.
+```
+dotnet run -c Release --framework <tfm> *
+```
+
+If you run without any parameters, you'll be offered the list of all benchmarks and get to choose.
+```
+dotnet run -c Release --framework <tfm> 
+```

+ 38 - 34
src/Security/build/dependencies.props

@@ -3,50 +3,54 @@
     <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
   </PropertyGroup>
   <PropertyGroup Label="Package Versions">
-    <InternalAspNetCoreSdkPackageVersion>2.2.0-preview2-20181004.6</InternalAspNetCoreSdkPackageVersion>
-    <MicrosoftAspNetCoreAuthenticationAbstractionsPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreAuthenticationAbstractionsPackageVersion>
-    <MicrosoftAspNetCoreAuthenticationCorePackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreAuthenticationCorePackageVersion>
-    <MicrosoftAspNetCoreDataProtectionExtensionsPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreDataProtectionExtensionsPackageVersion>
-    <MicrosoftAspNetCoreDataProtectionPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreDataProtectionPackageVersion>
-    <MicrosoftAspNetCoreDiagnosticsPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreDiagnosticsPackageVersion>
-    <MicrosoftAspNetCoreHostingPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreHostingPackageVersion>
-    <MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
-    <MicrosoftAspNetCoreHttpPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreHttpPackageVersion>
-    <MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
-    <MicrosoftAspNetCoreServerKestrelHttpsPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreServerKestrelHttpsPackageVersion>
-    <MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreServerKestrelPackageVersion>
-    <MicrosoftAspNetCoreStaticFilesPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreStaticFilesPackageVersion>
-    <MicrosoftAspNetCoreTestHostPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreTestHostPackageVersion>
-    <MicrosoftAspNetCoreTestingPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreTestingPackageVersion>
-    <MicrosoftExtensionsCachingMemoryPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsCachingMemoryPackageVersion>
-    <MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion>
-    <MicrosoftExtensionsConfigurationUserSecretsPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsConfigurationUserSecretsPackageVersion>
-    <MicrosoftExtensionsDependencyInjectionPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsDependencyInjectionPackageVersion>
-    <MicrosoftExtensionsFileProvidersEmbeddedPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsFileProvidersEmbeddedPackageVersion>
-    <MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
-    <MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsLoggingConsolePackageVersion>
-    <MicrosoftExtensionsLoggingDebugPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsLoggingDebugPackageVersion>
-    <MicrosoftExtensionsLoggingPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsLoggingPackageVersion>
-    <MicrosoftExtensionsOptionsPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsOptionsPackageVersion>
-    <MicrosoftExtensionsSecurityHelperSourcesPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsSecurityHelperSourcesPackageVersion>
-    <MicrosoftExtensionsWebEncodersPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsWebEncodersPackageVersion>
-    <MicrosoftIdentityModelClientsActiveDirectoryPackageVersion>3.14.2</MicrosoftIdentityModelClientsActiveDirectoryPackageVersion>
-    <MicrosoftIdentityModelProtocolsOpenIdConnectPackageVersion>5.2.0</MicrosoftIdentityModelProtocolsOpenIdConnectPackageVersion>
-    <MicrosoftIdentityModelProtocolsWsFederationPackageVersion>5.2.0</MicrosoftIdentityModelProtocolsWsFederationPackageVersion>
-    <MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion>
-    <MicrosoftNETCoreApp21PackageVersion>2.1.3</MicrosoftNETCoreApp21PackageVersion>
-    <MicrosoftNETCoreApp22PackageVersion>2.2.0-preview3-27001-02</MicrosoftNETCoreApp22PackageVersion>
+    <BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
+    <InternalAspNetCoreSdkPackageVersion>3.0.0-build-20181114.5</InternalAspNetCoreSdkPackageVersion>
+    <MicrosoftAspNetCoreAuthenticationAbstractionsPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreAuthenticationAbstractionsPackageVersion>
+    <MicrosoftAspNetCoreAuthenticationCorePackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreAuthenticationCorePackageVersion>
+    <MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>3.0.0-preview-181113-11</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
+    <MicrosoftAspNetCoreDataProtectionExtensionsPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreDataProtectionExtensionsPackageVersion>
+    <MicrosoftAspNetCoreDataProtectionPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreDataProtectionPackageVersion>
+    <MicrosoftAspNetCoreDiagnosticsPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreDiagnosticsPackageVersion>
+    <MicrosoftAspNetCoreHostingPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreHostingPackageVersion>
+    <MicrosoftAspNetCoreHttpAbstractionsPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
+    <MicrosoftAspNetCoreHttpExtensionsPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
+    <MicrosoftAspNetCoreHttpPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreHttpPackageVersion>
+    <MicrosoftAspNetCoreRoutingPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreRoutingPackageVersion>
+    <MicrosoftAspNetCoreServerIISIntegrationPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
+    <MicrosoftAspNetCoreServerKestrelHttpsPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreServerKestrelHttpsPackageVersion>
+    <MicrosoftAspNetCoreServerKestrelPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreServerKestrelPackageVersion>
+    <MicrosoftAspNetCoreStaticFilesPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreStaticFilesPackageVersion>
+    <MicrosoftAspNetCoreTestHostPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreTestHostPackageVersion>
+    <MicrosoftAspNetCoreTestingPackageVersion>3.0.0-preview-181113-11</MicrosoftAspNetCoreTestingPackageVersion>
+    <MicrosoftExtensionsCachingMemoryPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsCachingMemoryPackageVersion>
+    <MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion>
+    <MicrosoftExtensionsConfigurationUserSecretsPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsConfigurationUserSecretsPackageVersion>
+    <MicrosoftExtensionsDependencyInjectionPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsDependencyInjectionPackageVersion>
+    <MicrosoftExtensionsFileProvidersEmbeddedPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsFileProvidersEmbeddedPackageVersion>
+    <MicrosoftExtensionsLoggingAbstractionsPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
+    <MicrosoftExtensionsLoggingConsolePackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsLoggingConsolePackageVersion>
+    <MicrosoftExtensionsLoggingDebugPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsLoggingDebugPackageVersion>
+    <MicrosoftExtensionsLoggingPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsLoggingPackageVersion>
+    <MicrosoftExtensionsOptionsPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsOptionsPackageVersion>
+    <MicrosoftExtensionsSecurityHelperSourcesPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsSecurityHelperSourcesPackageVersion>
+    <MicrosoftExtensionsWebEncodersPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsWebEncodersPackageVersion>
+    <MicrosoftIdentityModelClientsActiveDirectoryPackageVersion>3.19.8</MicrosoftIdentityModelClientsActiveDirectoryPackageVersion>
+    <MicrosoftIdentityModelProtocolsOpenIdConnectPackageVersion>5.3.0</MicrosoftIdentityModelProtocolsOpenIdConnectPackageVersion>
+    <MicrosoftIdentityModelProtocolsWsFederationPackageVersion>5.3.0</MicrosoftIdentityModelProtocolsWsFederationPackageVersion>
+    <MicrosoftNETCoreAppPackageVersion>3.0.0-preview1-26907-05</MicrosoftNETCoreAppPackageVersion>
     <MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
     <MicrosoftOwinSecurityCookiesPackageVersion>3.0.1</MicrosoftOwinSecurityCookiesPackageVersion>
     <MicrosoftOwinSecurityPackageVersion>3.0.1</MicrosoftOwinSecurityPackageVersion>
     <MicrosoftOwinTestingPackageVersion>3.0.1</MicrosoftOwinTestingPackageVersion>
+    <MoqPackageVersion>4.10.0</MoqPackageVersion>
     <NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
     <NewtonsoftJsonPackageVersion>11.0.2</NewtonsoftJsonPackageVersion>
-    <SystemIdentityModelTokensJwtPackageVersion>5.2.0</SystemIdentityModelTokensJwtPackageVersion>
+    <SystemIdentityModelTokensJwtPackageVersion>5.3.0</SystemIdentityModelTokensJwtPackageVersion>
     <XunitAnalyzersPackageVersion>0.10.0</XunitAnalyzersPackageVersion>
     <XunitPackageVersion>2.3.1</XunitPackageVersion>
     <XunitRunnerVisualStudioPackageVersion>2.4.0</XunitRunnerVisualStudioPackageVersion>
   </PropertyGroup>
+  <PropertyGroup Label="Package Versions: Pinned" />
   <Import Project="$(DotNetPackageVersionPropsPath)" Condition=" '$(DotNetPackageVersionPropsPath)' != '' " />
   <PropertyGroup Label="Package Versions: Pinned" />
 </Project>

+ 1 - 7
src/Security/build/repo.props

@@ -1,19 +1,13 @@
 <Project>
   <Import Project="dependencies.props" />
 
-  <ItemGroup>
-    <ExcludeFromTest Include="$(RepositoryRoot)test\Microsoft.Owin.Security.Interop.Test\*.csproj" Condition="'$(OS)' != 'Windows_NT'" />
-  </ItemGroup>
   <PropertyGroup>
     <!-- These properties are use by the automation that updates dependencies.props -->
     <LineupPackageId>Internal.AspNetCore.Universe.Lineup</LineupPackageId>
-    <LineupPackageVersion>2.2.0-*</LineupPackageVersion>
     <LineupPackageRestoreSource>https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json</LineupPackageRestoreSource>
   </PropertyGroup>
 
   <ItemGroup>
-    <DotNetCoreRuntime Include="$(MicrosoftNETCoreApp20PackageVersion)" />
-    <DotNetCoreRuntime Include="$(MicrosoftNETCoreApp21PackageVersion)" />
-    <DotNetCoreRuntime Include="$(MicrosoftNETCoreApp22PackageVersion)" />
+    <DotNetCoreRuntime Include="$(MicrosoftNETCoreAppPackageVersion)" />
   </ItemGroup>
 </Project>

+ 1 - 1
src/Security/samples/CookiePolicySample/CookiePolicySample.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
-    <TargetFrameworks>net461;netcoreapp2.2</TargetFrameworks>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
   </PropertyGroup>
 
   <ItemGroup>

+ 1 - 1
src/Security/samples/CookieSample/CookieSample.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
-    <TargetFrameworks>net461;netcoreapp2.2</TargetFrameworks>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
   </PropertyGroup>
 
   <ItemGroup>

+ 1 - 1
src/Security/samples/CookieSessionSample/CookieSessionSample.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
-    <TargetFrameworks>net461;netcoreapp2.2</TargetFrameworks>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
   </PropertyGroup>
 
   <ItemGroup>

+ 1 - 1
src/Security/samples/JwtBearerSample/JwtBearerSample.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
-    <TargetFrameworks>net461;netcoreapp2.2</TargetFrameworks>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <UserSecretsId>aspnet5-JwtBearerSample-20151210102827</UserSecretsId>
  </PropertyGroup>
 

+ 1 - 0
src/Security/samples/JwtBearerSample/wwwroot/index.html

@@ -64,5 +64,6 @@
         <script src="App/Scripts/userDataCtrl.js"></script>
         <script src="App/Scripts/todoListCtrl.js"></script>
         <script src="App/Scripts/todoListSvc.js"></script>
+    </div>
 </body>
 </html>

+ 1 - 1
src/Security/samples/OpenIdConnect.AzureAdSample/OpenIdConnect.AzureAdSample.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
-    <TargetFrameworks>net461;netcoreapp2.2</TargetFrameworks>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <UserSecretsId>aspnet5-OpenIdConnectSample-20151210110318</UserSecretsId>
   </PropertyGroup>
 

+ 1 - 1
src/Security/samples/OpenIdConnectSample/OpenIdConnectSample.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
-    <TargetFrameworks>net461;netcoreapp2.2</TargetFrameworks>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <UserSecretsId>aspnet5-OpenIdConnectSample-20151210110318</UserSecretsId>
   </PropertyGroup>
 

+ 11 - 0
src/Security/samples/OpenIdConnectSample/Startup.cs

@@ -63,6 +63,7 @@ namespace OpenIdConnectSample
                 o.ResponseType = OpenIdConnectResponseType.CodeIdToken;
                 o.SaveTokens = true;
                 o.GetClaimsFromUserInfoEndpoint = true;
+                o.AccessDeniedPath = "/access-denied-from-remote";
 
                 o.ClaimActions.MapAllExcept("aud", "iss", "iat", "nbf", "exp", "aio", "c_hash", "uti", "nonce");
 
@@ -126,6 +127,16 @@ namespace OpenIdConnectSample
                     return;
                 }
 
+                if (context.Request.Path.Equals("/access-denied-from-remote"))
+                {
+                    await WriteHtmlAsync(response, async res =>
+                    {
+                        await res.WriteAsync($"<h1>Access Denied error received from the remote authorization server</h1>");
+                        await res.WriteAsync("<a class=\"btn btn-default\" href=\"/\">Home</a>");
+                    });
+                    return;
+                }
+
                 if (context.Request.Path.Equals("/Account/AccessDenied"))
                 {
                     await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

+ 1 - 1
src/Security/samples/SocialSample/SocialSample.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
-    <TargetFrameworks>net461;netcoreapp2.2</TargetFrameworks>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <UserSecretsId>aspnet5-SocialSample-20151210111056</UserSecretsId>
   </PropertyGroup>
 

+ 1 - 1
src/Security/samples/WsFedSample/WsFedSample.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
-    <TargetFrameworks>net461</TargetFrameworks>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
   </PropertyGroup>
 
   <ItemGroup>

+ 2 - 2
src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs

@@ -422,7 +422,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
             var returnUrl = properties.RedirectUri;
             if (string.IsNullOrEmpty(returnUrl))
             {
-                returnUrl = OriginalPathBase + Request.Path + Request.QueryString;
+                returnUrl = OriginalPathBase + OriginalPath + Request.QueryString;
             }
             var accessDeniedUri = Options.AccessDeniedPath + QueryString.Create(Options.ReturnUrlParameter, returnUrl);
             var redirectContext = new RedirectContext<CookieAuthenticationOptions>(Context, Scheme, Options, properties, BuildRedirectUri(accessDeniedUri));
@@ -434,7 +434,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
             var redirectUri = properties.RedirectUri;
             if (string.IsNullOrEmpty(redirectUri))
             {
-                redirectUri = OriginalPathBase + Request.Path + Request.QueryString;
+                redirectUri = OriginalPathBase + OriginalPath + Request.QueryString;
             }
 
             var loginUri = Options.LoginPath + QueryString.Create(Options.ReturnUrlParameter, redirectUri);

+ 0 - 63
src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationOptions.cs

@@ -147,68 +147,5 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
         /// </para>
         /// </summary>
         public TimeSpan ExpireTimeSpan { get; set; }
-
-        #region Obsolete API
-        /// <summary>
-        /// <para>
-        /// This property is obsolete and will be removed in a future version. The recommended alternative is <seealso cref="CookieBuilder.Name"/> on <see cref="Cookie"/>.
-        /// </para>
-        /// <para>
-        /// Determines the cookie name used to persist the identity. The default value is ".AspNetCore.Cookies".
-        /// This value should be changed if you change the name of the AuthenticationScheme, especially if your
-        /// system uses the cookie authentication handler multiple times.
-        /// </para>
-        /// </summary>
-        [Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is " + nameof(Cookie) + "." + nameof(CookieBuilder.Name) + ".")]
-        public string CookieName { get => Cookie.Name; set => Cookie.Name = value; }
-
-        /// <summary>
-        /// <para>
-        /// This property is obsolete and will be removed in a future version. The recommended alternative is <seealso cref="CookieBuilder.Domain"/> on <see cref="Cookie"/>.
-        /// </para>
-        /// <para>
-        /// Determines the domain used to create the cookie. Is not provided by default.
-        /// </para>
-        /// </summary>
-        [Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is " + nameof(Cookie) + "." + nameof(CookieBuilder.Domain) + ".")]
-        public string CookieDomain { get => Cookie.Domain; set => Cookie.Domain = value; }
-
-        /// <summary>
-        /// <para>
-        /// This property is obsolete and will be removed in a future version. The recommended alternative is <seealso cref="CookieBuilder.Path"/> on <see cref="Cookie"/>.
-        /// </para>
-        /// <para>
-        /// Determines the path used to create the cookie. The default value is "/" for highest browser compatibility.
-        /// </para>
-        /// </summary>
-        [Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is " + nameof(Cookie) + "." + nameof(CookieBuilder.Path) + ".")]
-        public string CookiePath { get => Cookie.Path; set => Cookie.Path = value; }
-
-        /// <summary>
-        /// <para>
-        /// This property is obsolete and will be removed in a future version. The recommended alternative is <seealso cref="CookieBuilder.HttpOnly"/> on <see cref="Cookie"/>.
-        /// </para>
-        /// <para>
-        /// Determines if the browser should allow the cookie to be accessed by client-side javascript. The
-        /// default is true, which means the cookie will only be passed to http requests and is not made available
-        /// to script on the page.
-        /// </para>
-        /// </summary>
-        [Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is " + nameof(Cookie) + "." + nameof(CookieBuilder.HttpOnly) + ".")]
-        public bool CookieHttpOnly { get => Cookie.HttpOnly; set => Cookie.HttpOnly = value; }
-
-        /// <summary>
-        /// <para>
-        /// This property is obsolete and will be removed in a future version. The recommended alternative is <seealso cref="CookieBuilder.SecurePolicy"/> on <see cref="Cookie"/>.
-        /// </para>
-        /// <para>
-        /// Determines if the cookie should only be transmitted on HTTPS request. The default is to limit the cookie
-        /// to HTTPS requests if the page which is doing the SignIn is also HTTPS. If you have an HTTPS sign in page
-        /// and portions of your site are HTTP you may need to change this value.
-        /// </para>
-        /// </summary>
-        [Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is " + nameof(Cookie) + "." + nameof(CookieBuilder.SecurePolicy) + ".")]
-        public CookieSecurePolicy CookieSecure { get => Cookie.SecurePolicy; set => Cookie.SecurePolicy = value; }
-        #endregion
     }
 }

+ 1 - 1
src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/Microsoft.AspNetCore.Authentication.Cookies.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <Description>ASP.NET Core middleware that enables an application to use cookie based authentication.</Description>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <DefineConstants>$(DefineConstants);SECURITY</DefineConstants>
     <NoWarn>$(NoWarn);CS1591</NoWarn>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>

+ 52 - 0
src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/breakingchanges.netcore.json

@@ -0,0 +1,52 @@
+  [
+    {
+      "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions",
+      "MemberId": "public Microsoft.AspNetCore.Http.CookieSecurePolicy get_CookieSecure()",
+      "Kind": "Removal"
+    },
+    {
+      "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions",
+      "MemberId": "public System.Boolean get_CookieHttpOnly()",
+      "Kind": "Removal"
+    },
+    {
+      "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions",
+      "MemberId": "public System.String get_CookieDomain()",
+      "Kind": "Removal"
+    },
+    {
+      "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions",
+      "MemberId": "public System.String get_CookieName()",
+      "Kind": "Removal"
+    },
+    {
+      "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions",
+      "MemberId": "public System.String get_CookiePath()",
+      "Kind": "Removal"
+    },
+    {
+      "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions",
+      "MemberId": "public System.Void set_CookieDomain(System.String value)",
+      "Kind": "Removal"
+    },
+    {
+      "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions",
+      "MemberId": "public System.Void set_CookieHttpOnly(System.Boolean value)",
+      "Kind": "Removal"
+    },
+    {
+      "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions",
+      "MemberId": "public System.Void set_CookieName(System.String value)",
+      "Kind": "Removal"
+    },
+    {
+      "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions",
+      "MemberId": "public System.Void set_CookiePath(System.String value)",
+      "Kind": "Removal"
+    },
+    {
+      "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions",
+      "MemberId": "public System.Void set_CookieSecure(Microsoft.AspNetCore.Http.CookieSecurePolicy value)",
+      "Kind": "Removal"
+    }
+  ]

+ 1 - 1
src/Security/src/Microsoft.AspNetCore.Authentication.Facebook/Microsoft.AspNetCore.Authentication.Facebook.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <Description>ASP.NET Core middleware that enables an application to support Facebook's OAuth 2.0 authentication workflow.</Description>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <NoWarn>$(NoWarn);CS1591</NoWarn>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PackageTags>aspnetcore;authentication;security</PackageTags>

+ 1 - 1
src/Security/src/Microsoft.AspNetCore.Authentication.Google/Microsoft.AspNetCore.Authentication.Google.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <Description>ASP.NET Core contains middleware to support Google's OpenId and OAuth 2.0 authentication workflows.</Description>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <NoWarn>$(NoWarn);CS1591</NoWarn>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PackageTags>aspnetcore;authentication;security</PackageTags>

+ 1 - 1
src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs

@@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
     {
         private OpenIdConnectConfiguration _configuration;
 
-        public JwtBearerHandler(IOptionsMonitor<JwtBearerOptions> options, ILoggerFactory logger, UrlEncoder encoder, IDataProtectionProvider dataProtection, ISystemClock clock)
+        public JwtBearerHandler(IOptionsMonitor<JwtBearerOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
             : base(options, logger, encoder, clock)
         { }
 

+ 1 - 1
src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/Microsoft.AspNetCore.Authentication.JwtBearer.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <Description>ASP.NET Core middleware that enables an application to receive an OpenID Connect bearer token.</Description>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <NoWarn>$(NoWarn);CS1591</NoWarn>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PackageTags>aspnetcore;authentication;security</PackageTags>

+ 7 - 0
src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/breakingchanges.netcore.json

@@ -0,0 +1,7 @@
+  [
+    {
+      "TypeId": "public class Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler : Microsoft.AspNetCore.Authentication.AuthenticationHandler<Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions>",
+      "MemberId": "public .ctor(Microsoft.Extensions.Options.IOptionsMonitor<Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions> options, Microsoft.Extensions.Logging.ILoggerFactory logger, System.Text.Encodings.Web.UrlEncoder encoder, Microsoft.AspNetCore.DataProtection.IDataProtectionProvider dataProtection, Microsoft.AspNetCore.Authentication.ISystemClock clock)",
+      "Kind": "Removal"
+    }
+  ]

+ 1 - 1
src/Security/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/Microsoft.AspNetCore.Authentication.MicrosoftAccount.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <Description>ASP.NET Core middleware that enables an application to support the Microsoft Account authentication workflow.</Description>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <NoWarn>$(NoWarn);CS1591</NoWarn>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PackageTags>aspnetcore;authentication;security</PackageTags>

+ 1 - 1
src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/Microsoft.AspNetCore.Authentication.OAuth.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <Description>ASP.NET Core middleware that enables an application to support any standard OAuth 2.0 authentication workflow.</Description>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <NoWarn>$(NoWarn);CS1591</NoWarn>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PackageTags>aspnetcore;authentication;security</PackageTags>

+ 11 - 1
src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs

@@ -63,6 +63,16 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
             var error = query["error"];
             if (!StringValues.IsNullOrEmpty(error))
             {
+                // Note: access_denied errors are special protocol errors indicating the user didn't
+                // approve the authorization demand requested by the remote authorization server.
+                // Since it's a frequent scenario (that is not caused by incorrect configuration),
+                // denied errors are handled differently using HandleAccessDeniedErrorAsync().
+                // Visit https://tools.ietf.org/html/rfc6749#section-4.1.2.1 for more information.
+                if (StringValues.Equals(error, "access_denied"))
+                {
+                    return await HandleAccessDeniedErrorAsync(properties);
+                }
+
                 var failureMessage = new StringBuilder();
                 failureMessage.Append(error);
                 var errorDescription = query["error_description"];
@@ -194,7 +204,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
         {
             if (string.IsNullOrEmpty(properties.RedirectUri))
             {
-                properties.RedirectUri = CurrentUri;
+                properties.RedirectUri = OriginalPathBase + OriginalPath + Request.QueryString;
             }
 
             // OAuth2 10.12 CSRF

+ 2 - 4
src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthOptions.cs

@@ -3,11 +3,9 @@
 
 using System;
 using System.Collections.Generic;
-using Microsoft.AspNetCore.Authentication;
-using Microsoft.AspNetCore.Authentication.OAuth;
-using Microsoft.AspNetCore.Authentication.OAuth.Claims;
-using Microsoft.AspNetCore.Http.Authentication;
 using System.Globalization;
+using Microsoft.AspNetCore.Authentication.OAuth.Claims;
+using Microsoft.AspNetCore.Http;
 
 namespace Microsoft.AspNetCore.Authentication.OAuth
 {

+ 1 - 1
src/Security/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Microsoft.AspNetCore.Authentication.OpenIdConnect.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <Description>ASP.NET Core middleware that enables an application to support the OpenID Connect authentication workflow.</Description>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <NoWarn>$(NoWarn);CS1591</NoWarn>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PackageTags>aspnetcore;authentication;security</PackageTags>

+ 12 - 2
src/Security/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs

@@ -186,7 +186,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
                 properties.RedirectUri = BuildRedirectUriIfRelative(Options.SignedOutRedirectUri);
                 if (string.IsNullOrWhiteSpace(properties.RedirectUri))
                 {
-                    properties.RedirectUri = CurrentUri;
+                    properties.RedirectUri = OriginalPathBase + OriginalPath + Request.QueryString;
                 }
             }
             Logger.PostSignOutRedirect(properties.RedirectUri);
@@ -312,7 +312,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
             // 2. CurrentUri if RedirectUri is not set)
             if (string.IsNullOrEmpty(properties.RedirectUri))
             {
-                properties.RedirectUri = CurrentUri;
+                properties.RedirectUri = OriginalPathBase + OriginalPath + Request.QueryString;
             }
             Logger.PostAuthenticationLocalRedirect(properties.RedirectUri);
 
@@ -520,6 +520,16 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
                 // if any of the error fields are set, throw error null
                 if (!string.IsNullOrEmpty(authorizationResponse.Error))
                 {
+                    // Note: access_denied errors are special protocol errors indicating the user didn't
+                    // approve the authorization demand requested by the remote authorization server.
+                    // Since it's a frequent scenario (that is not caused by incorrect configuration),
+                    // denied errors are handled differently using HandleAccessDeniedErrorAsync().
+                    // Visit https://tools.ietf.org/html/rfc6749#section-4.1.2.1 for more information.
+                    if (string.Equals(authorizationResponse.Error, "access_denied", StringComparison.Ordinal))
+                    {
+                        return await HandleAccessDeniedErrorAsync(properties);
+                    }
+
                     return HandleRequestResult.Fail(CreateOpenIdConnectProtocolException(authorizationResponse, response: null), properties);
                 }
 

+ 1 - 1
src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/Microsoft.AspNetCore.Authentication.Twitter.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <Description>ASP.NET Core middleware that enables an application to support Twitter's OAuth 1.0 authentication workflow.</Description>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <NoWarn>$(NoWarn);CS1591</NoWarn>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PackageTags>aspnetcore;authentication;security</PackageTags>

+ 6 - 4
src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs

@@ -55,12 +55,14 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
 
             var properties = requestToken.Properties;
 
-            // REVIEW: see which of these are really errors
-
             var denied = query["denied"];
             if (!StringValues.IsNullOrEmpty(denied))
             {
-                return HandleRequestResult.Fail("The user denied permissions.", properties);
+                // Note: denied errors are special protocol errors indicating the user didn't
+                // approve the authorization demand requested by the remote authorization server.
+                // Since it's a frequent scenario (that is not caused by incorrect configuration),
+                // denied errors are handled differently using HandleAccessDeniedErrorAsync().
+                return await HandleAccessDeniedErrorAsync(properties);
             }
 
             var returnedToken = query["oauth_token"];
@@ -130,7 +132,7 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
         {
             if (string.IsNullOrEmpty(properties.RedirectUri))
             {
-                properties.RedirectUri = CurrentUri;
+                properties.RedirectUri = OriginalPathBase + OriginalPath + Request.QueryString;
             }
 
             // If CallbackConfirmed is false, this will throw

+ 1 - 1
src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterOptions.cs

@@ -2,8 +2,8 @@
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
 using System;
-using System.Security.Claims;
 using System.Globalization;
+using System.Security.Claims;
 using Microsoft.AspNetCore.Authentication.OAuth.Claims;
 using Microsoft.AspNetCore.Http;
 

+ 1 - 1
src/Security/src/Microsoft.AspNetCore.Authentication.WsFederation/Microsoft.AspNetCore.Authentication.WsFederation.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <Description>ASP.NET Core middleware that enables an application to support the WsFederation authentication workflow.</Description>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PackageTags>aspnetcore;authentication;security</PackageTags>
   </PropertyGroup>

+ 1 - 1
src/Security/src/Microsoft.AspNetCore.Authentication.WsFederation/WsFederationHandler.cs

@@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Authentication.WsFederation
             // Save the original challenge URI so we can redirect back to it when we're done.
             if (string.IsNullOrEmpty(properties.RedirectUri))
             {
-                properties.RedirectUri = CurrentUri;
+                properties.RedirectUri = OriginalPathBase + OriginalPath + Request.QueryString;
             }
 
             var wsFederationMessage = new WsFederationMessage()

+ 0 - 42
src/Security/src/Microsoft.AspNetCore.Authentication/AuthenticationServiceCollectionExtensions.cs

@@ -46,48 +46,6 @@ namespace Microsoft.Extensions.DependencyInjection
             return builder;
         }
 
-        [Obsolete("AddScheme is obsolete. Use AddAuthentication().AddScheme instead.")]
-        public static IServiceCollection AddScheme<TOptions, THandler>(this IServiceCollection services, string authenticationScheme, string displayName, Action<AuthenticationSchemeBuilder> configureScheme, Action<TOptions> configureOptions)
-            where TOptions : AuthenticationSchemeOptions, new()
-            where THandler : AuthenticationHandler<TOptions>
-        {
-            services.AddAuthentication(o =>
-            {
-                o.AddScheme(authenticationScheme, scheme => {
-                    scheme.HandlerType = typeof(THandler);
-                    scheme.DisplayName = displayName;
-                    configureScheme?.Invoke(scheme);
-                });
-            });
-            if (configureOptions != null)
-            {
-                services.Configure(authenticationScheme, configureOptions);
-            }
-            services.AddTransient<THandler>();
-            return services;
-        }
-
-        [Obsolete("AddScheme is obsolete. Use AddAuthentication().AddScheme instead.")]
-        public static IServiceCollection AddScheme<TOptions, THandler>(this IServiceCollection services, string authenticationScheme, Action<TOptions> configureOptions)
-            where TOptions : AuthenticationSchemeOptions, new()
-            where THandler : AuthenticationHandler<TOptions>
-            => services.AddScheme<TOptions, THandler>(authenticationScheme, displayName: null, configureScheme: null, configureOptions: configureOptions);
-
-        [Obsolete("AddScheme is obsolete. Use AddAuthentication().AddScheme instead.")]
-        public static IServiceCollection AddScheme<TOptions, THandler>(this IServiceCollection services, string authenticationScheme, string displayName, Action<TOptions> configureOptions)
-            where TOptions : AuthenticationSchemeOptions, new()
-            where THandler : AuthenticationHandler<TOptions>
-            => services.AddScheme<TOptions, THandler>(authenticationScheme, displayName, configureScheme: null, configureOptions: configureOptions);
-
-        [Obsolete("AddScheme is obsolete. Use AddAuthentication().AddScheme instead.")]
-        public static IServiceCollection AddRemoteScheme<TOptions, THandler>(this IServiceCollection services, string authenticationScheme, string displayName, Action<TOptions> configureOptions)
-            where TOptions : RemoteAuthenticationOptions, new()
-            where THandler : RemoteAuthenticationHandler<TOptions>
-        {
-            services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<TOptions>, EnsureSignInScheme<TOptions>>());
-            return services.AddScheme<TOptions, THandler>(authenticationScheme, displayName, configureScheme: null, configureOptions: configureOptions);
-        }
-
         // Used to ensure that there's always a sign in scheme
         private class EnsureSignInScheme<TOptions> : IPostConfigureOptions<TOptions> where TOptions : RemoteAuthenticationOptions
         {

+ 44 - 0
src/Security/src/Microsoft.AspNetCore.Authentication/Events/AccessDeniedContext.cs

@@ -0,0 +1,44 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Provides access denied failure context information to handler providers.
+    /// </summary>
+    public class AccessDeniedContext : HandleRequestContext<RemoteAuthenticationOptions>
+    {
+        public AccessDeniedContext(
+            HttpContext context,
+            AuthenticationScheme scheme,
+            RemoteAuthenticationOptions options)
+            : base(context, scheme, options)
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the endpoint path the user agent will be redirected to.
+        /// By default, this property is set to <see cref="RemoteAuthenticationOptions.AccessDeniedPath"/>.
+        /// </summary>
+        public PathString AccessDeniedPath { get; set; }
+
+        /// <summary>
+        /// Additional state values for the authentication session.
+        /// </summary>
+        public AuthenticationProperties Properties { get; set; }
+
+        /// <summary>
+        /// Gets or sets the return URL that will be flowed up to the access denied page.
+        /// If <see cref="ReturnUrlParameter"/> is not set, this property is not used.
+        /// </summary>
+        public string ReturnUrl { get; set; }
+
+        /// <summary>
+        /// Gets or sets the parameter name that will be used to flow the return URL.
+        /// By default, this property is set to <see cref="RemoteAuthenticationOptions.ReturnUrlParameter"/>.
+        /// </summary>
+        public string ReturnUrlParameter { get; set; }
+    }
+}

+ 7 - 1
src/Security/src/Microsoft.AspNetCore.Authentication/Events/RemoteAuthenticationEvents.cs

@@ -8,12 +8,18 @@ namespace Microsoft.AspNetCore.Authentication
 {
     public class RemoteAuthenticationEvents
     {
+        public Func<AccessDeniedContext, Task> OnAccessDenied { get; set; } = context => Task.CompletedTask;
         public Func<RemoteFailureContext, Task> OnRemoteFailure { get; set; } = context => Task.CompletedTask;
 
         public Func<TicketReceivedContext, Task> OnTicketReceived { get; set; } = context => Task.CompletedTask;
 
         /// <summary>
-        /// Invoked when there is a remote failure
+        /// Invoked when an access denied error was returned by the remote server.
+        /// </summary>
+        public virtual Task AccessDenied(AccessDeniedContext context) => OnAccessDenied(context);
+
+        /// <summary>
+        /// Invoked when there is a remote failure.
         /// </summary>
         public virtual Task RemoteFailure(RemoteFailureContext context) => OnRemoteFailure(context);
 

+ 41 - 11
src/Security/src/Microsoft.AspNetCore.Authentication/LoggingExtensions.cs

@@ -7,17 +7,20 @@ namespace Microsoft.Extensions.Logging
 {
     internal static class LoggingExtensions
     {
-        private static Action<ILogger, string, Exception> _authSchemeAuthenticated;
-        private static Action<ILogger, string, Exception> _authSchemeNotAuthenticated;
-        private static Action<ILogger, string, string, Exception> _authSchemeNotAuthenticatedWithFailure;
-        private static Action<ILogger, string, Exception> _authSchemeChallenged;
-        private static Action<ILogger, string, Exception> _authSchemeForbidden;
-        private static Action<ILogger, string, Exception> _remoteAuthenticationError;
-        private static Action<ILogger, Exception> _signInHandled;
-        private static Action<ILogger, Exception> _signInSkipped;
-        private static Action<ILogger, string, Exception> _correlationPropertyNotFound;
-        private static Action<ILogger, string, Exception> _correlationCookieNotFound;
-        private static Action<ILogger, string, string, Exception> _unexpectedCorrelationCookieValue;
+        private static readonly Action<ILogger, string, Exception> _authSchemeAuthenticated;
+        private static readonly Action<ILogger, string, Exception> _authSchemeNotAuthenticated;
+        private static readonly Action<ILogger, string, string, Exception> _authSchemeNotAuthenticatedWithFailure;
+        private static readonly Action<ILogger, string, Exception> _authSchemeChallenged;
+        private static readonly Action<ILogger, string, Exception> _authSchemeForbidden;
+        private static readonly Action<ILogger, string, Exception> _remoteAuthenticationError;
+        private static readonly Action<ILogger, Exception> _signInHandled;
+        private static readonly Action<ILogger, Exception> _signInSkipped;
+        private static readonly Action<ILogger, string, Exception> _correlationPropertyNotFound;
+        private static readonly Action<ILogger, string, Exception> _correlationCookieNotFound;
+        private static readonly Action<ILogger, string, string, Exception> _unexpectedCorrelationCookieValue;
+        private static readonly Action<ILogger, Exception> _accessDeniedError;
+        private static readonly Action<ILogger, Exception> _accessDeniedContextHandled;
+        private static readonly Action<ILogger, Exception> _accessDeniedContextSkipped;
 
         static LoggingExtensions()
         {
@@ -65,6 +68,18 @@ namespace Microsoft.Extensions.Logging
                eventId: 16,
                logLevel: LogLevel.Warning,
                formatString: "The correlation cookie value '{CorrelationCookieName}' did not match the expected value '{CorrelationCookieValue}'.");
+            _accessDeniedError = LoggerMessage.Define(
+                eventId: 17,
+                logLevel: LogLevel.Information,
+                formatString: "Access was denied by the resource owner or by the remote server.");
+            _accessDeniedContextHandled = LoggerMessage.Define(
+                eventId: 18,
+                logLevel: LogLevel.Debug,
+                formatString: "The AccessDenied event returned Handled.");
+            _accessDeniedContextSkipped = LoggerMessage.Define(
+                eventId: 19,
+                logLevel: LogLevel.Debug,
+                formatString: "The AccessDenied event returned Skipped.");
         }
 
         public static void AuthenticationSchemeAuthenticated(this ILogger logger, string authenticationScheme)
@@ -121,5 +136,20 @@ namespace Microsoft.Extensions.Logging
         {
             _unexpectedCorrelationCookieValue(logger, cookieName, cookieValue, null);
         }
+
+        public static void AccessDeniedError(this ILogger logger)
+        {
+            _accessDeniedError(logger, null);
+        }
+
+        public static void AccessDeniedContextHandled(this ILogger logger)
+        {
+            _accessDeniedContextHandled(logger, null);
+        }
+
+        public static void AccessDeniedContextSkipped(this ILogger logger)
+        {
+            _accessDeniedContextSkipped(logger, null);
+        }
     }
 }

+ 1 - 1
src/Security/src/Microsoft.AspNetCore.Authentication/Microsoft.AspNetCore.Authentication.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <Description>ASP.NET Core common types used by the various authentication middleware components.</Description>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <NoWarn>$(NoWarn);CS1591</NoWarn>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PackageTags>aspnetcore;authentication;security</PackageTags>

+ 44 - 0
src/Security/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs

@@ -5,6 +5,7 @@ using System;
 using System.Security.Cryptography;
 using System.Text.Encodings.Web;
 using System.Threading.Tasks;
+using Microsoft.AspNetCore.WebUtilities;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
 
@@ -241,5 +242,48 @@ namespace Microsoft.AspNetCore.Authentication
 
             return true;
         }
+
+        protected virtual async Task<HandleRequestResult> HandleAccessDeniedErrorAsync(AuthenticationProperties properties)
+        {
+            Logger.AccessDeniedError();
+            var context = new AccessDeniedContext(Context, Scheme, Options)
+            {
+                AccessDeniedPath = Options.AccessDeniedPath,
+                Properties = properties,
+                ReturnUrl = properties?.RedirectUri,
+                ReturnUrlParameter = Options.ReturnUrlParameter
+            };
+            await Events.AccessDenied(context);
+
+            if (context.Result != null)
+            {
+                if (context.Result.Handled)
+                {
+                    Logger.AccessDeniedContextHandled();
+                }
+                else if (context.Result.Skipped)
+                {
+                    Logger.AccessDeniedContextSkipped();
+                }
+
+                return context.Result;
+            }
+
+            // If an access denied endpoint was specified, redirect the user agent.
+            // Otherwise, invoke the RemoteFailure event for further processing.
+            if (context.AccessDeniedPath.HasValue)
+            {
+                string uri = context.AccessDeniedPath;
+                if (!string.IsNullOrEmpty(context.ReturnUrlParameter) && !string.IsNullOrEmpty(context.ReturnUrl))
+                {
+                    uri = QueryHelpers.AddQueryString(uri, context.ReturnUrlParameter, context.ReturnUrl);
+                }
+                Response.Redirect(uri);
+
+                return HandleRequestResult.Handle();
+            }
+
+            return HandleRequestResult.Fail("Access was denied by the resource owner or by the remote server.", properties);
+        }
     }
 }

+ 16 - 0
src/Security/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationOptions.cs

@@ -89,6 +89,22 @@ namespace Microsoft.AspNetCore.Authentication
         /// </summary>
         public PathString CallbackPath { get; set; }
 
+        /// <summary>
+        /// Gets or sets the optional path the user agent is redirected to if the user
+        /// doesn't approve the authorization demand requested by the remote server.
+        /// This property is not set by default. In this case, an exception is thrown
+        /// if an access_denied response is returned by the remote authorization server.
+        /// </summary>
+        public PathString AccessDeniedPath { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name of the parameter used to convey the original location
+        /// of the user before the remote challenge was triggered up to the access denied page.
+        /// This property is only used when the <see cref="AccessDeniedPath"/> is explicitly specified.
+        /// </summary>
+        // Note: this deliberately matches the default parameter name used by the cookie handler.
+        public string ReturnUrlParameter { get; set; } = "ReturnUrl";
+
         /// <summary>
         /// Gets or sets the authentication scheme corresponding to the middleware
         /// responsible of persisting user's identity after a successful authentication.

+ 22 - 0
src/Security/src/Microsoft.AspNetCore.Authentication/breakingchanges.netcore.json

@@ -0,0 +1,22 @@
+  [
+    {
+      "TypeId": "public static class Microsoft.Extensions.DependencyInjection.AuthenticationServiceCollectionExtensions",
+      "MemberId": "public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddRemoteScheme<T0, T1>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.String authenticationScheme, System.String displayName, System.Action<T0> configureOptions) where T0 : Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions, new() where T1 : Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler<T0>",
+      "Kind": "Removal"
+    },
+    {
+      "TypeId": "public static class Microsoft.Extensions.DependencyInjection.AuthenticationServiceCollectionExtensions",
+      "MemberId": "public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddScheme<T0, T1>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.String authenticationScheme, System.Action<T0> configureOptions) where T0 : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions, new() where T1 : Microsoft.AspNetCore.Authentication.AuthenticationHandler<T0>",
+      "Kind": "Removal"
+    },
+    {
+      "TypeId": "public static class Microsoft.Extensions.DependencyInjection.AuthenticationServiceCollectionExtensions",
+      "MemberId": "public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddScheme<T0, T1>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.String authenticationScheme, System.String displayName, System.Action<Microsoft.AspNetCore.Authentication.AuthenticationSchemeBuilder> configureScheme, System.Action<T0> configureOptions) where T0 : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions, new() where T1 : Microsoft.AspNetCore.Authentication.AuthenticationHandler<T0>",
+      "Kind": "Removal"
+    },
+    {
+      "TypeId": "public static class Microsoft.Extensions.DependencyInjection.AuthenticationServiceCollectionExtensions",
+      "MemberId": "public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddScheme<T0, T1>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.String authenticationScheme, System.String displayName, System.Action<T0> configureOptions) where T0 : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions, new() where T1 : Microsoft.AspNetCore.Authentication.AuthenticationHandler<T0>",
+      "Kind": "Removal"
+    }
+  ]

+ 29 - 0
src/Security/src/Microsoft.AspNetCore.Authorization.Policy/AuthorizationAppBuilderExtensions.cs

@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Authorization;
+
+namespace Microsoft.AspNetCore.Builder
+{
+    /// <summary>
+    /// Extension methods to add authorization capabilities to an HTTP application pipeline.
+    /// </summary>
+    public static class AuthorizationAppBuilderExtensions
+    {
+        /// <summary>
+        /// Adds the <see cref="AuthorizationMiddleware"/> to the specified <see cref="IApplicationBuilder"/>, which enables authorization capabilities.
+        /// </summary>
+        /// <param name="app">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        public static IApplicationBuilder UseAuthorization(this IApplicationBuilder app)
+        {
+            if (app == null)
+            {
+                throw new ArgumentNullException(nameof(app));
+            }
+
+            return app.UseMiddleware<AuthorizationMiddleware>();
+        }
+    }
+}

+ 50 - 0
src/Security/src/Microsoft.AspNetCore.Authorization.Policy/AuthorizationEndpointConventionBuilderExtensions.cs

@@ -0,0 +1,50 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Routing;
+
+namespace Microsoft.AspNetCore.Builder
+{
+    public static class AuthorizationEndpointConventionBuilderExtensions
+    {
+        public static IEndpointConventionBuilder RequireAuthorization(this IEndpointConventionBuilder builder, params IAuthorizeData[] authorizeData)
+        {
+            if (builder == null)
+            {
+                throw new ArgumentNullException(nameof(builder));
+            }
+
+            if (authorizeData == null)
+            {
+                throw new ArgumentNullException(nameof(authorizeData));
+            }
+
+            builder.Apply(endpointBuilder =>
+            {
+                foreach (var data in authorizeData)
+                {
+                    endpointBuilder.Metadata.Add(data);
+                }
+            });
+            return builder;
+        }
+
+        public static IEndpointConventionBuilder RequireAuthorization(this IEndpointConventionBuilder builder, params string[] policyNames)
+        {
+            if (builder == null)
+            {
+                throw new ArgumentNullException(nameof(builder));
+            }
+
+            if (policyNames == null)
+            {
+                throw new ArgumentNullException(nameof(policyNames));
+            }
+
+            return builder.RequireAuthorization(policyNames.Select(n => new AuthorizeAttribute(n)).ToArray());
+        }
+    }
+}

+ 104 - 0
src/Security/src/Microsoft.AspNetCore.Authorization.Policy/AuthorizationMiddleware.cs

@@ -0,0 +1,104 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authorization.Policy;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Endpoints;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Authorization
+{
+    public class AuthorizationMiddleware
+    {
+        private readonly RequestDelegate _next;
+        private readonly IAuthorizationPolicyProvider _policyProvider;
+
+        public AuthorizationMiddleware(RequestDelegate next, IAuthorizationPolicyProvider policyProvider)
+        {
+            if (next == null)
+            {
+                throw new ArgumentNullException(nameof(next));
+            }
+
+            if (policyProvider == null)
+            {
+                throw new ArgumentNullException(nameof(policyProvider));
+            }
+
+            _next = next;
+            _policyProvider = policyProvider;
+        }
+
+        public async Task Invoke(HttpContext context)
+        {
+            if (context == null)
+            {
+                throw new ArgumentNullException(nameof(context));
+            }
+
+            var endpoint = context.GetEndpoint();
+            var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();
+            var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData);
+            if (policy == null)
+            {
+                await _next(context);
+                return;
+            }
+
+            // Policy evaluator has transient lifetime so it fetched from request services instead of injecting in constructor
+            var policyEvaluator = context.RequestServices.GetRequiredService<IPolicyEvaluator>();
+
+            var authenticateResult = await policyEvaluator.AuthenticateAsync(policy, context);
+
+            // Allow Anonymous skips all authorization
+            if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)
+            {
+                await _next(context);
+                return;
+            }
+
+            // Note that the resource will be null if there is no matched endpoint
+            var authorizeResult = await policyEvaluator.AuthorizeAsync(policy, authenticateResult, context, resource: endpoint);
+
+            if (authorizeResult.Challenged)
+            {
+                if (policy.AuthenticationSchemes.Any())
+                {
+                    foreach (var scheme in policy.AuthenticationSchemes)
+                    {
+                        await context.ChallengeAsync(scheme);
+                    }
+                }
+                else
+                {
+                    await context.ChallengeAsync();
+                }
+
+                return;
+            }
+            else if (authorizeResult.Forbidden)
+            {
+                if (policy.AuthenticationSchemes.Any())
+                {
+                    foreach (var scheme in policy.AuthenticationSchemes)
+                    {
+                        await context.ForbidAsync(scheme);
+                    }
+                }
+                else
+                {
+                    await context.ForbidAsync();
+                }
+
+                return;
+            }
+
+            await _next(context);
+        }
+    }
+}

+ 4 - 2
src/Security/src/Microsoft.AspNetCore.Authorization.Policy/Microsoft.AspNetCore.Authorization.Policy.csproj

@@ -2,8 +2,8 @@
 
   <PropertyGroup>
     <Description>ASP.NET Core authorization policy helper classes.</Description>
-    <TargetFramework>netstandard2.0</TargetFramework>
-    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+    <NoWarn>$(NoWarn);CS1591;NU1605</NoWarn>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PackageTags>aspnetcore;authorization</PackageTags>
   </PropertyGroup>
@@ -14,6 +14,8 @@
 
   <ItemGroup>
     <PackageReference Include="Microsoft.AspNetCore.Authentication.Abstractions" Version="$(MicrosoftAspNetCoreAuthenticationAbstractionsPackageVersion)" />
+    <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="$(MicrosoftAspNetCoreHttpAbstractionsPackageVersion)" />
+    <PackageReference Include="Microsoft.AspNetCore.Routing" Version="$(MicrosoftAspNetCoreRoutingPackageVersion)" />
     <PackageReference Include="Microsoft.Extensions.SecurityHelper.Sources" PrivateAssets="All" Version="$(MicrosoftExtensionsSecurityHelperSourcesPackageVersion)" />
   </ItemGroup>
 

+ 14 - 2
src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationOptions.cs

@@ -20,13 +20,25 @@ namespace Microsoft.AspNetCore.Authorization
         public bool InvokeHandlersAfterFailure { get; set; } = true;
 
         /// <summary>
-        /// Gets or sets the default authorization policy.
+        /// Gets or sets the default authorization policy. Defaults to require authenticated users.
         /// </summary>
         /// <remarks>
-        /// The default policy is to require any authenticated user.
+        /// The default policy used when evaluating <see cref="IAuthorizeData"/> with no policy name specified.
         /// </remarks>
         public AuthorizationPolicy DefaultPolicy { get; set; } = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
 
+        /// <summary>
+        /// Gets or sets the required authorization policy. Defaults to null.
+        /// </summary>
+        /// <remarks>
+        /// By default the required policy is null.
+        /// 
+        /// If a required policy has been specified then it is always evaluated, even if there are no
+        /// <see cref="IAuthorizeData"/> instances for a resource. If a resource has <see cref="IAuthorizeData"/>
+        /// then they are evaluated together with the required policy.
+        /// </remarks>
+        public AuthorizationPolicy RequiredPolicy { get; set; }
+
         /// <summary>
         /// Add an authorization policy with the provided name.
         /// </summary>

+ 56 - 28
src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationPolicy.cs

@@ -120,46 +120,74 @@ namespace Microsoft.AspNetCore.Authorization
                 throw new ArgumentNullException(nameof(authorizeData));
             }
 
-            var policyBuilder = new AuthorizationPolicyBuilder();
-            var any = false;
-            foreach (var authorizeDatum in authorizeData)
+            // Avoid allocating enumerator if the data is known to be empty
+            var skipEnumeratingData = false;
+            if (authorizeData is IList<IAuthorizeData> dataList)
             {
-                any = true;
-                var useDefaultPolicy = true;
-                if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
+                skipEnumeratingData = dataList.Count == 0;
+            }
+
+            AuthorizationPolicyBuilder policyBuilder = null;
+            if (!skipEnumeratingData)
+            {
+                foreach (var authorizeDatum in authorizeData)
                 {
-                    var policy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy);
-                    if (policy == null)
+                    if (policyBuilder == null)
                     {
-                        throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeDatum.Policy));
+                        policyBuilder = new AuthorizationPolicyBuilder();
                     }
-                    policyBuilder.Combine(policy);
-                    useDefaultPolicy = false;
-                }
-                var rolesSplit = authorizeDatum.Roles?.Split(',');
-                if (rolesSplit != null && rolesSplit.Any())
-                {
-                    var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim());
-                    policyBuilder.RequireRole(trimmedRolesSplit);
-                    useDefaultPolicy = false;
-                }
-                var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(',');
-                if (authTypesSplit != null && authTypesSplit.Any())
-                {
-                    foreach (var authType in authTypesSplit)
+
+                    var useDefaultPolicy = true;
+                    if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
+                    {
+                        var policy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy);
+                        if (policy == null)
+                        {
+                            throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeDatum.Policy));
+                        }
+                        policyBuilder.Combine(policy);
+                        useDefaultPolicy = false;
+                    }
+
+                    var rolesSplit = authorizeDatum.Roles?.Split(',');
+                    if (rolesSplit != null && rolesSplit.Any())
+                    {
+                        var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim());
+                        policyBuilder.RequireRole(trimmedRolesSplit);
+                        useDefaultPolicy = false;
+                    }
+
+                    var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(',');
+                    if (authTypesSplit != null && authTypesSplit.Any())
                     {
-                        if (!string.IsNullOrWhiteSpace(authType))
+                        foreach (var authType in authTypesSplit)
                         {
-                            policyBuilder.AuthenticationSchemes.Add(authType.Trim());
+                            if (!string.IsNullOrWhiteSpace(authType))
+                            {
+                                policyBuilder.AuthenticationSchemes.Add(authType.Trim());
+                            }
                         }
                     }
+
+                    if (useDefaultPolicy)
+                    {
+                        policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());
+                    }
                 }
-                if (useDefaultPolicy)
+            }
+
+            var requiredPolicy = await policyProvider.GetRequiredPolicyAsync();
+            if (requiredPolicy != null)
+            {
+                if (policyBuilder == null)
                 {
-                    policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());
+                    policyBuilder = new AuthorizationPolicyBuilder();
                 }
+
+                policyBuilder.Combine(requiredPolicy);
             }
-            return any ? policyBuilder.Build() : null;
+
+            return policyBuilder?.Build();
         }
     }
 }

+ 9 - 9
src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationPolicyBuilder.cs

@@ -96,16 +96,16 @@ namespace Microsoft.AspNetCore.Authorization
         /// to the current instance.
         /// </summary>
         /// <param name="claimType">The claim type required.</param>
-        /// <param name="requiredValues">Values the claim must process one or more of for evaluation to succeed.</param>
+        /// <param name="allowedValues">Values the claim must process one or more of for evaluation to succeed.</param>
         /// <returns>A reference to this instance after the operation has completed.</returns>
-        public AuthorizationPolicyBuilder RequireClaim(string claimType, params string[] requiredValues)
+        public AuthorizationPolicyBuilder RequireClaim(string claimType, params string[] allowedValues)
         {
             if (claimType == null)
             {
                 throw new ArgumentNullException(nameof(claimType));
             }
 
-            return RequireClaim(claimType, (IEnumerable<string>)requiredValues);
+            return RequireClaim(claimType, (IEnumerable<string>)allowedValues);
         }
 
         /// <summary>
@@ -113,16 +113,16 @@ namespace Microsoft.AspNetCore.Authorization
         /// to the current instance.
         /// </summary>
         /// <param name="claimType">The claim type required.</param>
-        /// <param name="requiredValues">Values the claim must process one or more of for evaluation to succeed.</param>
+        /// <param name="allowedValues">Values the claim must process one or more of for evaluation to succeed.</param>
         /// <returns>A reference to this instance after the operation has completed.</returns>
-        public AuthorizationPolicyBuilder RequireClaim(string claimType, IEnumerable<string> requiredValues)
+        public AuthorizationPolicyBuilder RequireClaim(string claimType, IEnumerable<string> allowedValues)
         {
             if (claimType == null)
             {
                 throw new ArgumentNullException(nameof(claimType));
             }
 
-            Requirements.Add(new ClaimsAuthorizationRequirement(claimType, requiredValues));
+            Requirements.Add(new ClaimsAuthorizationRequirement(claimType, allowedValues));
             return this;
         }
 
@@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Authorization
         /// Adds a <see cref="RolesAuthorizationRequirement"/>
         /// to the current instance.
         /// </summary>
-        /// <param name="roles">The roles required.</param>
+        /// <param name="roles">The allowed roles.</param>
         /// <returns>A reference to this instance after the operation has completed.</returns>
         public AuthorizationPolicyBuilder RequireRole(params string[] roles)
         {
@@ -163,7 +163,7 @@ namespace Microsoft.AspNetCore.Authorization
         /// Adds a <see cref="RolesAuthorizationRequirement"/>
         /// to the current instance.
         /// </summary>
-        /// <param name="roles">The roles required.</param>
+        /// <param name="roles">The allowed roles.</param>
         /// <returns>A reference to this instance after the operation has completed.</returns>
         public AuthorizationPolicyBuilder RequireRole(IEnumerable<string> roles)
         {
@@ -247,4 +247,4 @@ namespace Microsoft.AspNetCore.Authorization
             return new AuthorizationPolicy(Requirements, AuthenticationSchemes.Distinct());
         }
     }
-}
+}

+ 0 - 10
src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizeAttribute.cs

@@ -39,15 +39,5 @@ namespace Microsoft.AspNetCore.Authorization
         /// Gets or sets a comma delimited list of schemes from which user information is constructed.
         /// </summary>
         public string AuthenticationSchemes { get; set; }
-
-        /// <summary>
-        /// Gets or sets a comma delimited list of schemes from which user information is constructed.
-        /// </summary>
-        [Obsolete("Use AuthenticationSchemes instead.", error: false)]
-        public string ActiveAuthenticationSchemes
-        {
-            get => AuthenticationSchemes;
-            set => AuthenticationSchemes = value;
-        }
     }
 }

+ 22 - 1
src/Security/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationPolicyProvider.cs

@@ -14,6 +14,8 @@ namespace Microsoft.AspNetCore.Authorization
     public class DefaultAuthorizationPolicyProvider : IAuthorizationPolicyProvider
     {
         private readonly AuthorizationOptions _options;
+        private Task<AuthorizationPolicy> _cachedDefaultPolicy;
+        private Task<AuthorizationPolicy> _cachedRequiredPolicy;
 
         /// <summary>
         /// Creates a new instance of <see cref="DefaultAuthorizationPolicyProvider"/>.
@@ -35,7 +37,26 @@ namespace Microsoft.AspNetCore.Authorization
         /// <returns>The default authorization policy.</returns>
         public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
         {
-            return Task.FromResult(_options.DefaultPolicy);
+            return GetCachedPolicy(ref _cachedDefaultPolicy, _options.DefaultPolicy);
+        }
+
+        /// <summary>
+        /// Gets the required authorization policy.
+        /// </summary>
+        /// <returns>The required authorization policy.</returns>
+        public Task<AuthorizationPolicy> GetRequiredPolicyAsync()
+        {
+            return GetCachedPolicy(ref _cachedRequiredPolicy, _options.RequiredPolicy);
+        }
+
+        private Task<AuthorizationPolicy> GetCachedPolicy(ref Task<AuthorizationPolicy> cachedPolicy, AuthorizationPolicy currentPolicy)
+        {
+            var local = cachedPolicy;
+            if (local == null || local.Result != currentPolicy)
+            {
+                cachedPolicy = local = Task.FromResult(currentPolicy);
+            }
+            return local;
         }
 
         /// <summary>

+ 6 - 0
src/Security/src/Microsoft.AspNetCore.Authorization/IAuthorizationPolicyProvider.cs

@@ -22,5 +22,11 @@ namespace Microsoft.AspNetCore.Authorization
         /// </summary>
         /// <returns>The default authorization policy.</returns>
         Task<AuthorizationPolicy> GetDefaultPolicyAsync();
+
+        /// <summary>
+        /// Gets the required authorization policy.
+        /// </summary>
+        /// <returns>The required authorization policy.</returns>
+        Task<AuthorizationPolicy> GetRequiredPolicyAsync();
     }
 }

+ 1 - 1
src/Security/src/Microsoft.AspNetCore.Authorization/Microsoft.AspNetCore.Authorization.csproj

@@ -5,7 +5,7 @@
 Commonly used types:
 Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute
 Microsoft.AspNetCore.Authorization.AuthorizeAttribute</Description>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <NoWarn>$(NoWarn);CS1591</NoWarn>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PackageTags>aspnetcore;authorization</PackageTags>

+ 1 - 1
src/Security/src/Microsoft.AspNetCore.CookiePolicy/Microsoft.AspNetCore.CookiePolicy.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <Description>ASP.NET Core cookie policy classes to control the behavior of cookies.</Description>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <NoWarn>$(NoWarn);CS1591</NoWarn>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PackageTags>aspnetcore</PackageTags>

+ 0 - 7
src/Security/test/Directory.Build.props

@@ -1,13 +1,6 @@
 <Project>
   <Import Project="..\Directory.Build.props" />
 
-  <PropertyGroup>
-    <DeveloperBuildTestTfms>netcoreapp2.2</DeveloperBuildTestTfms>
-    <StandardTestTfms>$(DeveloperBuildTestTfms)</StandardTestTfms>
-    <StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' ">$(StandardTestTfms)</StandardTestTfms>
-    <StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' AND '$(OS)' == 'Windows_NT' ">$(StandardTestTfms);net461</StandardTestTfms>
-  </PropertyGroup>
-
   <ItemGroup>
     <PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
     <PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />

+ 5 - 421
src/Security/test/Microsoft.AspNetCore.Authentication.Test/CookieTests.cs

@@ -10,7 +10,6 @@ using System.Security.Principal;
 using System.Text;
 using System.Threading.Tasks;
 using System.Xml.Linq;
-using Microsoft.AspNetCore.Authentication.Tests;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.DataProtection;
 using Microsoft.AspNetCore.Hosting;
@@ -22,431 +21,16 @@ using Xunit;
 
 namespace Microsoft.AspNetCore.Authentication.Cookies
 {
-    public class CookieTests
+    public class CookieTests : SharedAuthenticationTests<CookieAuthenticationOptions>
     {
         private TestClock _clock = new TestClock();
 
-        [Fact]
-        public async Task CanForwardDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler>("auth1", "auth1");
-            })
-            .AddCookie(o => o.ForwardDefault = "auth1");
-
-            var forwardDefault = new TestHandler();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, forwardDefault.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, forwardDefault.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, forwardDefault.ChallengeCount);
-
-            await context.SignOutAsync();
-            Assert.Equal(1, forwardDefault.SignOutCount);
-
-            await context.SignInAsync(new ClaimsPrincipal());
-            Assert.Equal(1, forwardDefault.SignInCount);
-        }
-
-        [Fact]
-        public async Task ForwardSignInWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddCookie(o =>
-            {
-                o.ForwardDefault = "auth1";
-                o.ForwardSignIn = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.SignInAsync(new ClaimsPrincipal());
-            Assert.Equal(1, specific.SignInCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignOutCount);
+        protected override string DefaultScheme => CookieAuthenticationDefaults.AuthenticationScheme;
+        protected override Type HandlerType => typeof(CookieAuthenticationHandler);
 
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardSignOutWinsOverDefault()
+        protected override void RegisterAuth(AuthenticationBuilder services, Action<CookieAuthenticationOptions> configure)
         {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddCookie(o =>
-            {
-                o.ForwardDefault = "auth1";
-                o.ForwardSignOut = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.SignOutAsync();
-            Assert.Equal(1, specific.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardForbidWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddCookie(o =>
-            {
-                o.ForwardDefault = "auth1";
-                o.ForwardForbid = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.ForbidAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(1, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardAuthenticateWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddCookie(o =>
-            {
-                o.ForwardDefault = "auth1";
-                o.ForwardAuthenticate = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(1, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardChallengeWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler>("specific", "specific");
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-            })
-            .AddCookie(o =>
-            {
-                o.ForwardDefault = "auth1";
-                o.ForwardChallenge = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.ChallengeAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(1, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardSelectorWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddCookie(o =>
-            {
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => "selector";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, selector.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, selector.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, selector.ChallengeCount);
-
-            await context.SignOutAsync();
-            Assert.Equal(1, selector.SignOutCount);
-
-            await context.SignInAsync(new ClaimsPrincipal());
-            Assert.Equal(1, selector.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-            Assert.Equal(0, specific.SignOutCount);
-        }
-
-        [Fact]
-        public async Task NullForwardSelectorUsesDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddCookie(o =>
-            {
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => null;
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, forwardDefault.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, forwardDefault.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, forwardDefault.ChallengeCount);
-
-            await context.SignOutAsync();
-            Assert.Equal(1, forwardDefault.SignOutCount);
-
-            await context.SignInAsync(new ClaimsPrincipal());
-            Assert.Equal(1, forwardDefault.SignInCount);
-
-            Assert.Equal(0, selector.AuthenticateCount);
-            Assert.Equal(0, selector.ForbidCount);
-            Assert.Equal(0, selector.ChallengeCount);
-            Assert.Equal(0, selector.SignInCount);
-            Assert.Equal(0, selector.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-            Assert.Equal(0, specific.SignOutCount);
-        }
-
-        [Fact]
-        public async Task SpecificForwardWinsOverSelectorAndDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddCookie(o =>
-            {
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => "selector";
-                o.ForwardAuthenticate = "specific";
-                o.ForwardChallenge = "specific";
-                o.ForwardSignIn = "specific";
-                o.ForwardSignOut = "specific";
-                o.ForwardForbid = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, specific.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, specific.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, specific.ChallengeCount);
-
-            await context.SignOutAsync();
-            Assert.Equal(1, specific.SignOutCount);
-
-            await context.SignInAsync(new ClaimsPrincipal());
-            Assert.Equal(1, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-            Assert.Equal(0, selector.AuthenticateCount);
-            Assert.Equal(0, selector.ForbidCount);
-            Assert.Equal(0, selector.ChallengeCount);
-            Assert.Equal(0, selector.SignInCount);
-            Assert.Equal(0, selector.SignOutCount);
-        }
-
-        [Fact]
-        public async Task VerifySchemeDefaults()
-        {
-            var services = new ServiceCollection();
-            services.AddAuthentication().AddCookie();
-            var sp = services.BuildServiceProvider();
-            var schemeProvider = sp.GetRequiredService<IAuthenticationSchemeProvider>();
-            var scheme = await schemeProvider.GetSchemeAsync(CookieAuthenticationDefaults.AuthenticationScheme);
-            Assert.NotNull(scheme);
-            Assert.Equal("CookieAuthenticationHandler", scheme.HandlerType.Name);
-            Assert.Null(scheme.DisplayName);
+            services.AddCookie(configure);
         }
 
         [Fact]

+ 20 - 467
src/Security/test/Microsoft.AspNetCore.Authentication.Test/FacebookTests.cs

@@ -1,17 +1,8 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
-using System;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Security.Claims;
-using System.Text;
-using System.Text.Encodings.Web;
-using System.Threading.Tasks;
 using Microsoft.AspNetCore.Authentication.Cookies;
 using Microsoft.AspNetCore.Authentication.OAuth;
-using Microsoft.AspNetCore.Authentication.Tests;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.DataProtection;
 using Microsoft.AspNetCore.Hosting;
@@ -20,476 +11,38 @@ using Microsoft.AspNetCore.TestHost;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging.Abstractions;
 using Newtonsoft.Json;
+using System;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
 using Xunit;
 
 namespace Microsoft.AspNetCore.Authentication.Facebook
 {
-    public class FacebookTests
+    public class FacebookTests : RemoteAuthenticationTests<FacebookOptions>
     {
-        private void ConfigureDefaults(FacebookOptions o)
-        {
-            o.AppId = "whatever";
-            o.AppSecret = "whatever";
-            o.SignInScheme = "auth1";
-        }
+        protected override string DefaultScheme => FacebookDefaults.AuthenticationScheme;
+        protected override Type HandlerType => typeof(FacebookHandler);
+        protected override bool SupportsSignIn { get => false; }
+        protected override bool SupportsSignOut { get => false; }
 
-        [Fact]
-        public async Task CanForwardDefault()
+        protected override void RegisterAuth(AuthenticationBuilder services, Action<FacebookOptions> configure)
         {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = FacebookDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler>("auth1", "auth1");
-            })
-            .AddFacebook(o =>
+            services.AddFacebook(o =>
             {
                 ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
+                configure.Invoke(o);
             });
-
-            var forwardDefault = new TestHandler();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, forwardDefault.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, forwardDefault.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, forwardDefault.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-        }
-
-        [Fact]
-        public async Task ForwardSignInThrows()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = FacebookDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddFacebook(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardSignOut = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
         }
-
-        [Fact]
-        public async Task ForwardSignOutThrows()
+ 
+        protected override void ConfigureDefaults(FacebookOptions o)
         {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = FacebookDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddFacebook(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardSignOut = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-        }
-
-        [Fact]
-        public async Task ForwardForbidWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = FacebookDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddFacebook(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardForbid = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.ForbidAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(1, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardAuthenticateWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = FacebookDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddFacebook(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardAuthenticate = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(1, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardChallengeWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = FacebookDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler>("specific", "specific");
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-            })
-            .AddFacebook(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardChallenge = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.ChallengeAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(1, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardSelectorWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = FacebookDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddFacebook(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => "selector";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, selector.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, selector.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, selector.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-            Assert.Equal(0, specific.SignOutCount);
-        }
-
-        [Fact]
-        public async Task NullForwardSelectorUsesDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = FacebookDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddFacebook(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => null;
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, forwardDefault.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, forwardDefault.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, forwardDefault.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, selector.AuthenticateCount);
-            Assert.Equal(0, selector.ForbidCount);
-            Assert.Equal(0, selector.ChallengeCount);
-            Assert.Equal(0, selector.SignInCount);
-            Assert.Equal(0, selector.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-            Assert.Equal(0, specific.SignOutCount);
-        }
-
-        [Fact]
-        public async Task SpecificForwardWinsOverSelectorAndDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = FacebookDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddFacebook(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => "selector";
-                o.ForwardAuthenticate = "specific";
-                o.ForwardChallenge = "specific";
-                o.ForwardSignIn = "specific";
-                o.ForwardSignOut = "specific";
-                o.ForwardForbid = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, specific.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, specific.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, specific.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-            Assert.Equal(0, selector.AuthenticateCount);
-            Assert.Equal(0, selector.ForbidCount);
-            Assert.Equal(0, selector.ChallengeCount);
-            Assert.Equal(0, selector.SignInCount);
-            Assert.Equal(0, selector.SignOutCount);
-        }
-
-        [Fact]
-        public async Task VerifySignInSchemeCannotBeSetToSelf()
-        {
-            var server = CreateServer(
-                app => { },
-                services => services.AddAuthentication().AddFacebook(o =>
-                {
-                    o.AppId = "whatever";
-                    o.AppSecret = "whatever";
-                    o.SignInScheme = FacebookDefaults.AuthenticationScheme;
-                }),
-                async context =>
-                {
-                    await context.ChallengeAsync("Facebook");
-                    return true;
-                });
-            var error = await Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync("https://example.com/challenge"));
-            Assert.Contains("cannot be set to itself", error.Message);
-        }
-
-        [Fact]
-        public async Task VerifySignInSchemeCannotBeSetToSelfUsingDefaultScheme()
-        {
-            var server = CreateServer(
-                app => { },
-                services => services.AddAuthentication(o => o.DefaultScheme = FacebookDefaults.AuthenticationScheme).AddFacebook(o =>
-                {
-                    o.AppId = "whatever";
-                    o.AppSecret = "whatever";
-                }),
-                async context =>
-                {
-                    await context.ChallengeAsync("Facebook");
-                    return true;
-                });
-            var error = await Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync("https://example.com/challenge"));
-            Assert.Contains("cannot be set to itself", error.Message);
-        }
-
-        [Fact]
-        public async Task VerifySignInSchemeCannotBeSetToSelfUsingDefaultSignInScheme()
-        {
-            var server = CreateServer(
-                app => { },
-                services => services.AddAuthentication(o => o.DefaultSignInScheme = FacebookDefaults.AuthenticationScheme).AddFacebook(o =>
-                {
-                    o.AppId = "whatever";
-                    o.AppSecret = "whatever";
-                }),
-                async context =>
-                {
-                    await context.ChallengeAsync("Facebook");
-                    return true;
-                });
-            var error = await Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync("https://example.com/challenge"));
-            Assert.Contains("cannot be set to itself", error.Message);
-        }
-
-        [Fact]
-        public async Task VerifySchemeDefaults()
-        {
-            var services = new ServiceCollection();
-            services.AddAuthentication().AddFacebook();
-            var sp = services.BuildServiceProvider();
-            var schemeProvider = sp.GetRequiredService<IAuthenticationSchemeProvider>();
-            var scheme = await schemeProvider.GetSchemeAsync(FacebookDefaults.AuthenticationScheme);
-            Assert.NotNull(scheme);
-            Assert.Equal("FacebookHandler", scheme.HandlerType.Name);
-            Assert.Equal(FacebookDefaults.AuthenticationScheme, scheme.DisplayName);
+            o.AppId = "whatever";
+            o.AppSecret = "whatever";
+            o.SignInScheme = "auth1";
         }
 
         [Fact]

+ 87 - 424
src/Security/test/Microsoft.AspNetCore.Authentication.Test/GoogleTests.cs

@@ -1,16 +1,6 @@
 // Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.
 
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Security.Claims;
-using System.Text;
-using System.Text.Encodings.Web;
-using System.Threading.Tasks;
 using Microsoft.AspNetCore.Authentication.OAuth;
-using Microsoft.AspNetCore.Authentication.Tests;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.DataProtection;
 using Microsoft.AspNetCore.Hosting;
@@ -20,431 +10,40 @@ using Microsoft.AspNetCore.WebUtilities;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging.Abstractions;
 using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Security.Claims;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
 using Xunit;
 
 namespace Microsoft.AspNetCore.Authentication.Google
 {
-    public class GoogleTests
+    public class GoogleTests : RemoteAuthenticationTests<GoogleOptions>
     {
-        private void ConfigureDefaults(GoogleOptions o)
-        {
-            o.ClientId = "whatever";
-            o.ClientSecret = "whatever";
-            o.SignInScheme = "auth1";
-        }
-
-        [Fact]
-        public async Task CanForwardDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = GoogleDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler>("auth1", "auth1");
-            })
-            .AddGoogle(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-            });
-
-            var forwardDefault = new TestHandler();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, forwardDefault.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, forwardDefault.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, forwardDefault.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-        }
-
-        [Fact]
-        public async Task ForwardSignInThrows()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = GoogleDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddGoogle(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardSignOut = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
+        protected override string DefaultScheme => GoogleDefaults.AuthenticationScheme;
+        protected override Type HandlerType => typeof(GoogleHandler);
+        protected override bool SupportsSignIn { get => false; }
+        protected override bool SupportsSignOut { get => false; }
 
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-        }
-
-        [Fact]
-        public async Task ForwardSignOutThrows()
+        protected override void RegisterAuth(AuthenticationBuilder services, Action<GoogleOptions> configure)
         {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = GoogleDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddGoogle(o =>
+            services.AddGoogle(o =>
             {
                 ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardSignOut = "specific";
+                configure.Invoke(o);
             });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
         }
 
-        [Fact]
-        public async Task ForwardForbidWinsOverDefault()
+        protected override void ConfigureDefaults(GoogleOptions o)
         {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = GoogleDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddGoogle(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardForbid = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.ForbidAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(1, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardAuthenticateWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = GoogleDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddGoogle(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardAuthenticate = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(1, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardChallengeWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = GoogleDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler>("specific", "specific");
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-            })
-            .AddGoogle(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardChallenge = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.ChallengeAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(1, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardSelectorWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = GoogleDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddGoogle(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => "selector";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, selector.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, selector.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, selector.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-            Assert.Equal(0, specific.SignOutCount);
-        }
-
-        [Fact]
-        public async Task NullForwardSelectorUsesDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = GoogleDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddGoogle(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => null;
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, forwardDefault.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, forwardDefault.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, forwardDefault.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, selector.AuthenticateCount);
-            Assert.Equal(0, selector.ForbidCount);
-            Assert.Equal(0, selector.ChallengeCount);
-            Assert.Equal(0, selector.SignInCount);
-            Assert.Equal(0, selector.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-            Assert.Equal(0, specific.SignOutCount);
-        }
-
-        [Fact]
-        public async Task SpecificForwardWinsOverSelectorAndDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = GoogleDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddGoogle(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => "selector";
-                o.ForwardAuthenticate = "specific";
-                o.ForwardChallenge = "specific";
-                o.ForwardSignIn = "specific";
-                o.ForwardSignOut = "specific";
-                o.ForwardForbid = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, specific.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, specific.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, specific.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-            Assert.Equal(0, selector.AuthenticateCount);
-            Assert.Equal(0, selector.ForbidCount);
-            Assert.Equal(0, selector.ChallengeCount);
-            Assert.Equal(0, selector.SignInCount);
-            Assert.Equal(0, selector.SignOutCount);
-        }
-
-        [Fact]
-        public async Task VerifySignInSchemeCannotBeSetToSelf()
-        {
-            var server = CreateServer(o =>
-            {
-                o.ClientId = "Test Id";
-                o.ClientSecret = "Test Secret";
-                o.SignInScheme = GoogleDefaults.AuthenticationScheme;
-            });
-            var error = await Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync("https://example.com/challenge"));
-            Assert.Contains("cannot be set to itself", error.Message);
-        }
-
-        [Fact]
-        public async Task VerifySchemeDefaults()
-        {
-            var services = new ServiceCollection();
-            services.AddAuthentication().AddGoogle();
-            var sp = services.BuildServiceProvider();
-            var schemeProvider = sp.GetRequiredService<IAuthenticationSchemeProvider>();
-            var scheme = await schemeProvider.GetSchemeAsync(GoogleDefaults.AuthenticationScheme);
-            Assert.NotNull(scheme);
-            Assert.Equal("GoogleHandler", scheme.HandlerType.Name);
-            Assert.Equal(GoogleDefaults.AuthenticationScheme, scheme.DisplayName);
+            o.ClientId = "whatever";
+            o.ClientSecret = "whatever";
+            o.SignInScheme = "auth1";
         }
 
         [Fact]
@@ -758,6 +357,70 @@ namespace Microsoft.AspNetCore.Authentication.Google
             Assert.Equal("The oauth state was missing or invalid.", error.GetBaseException().Message);
         }
 
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReplyPathWithAccessDeniedErrorFails(bool redirect)
+        {
+            var server = CreateServer(o =>
+            {
+                o.ClientId = "Test Id";
+                o.ClientSecret = "Test Secret";
+                o.StateDataFormat = new TestStateDataFormat();
+                o.Events = redirect ? new OAuthEvents()
+                {
+                    OnAccessDenied = ctx =>
+                    {
+                        ctx.Response.Redirect("/error?FailureMessage=AccessDenied");
+                        ctx.HandleResponse();
+                        return Task.FromResult(0);
+                    }
+                } : new OAuthEvents();
+            });
+            var sendTask = server.SendAsync("https://example.com/signin-google?error=access_denied&error_description=SoBad&error_uri=foobar&state=protected_state",
+                ".AspNetCore.Correlation.Google.correlationId=N");
+            if (redirect)
+            {
+                var transaction = await sendTask;
+                Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
+                Assert.Equal("/error?FailureMessage=AccessDenied", transaction.Response.Headers.GetValues("Location").First());
+            }
+            else
+            {
+                var error = await Assert.ThrowsAnyAsync<Exception>(() => sendTask);
+                Assert.Equal("Access was denied by the resource owner or by the remote server.", error.GetBaseException().Message);
+            }
+        }
+
+        [Fact]
+        public async Task ReplyPathWithAccessDeniedError_AllowsCustomizingPath()
+        {
+            var server = CreateServer(o =>
+            {
+                o.ClientId = "Test Id";
+                o.ClientSecret = "Test Secret";
+                o.StateDataFormat = new TestStateDataFormat();
+                o.AccessDeniedPath = "/access-denied";
+                o.Events = new OAuthEvents()
+                {
+                    OnAccessDenied = ctx =>
+                    {
+                        Assert.Equal("/access-denied", ctx.AccessDeniedPath.Value);
+                        Assert.Equal("http://testhost/redirect", ctx.ReturnUrl);
+                        Assert.Equal("ReturnUrl", ctx.ReturnUrlParameter);
+                        ctx.AccessDeniedPath = "/custom-denied-page";
+                        ctx.ReturnUrl = "http://www.google.com/";
+                        ctx.ReturnUrlParameter = "rurl";
+                        return Task.FromResult(0);
+                    }
+                };
+            });
+            var transaction = await server.SendAsync("https://example.com/signin-google?error=access_denied&error_description=SoBad&error_uri=foobar&state=protected_state",
+                ".AspNetCore.Correlation.Google.correlationId=N");
+            Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
+            Assert.Equal("/custom-denied-page?rurl=http%3A%2F%2Fwww.google.com%2F", transaction.Response.Headers.GetValues("Location").First());
+        }
+
         [Theory]
         [InlineData(true)]
         [InlineData(false)]
@@ -779,7 +442,7 @@ namespace Microsoft.AspNetCore.Authentication.Google
                 } : new OAuthEvents();
             });
             var sendTask = server.SendAsync("https://example.com/signin-google?error=OMG&error_description=SoBad&error_uri=foobar&state=protected_state",
-                ".AspNetCore.Correlation.Google.corrilationId=N");
+                ".AspNetCore.Correlation.Google.correlationId=N");
             if (redirect)
             {
                 var transaction = await sendTask;
@@ -1606,7 +1269,7 @@ namespace Microsoft.AspNetCore.Authentication.Google
                 Assert.Equal("protected_state", protectedText);
                 var properties = new AuthenticationProperties(new Dictionary<string, string>()
                 {
-                    { ".xsrf", "corrilationId" },
+                    { ".xsrf", "correlationId" },
                     { "testkey", "testvalue" }
                 });
                 properties.RedirectUri = "http://testhost/redirect";

+ 15 - 407
src/Security/test/Microsoft.AspNetCore.Authentication.Test/JwtBearerTests.cs

@@ -1,6 +1,12 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.IdentityModel.Tokens;
 using System;
 using System.IdentityModel.Tokens.Jwt;
 using System.Linq;
@@ -11,426 +17,28 @@ using System.Security.Claims;
 using System.Text;
 using System.Threading.Tasks;
 using System.Xml.Linq;
-using Microsoft.AspNetCore.Authentication.Tests;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.TestHost;
-using Microsoft.AspNetCore.Testing.xunit;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.IdentityModel.Tokens;
 using Xunit;
 
 namespace Microsoft.AspNetCore.Authentication.JwtBearer
 {
-    public class JwtBearerTests
+    public class JwtBearerTests : SharedAuthenticationTests<JwtBearerOptions>
     {
-        private void ConfigureDefaults(JwtBearerOptions o)
-        {
-        }
-
-        [Fact]
-        public async Task CanForwardDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler>("auth1", "auth1");
-            })
-            .AddJwtBearer(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-            });
-
-            var forwardDefault = new TestHandler();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, forwardDefault.AuthenticateCount);
+        protected override string DefaultScheme => JwtBearerDefaults.AuthenticationScheme;
+        protected override Type HandlerType => typeof(JwtBearerHandler);
+        protected override bool SupportsSignIn { get => false; }
+        protected override bool SupportsSignOut { get => false; }
 
-            await context.ForbidAsync();
-            Assert.Equal(1, forwardDefault.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, forwardDefault.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-        }
-
-        [Fact]
-        public async Task ForwardSignInThrows()
+        protected override void RegisterAuth(AuthenticationBuilder services, Action<JwtBearerOptions> configure)
         {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddJwtBearer(o =>
+            services.AddJwtBearer(o =>
             {
                 ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardSignOut = "specific";
+                configure.Invoke(o);
             });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
         }
 
-        [Fact]
-        public async Task ForwardSignOutThrows()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddJwtBearer(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardSignOut = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-        }
-
-        [Fact]
-        public async Task ForwardForbidWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
-                o.DefaultSignInScheme = "auth1";
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddJwtBearer(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardForbid = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.ForbidAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(1, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardAuthenticateWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
-                o.DefaultSignInScheme = "auth1";
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddJwtBearer(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardAuthenticate = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(1, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardChallengeWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
-                o.DefaultSignInScheme = "auth1";
-                o.AddScheme<TestHandler>("specific", "specific");
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-            })
-            .AddJwtBearer(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardChallenge = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.ChallengeAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(1, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardSelectorWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddJwtBearer(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => "selector";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, selector.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, selector.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, selector.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-            Assert.Equal(0, specific.SignOutCount);
-        }
-
-        [Fact]
-        public async Task NullForwardSelectorUsesDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddJwtBearer(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => null;
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, forwardDefault.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, forwardDefault.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, forwardDefault.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, selector.AuthenticateCount);
-            Assert.Equal(0, selector.ForbidCount);
-            Assert.Equal(0, selector.ChallengeCount);
-            Assert.Equal(0, selector.SignInCount);
-            Assert.Equal(0, selector.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-            Assert.Equal(0, specific.SignOutCount);
-        }
-
-        [Fact]
-        public async Task SpecificForwardWinsOverSelectorAndDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddJwtBearer(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => "selector";
-                o.ForwardAuthenticate = "specific";
-                o.ForwardChallenge = "specific";
-                o.ForwardSignIn = "specific";
-                o.ForwardSignOut = "specific";
-                o.ForwardForbid = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, specific.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, specific.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, specific.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-            Assert.Equal(0, selector.AuthenticateCount);
-            Assert.Equal(0, selector.ForbidCount);
-            Assert.Equal(0, selector.ChallengeCount);
-            Assert.Equal(0, selector.SignInCount);
-            Assert.Equal(0, selector.SignOutCount);
-        }
-
-        [Fact]
-        public async Task VerifySchemeDefaults()
+        private void ConfigureDefaults(JwtBearerOptions o)
         {
-            var services = new ServiceCollection();
-            services.AddAuthentication().AddJwtBearer();
-            var sp = services.BuildServiceProvider();
-            var schemeProvider = sp.GetRequiredService<IAuthenticationSchemeProvider>();
-            var scheme = await schemeProvider.GetSchemeAsync(JwtBearerDefaults.AuthenticationScheme);
-            Assert.NotNull(scheme);
-            Assert.Equal("JwtBearerHandler", scheme.HandlerType.Name);
-            Assert.Null(scheme.DisplayName);
         }
 
         [Fact]

+ 1 - 1
src/Security/test/Microsoft.AspNetCore.Authentication.Test/Microsoft.AspNetCore.Authentication.Test.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
   </PropertyGroup>
 
   <ItemGroup>

+ 20 - 423
src/Security/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccountTests.cs

@@ -1,14 +1,5 @@
 // Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.
 
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Security.Claims;
-using System.Text;
-using System.Text.Encodings.Web;
-using System.Threading.Tasks;
 using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
 using Microsoft.AspNetCore.Authentication.OAuth;
 using Microsoft.AspNetCore.Builder;
@@ -16,436 +7,42 @@ using Microsoft.AspNetCore.DataProtection;
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.TestHost;
-using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging.Abstractions;
-using Microsoft.Extensions.Options;
 using Newtonsoft.Json;
+using System;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Security.Claims;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
 using Xunit;
 
 namespace Microsoft.AspNetCore.Authentication.Tests.MicrosoftAccount
 {
-    public class MicrosoftAccountTests
+    public class MicrosoftAccountTests : RemoteAuthenticationTests<MicrosoftAccountOptions>
     {
-        private void ConfigureDefaults(MicrosoftAccountOptions o)
-        {
-            o.ClientId = "whatever";
-            o.ClientSecret = "whatever";
-            o.SignInScheme = "auth1";
-        }
-
-        [Fact]
-        public async Task CanForwardDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler>("auth1", "auth1");
-            })
-            .AddMicrosoftAccount(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-            });
-
-            var forwardDefault = new TestHandler();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, forwardDefault.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, forwardDefault.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, forwardDefault.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-        }
-
-        [Fact]
-        public async Task ForwardSignInThrows()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddMicrosoftAccount(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardSignOut = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-        }
-
-        [Fact]
-        public async Task ForwardSignOutThrows()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddMicrosoftAccount(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardSignOut = "specific";
-            });
+        protected override string DefaultScheme => MicrosoftAccountDefaults.AuthenticationScheme;
+        protected override Type HandlerType => typeof(MicrosoftAccountHandler);
+        protected override bool SupportsSignIn { get => false; }
+        protected override bool SupportsSignOut { get => false; }
 
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-        }
-
-        [Fact]
-        public async Task ForwardForbidWinsOverDefault()
+        protected override void RegisterAuth(AuthenticationBuilder services, Action<MicrosoftAccountOptions> configure)
         {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddMicrosoftAccount(o =>
+            services.AddMicrosoftAccount(o =>
             {
                 ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardForbid = "specific";
+                configure.Invoke(o);
             });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.ForbidAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(1, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
         }
 
-        [Fact]
-        public async Task ForwardAuthenticateWinsOverDefault()
+        protected override void ConfigureDefaults(MicrosoftAccountOptions o)
         {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddMicrosoftAccount(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardAuthenticate = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(1, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardChallengeWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler>("specific", "specific");
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-            })
-            .AddMicrosoftAccount(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardChallenge = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.ChallengeAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(1, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardSelectorWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddMicrosoftAccount(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => "selector";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, selector.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, selector.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, selector.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-            Assert.Equal(0, specific.SignOutCount);
-        }
-
-        [Fact]
-        public async Task NullForwardSelectorUsesDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddMicrosoftAccount(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => null;
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, forwardDefault.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, forwardDefault.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, forwardDefault.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, selector.AuthenticateCount);
-            Assert.Equal(0, selector.ForbidCount);
-            Assert.Equal(0, selector.ChallengeCount);
-            Assert.Equal(0, selector.SignInCount);
-            Assert.Equal(0, selector.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-            Assert.Equal(0, specific.SignOutCount);
-        }
-
-        [Fact]
-        public async Task SpecificForwardWinsOverSelectorAndDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddMicrosoftAccount(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => "selector";
-                o.ForwardAuthenticate = "specific";
-                o.ForwardChallenge = "specific";
-                o.ForwardSignIn = "specific";
-                o.ForwardSignOut = "specific";
-                o.ForwardForbid = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, specific.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, specific.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, specific.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-            Assert.Equal(0, selector.AuthenticateCount);
-            Assert.Equal(0, selector.ForbidCount);
-            Assert.Equal(0, selector.ChallengeCount);
-            Assert.Equal(0, selector.SignInCount);
-            Assert.Equal(0, selector.SignOutCount);
-        }
-
-        [Fact]
-        public async Task VerifySignInSchemeCannotBeSetToSelf()
-        {
-            var server = CreateServer(o =>
-            {
-                o.ClientId = "Test Id";
-                o.ClientSecret = "Test Secret";
-                o.SignInScheme = MicrosoftAccountDefaults.AuthenticationScheme;
-            });
-            var error = await Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync("https://example.com/challenge"));
-            Assert.Contains("cannot be set to itself", error.Message);
-        }
-
-        [Fact]
-        public async Task VerifySchemeDefaults()
-        {
-            var services = new ServiceCollection();
-            services.AddAuthentication().AddMicrosoftAccount();
-            var sp = services.BuildServiceProvider();
-            var schemeProvider = sp.GetRequiredService<IAuthenticationSchemeProvider>();
-            var scheme = await schemeProvider.GetSchemeAsync(MicrosoftAccountDefaults.AuthenticationScheme);
-            Assert.NotNull(scheme);
-            Assert.Equal("MicrosoftAccountHandler", scheme.HandlerType.Name);
-            Assert.Equal(MicrosoftAccountDefaults.AuthenticationScheme, scheme.DisplayName);
+            o.ClientId = "whatever";
+            o.ClientSecret = "whatever";
+            o.SignInScheme = "auth1";
         }
 
         [Fact]

+ 112 - 430
src/Security/test/Microsoft.AspNetCore.Authentication.Test/OAuthTests.cs

@@ -1,447 +1,34 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
-using System;
-using System.Collections.Generic;
-using System.Net;
-using System.Security.Claims;
-using System.Threading.Tasks;
 using Microsoft.AspNetCore.Authentication.Cookies;
-using Microsoft.AspNetCore.Authentication.Tests;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.TestHost;
 using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading.Tasks;
 using Xunit;
 
 namespace Microsoft.AspNetCore.Authentication.OAuth
 {
-    public class OAuthTests
+    public class OAuthTests : RemoteAuthenticationTests<OAuthOptions>
     {
-        [Fact]
-        public async Task CanForwardDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = "default";
-                o.AddScheme<TestHandler>("auth1", "auth1");
-            })
-            .AddOAuth("default", o =>
-            {
-                ConfigureDefaults(o);
-                o.SignInScheme = "auth1";
-                o.ForwardDefault = "auth1";
-            });
-
-            var forwardDefault = new TestHandler();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, forwardDefault.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, forwardDefault.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, forwardDefault.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-        }
-
-        [Fact]
-        public async Task ForwardSignInThrows()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = "default";
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddOAuth("default", o =>
-            {
-                ConfigureDefaults(o);
-                o.SignInScheme = "auth1";
-                o.ForwardDefault = "auth1";
-                o.ForwardSignOut = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-        }
-
-        [Fact]
-        public async Task ForwardSignOutThrows()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = "default";
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddOAuth("default", o =>
-            {
-                ConfigureDefaults(o);
-                o.SignInScheme = "auth1";
-                o.ForwardDefault = "auth1";
-                o.ForwardSignOut = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-        }
-
-        [Fact]
-        public async Task ForwardForbidWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = "default";
-                o.DefaultSignInScheme = "auth1";
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddOAuth("default", o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardForbid = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.ForbidAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(1, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardAuthenticateWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = "default";
-                o.DefaultSignInScheme = "auth1";
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddOAuth("default", o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardAuthenticate = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(1, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardChallengeWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = "default";
-                o.DefaultSignInScheme = "auth1";
-                o.AddScheme<TestHandler>("specific", "specific");
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-            })
-            .AddOAuth("default", o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardChallenge = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.ChallengeAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(1, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardSelectorWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = "default";
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddOAuth("default", o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => "selector";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, selector.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, selector.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, selector.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-            Assert.Equal(0, specific.SignOutCount);
-        }
+        protected override string DefaultScheme => OAuthDefaults.DisplayName;
+        protected override Type HandlerType => typeof(OAuthHandler<OAuthOptions>);
+        protected override bool SupportsSignIn { get => false; }
+        protected override bool SupportsSignOut { get => false; }
 
-        [Fact]
-        public async Task NullForwardSelectorUsesDefault()
+        protected override void RegisterAuth(AuthenticationBuilder services, Action<OAuthOptions> configure)
         {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = "default";
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddOAuth("default", o =>
+            services.AddOAuth(DefaultScheme, o =>
             {
                 ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => null;
+                configure.Invoke(o);
             });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, forwardDefault.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, forwardDefault.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, forwardDefault.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, selector.AuthenticateCount);
-            Assert.Equal(0, selector.ForbidCount);
-            Assert.Equal(0, selector.ChallengeCount);
-            Assert.Equal(0, selector.SignInCount);
-            Assert.Equal(0, selector.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-            Assert.Equal(0, specific.SignOutCount);
-        }
-
-        [Fact]
-        public async Task SpecificForwardWinsOverSelectorAndDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = "default";
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddOAuth("default", o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => "selector";
-                o.ForwardAuthenticate = "specific";
-                o.ForwardChallenge = "specific";
-                o.ForwardSignIn = "specific";
-                o.ForwardSignOut = "specific";
-                o.ForwardForbid = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, specific.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, specific.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, specific.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-            Assert.Equal(0, selector.AuthenticateCount);
-            Assert.Equal(0, selector.ForbidCount);
-            Assert.Equal(0, selector.ChallengeCount);
-            Assert.Equal(0, selector.SignInCount);
-            Assert.Equal(0, selector.SignOutCount);
-        }
-
-
-        [Fact]
-        public async Task VerifySignInSchemeCannotBeSetToSelf()
-        {
-            var server = CreateServer(
-                services => services.AddAuthentication().AddOAuth("weeblie", o =>
-                {
-                    o.SignInScheme = "weeblie";
-                    o.ClientId = "whatever";
-                    o.ClientSecret = "whatever";
-                    o.CallbackPath = "/whatever";
-                    o.AuthorizationEndpoint = "/whatever";
-                    o.TokenEndpoint = "/whatever";
-                }));
-            var error = await Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync("https://example.com/"));
-            Assert.Contains("cannot be set to itself", error.Message);
-        }
-
-        [Fact]
-        public async Task VerifySchemeDefaults()
-        {
-            var services = new ServiceCollection();
-            services.AddAuthentication().AddOAuth("oauth", o => { });
-            var sp = services.BuildServiceProvider();
-            var schemeProvider = sp.GetRequiredService<IAuthenticationSchemeProvider>();
-            var scheme = await schemeProvider.GetSchemeAsync("oauth");
-            Assert.NotNull(scheme);
-            Assert.Equal("OAuthHandler`1", scheme.HandlerType.Name);
-            Assert.Equal(OAuthDefaults.DisplayName, scheme.DisplayName);
         }
 
         [Fact]
@@ -654,7 +241,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
             Assert.Contains("scope=baz%20qux", res.Headers.Location.Query);
         }
 
-        private void ConfigureDefaults(OAuthOptions o)
+        protected override void ConfigureDefaults(OAuthOptions o)
         {
             o.ClientId = "Test Id";
             o.ClientSecret = "secret";
@@ -664,6 +251,101 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
             o.CallbackPath = "/oauth-callback";
         }
 
+        [Fact]
+        public async Task HandleRequestAsync_RedirectsToAccessDeniedPathWhenExplicitlySet()
+        {
+            var server = CreateServer(
+                s => s.AddAuthentication().AddOAuth(
+                    "Weblie",
+                    opt =>
+                    {
+                        opt.ClientId = "Test Id";
+                        opt.ClientSecret = "secret";
+                        opt.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
+                        opt.AuthorizationEndpoint = "https://example.com/provider/login";
+                        opt.TokenEndpoint = "https://example.com/provider/token";
+                        opt.CallbackPath = "/oauth-callback";
+                        opt.AccessDeniedPath = "/access-denied";
+                        opt.StateDataFormat = new TestStateDataFormat();
+                        opt.Events.OnRemoteFailure = context => throw new InvalidOperationException("This event should not be called.");
+                    }));
+
+            var transaction = await server.SendAsync("https://www.example.com/oauth-callback?error=access_denied&state=protected_state",
+                ".AspNetCore.Correlation.Weblie.correlationId=N");
+
+            Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
+            Assert.Equal("/access-denied?ReturnUrl=http%3A%2F%2Ftesthost%2Fredirect", transaction.Response.Headers.Location.ToString());
+        }
+
+        [Fact]
+        public async Task HandleRequestAsync_InvokesAccessDeniedEvent()
+        {
+            var server = CreateServer(
+                s => s.AddAuthentication().AddOAuth(
+                    "Weblie",
+                    opt =>
+                    {
+                        opt.ClientId = "Test Id";
+                        opt.ClientSecret = "secret";
+                        opt.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
+                        opt.AuthorizationEndpoint = "https://example.com/provider/login";
+                        opt.TokenEndpoint = "https://example.com/provider/token";
+                        opt.CallbackPath = "/oauth-callback";
+                        opt.StateDataFormat = new TestStateDataFormat();
+                        opt.Events = new OAuthEvents()
+                        {
+                            OnAccessDenied = context =>
+                            {
+                                Assert.Equal("testvalue", context.Properties.Items["testkey"]);
+                                context.Response.StatusCode = StatusCodes.Status406NotAcceptable;
+                                context.HandleResponse();
+                                return Task.CompletedTask;
+                            }
+                        };
+                    }));
+
+            var transaction = await server.SendAsync("https://www.example.com/oauth-callback?error=access_denied&state=protected_state",
+                ".AspNetCore.Correlation.Weblie.correlationId=N");
+
+            Assert.Equal(HttpStatusCode.NotAcceptable, transaction.Response.StatusCode);
+            Assert.Null(transaction.Response.Headers.Location);
+        }
+
+        [Fact]
+        public async Task HandleRequestAsync_InvokesRemoteFailureEventWhenAccessDeniedPathIsNotExplicitlySet()
+        {
+            var server = CreateServer(
+                s => s.AddAuthentication().AddOAuth(
+                    "Weblie",
+                    opt =>
+                    {
+                        opt.ClientId = "Test Id";
+                        opt.ClientSecret = "secret";
+                        opt.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
+                        opt.AuthorizationEndpoint = "https://example.com/provider/login";
+                        opt.TokenEndpoint = "https://example.com/provider/token";
+                        opt.CallbackPath = "/oauth-callback";
+                        opt.StateDataFormat = new TestStateDataFormat();
+                        opt.Events = new OAuthEvents()
+                        {
+                            OnRemoteFailure = context =>
+                            {
+                                Assert.Equal("Access was denied by the resource owner or by the remote server.", context.Failure.Message);
+                                Assert.Equal("testvalue", context.Properties.Items["testkey"]);
+                                context.Response.StatusCode = StatusCodes.Status406NotAcceptable;
+                                context.HandleResponse();
+                                return Task.CompletedTask;
+                            }
+                        };
+                    }));
+
+            var transaction = await server.SendAsync("https://www.example.com/oauth-callback?error=access_denied&state=protected_state",
+                ".AspNetCore.Correlation.Weblie.correlationId=N");
+
+            Assert.Equal(HttpStatusCode.NotAcceptable, transaction.Response.StatusCode);
+            Assert.Null(transaction.Response.Headers.Location);
+        }
+
         [Fact]
         public async Task RemoteAuthenticationFailed_OAuthError_IncludesProperties()
         {
@@ -683,7 +365,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
                         {
                             OnRemoteFailure = context =>
                             {
-                                Assert.Contains("declined", context.Failure.Message);
+                                Assert.Contains("custom_error", context.Failure.Message);
                                 Assert.Equal("testvalue", context.Properties.Items["testkey"]);
                                 context.Response.StatusCode = StatusCodes.Status406NotAcceptable;
                                 context.HandleResponse();
@@ -692,8 +374,8 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
                         };
                     }));
 
-            var transaction = await server.SendAsync("https://www.example.com/oauth-callback?error=declined&state=protected_state",
-                ".AspNetCore.Correlation.Weblie.corrilationId=N");
+            var transaction = await server.SendAsync("https://www.example.com/oauth-callback?error=custom_error&state=protected_state",
+                ".AspNetCore.Correlation.Weblie.correlationId=N");
 
             Assert.Equal(HttpStatusCode.NotAcceptable, transaction.Response.StatusCode);
             Assert.Null(transaction.Response.Headers.Location);
@@ -736,7 +418,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
                 Assert.Equal("protected_state", protectedText);
                 var properties = new AuthenticationProperties(new Dictionary<string, string>()
                 {
-                    { ".xsrf", "corrilationId" },
+                    { ".xsrf", "correlationId" },
                     { "testkey", "testvalue" }
                 });
                 properties.RedirectUri = "http://testhost/redirect";

+ 58 - 2
src/Security/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectEventTests.cs

@@ -783,6 +783,52 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
             events.ValidateExpectations();
         }
 
+        [Fact]
+        public async Task OnAccessDenied_Skip_NoMoreEventsRun()
+        {
+            var events = new ExpectedOidcEvents()
+            {
+                ExpectMessageReceived = true,
+                ExpectAccessDenied = true
+            };
+            events.OnAccessDenied = context =>
+            {
+                context.SkipHandler();
+                return Task.FromResult(0);
+            };
+            var server = CreateServer(events, AppWritePath);
+
+            var response = await PostAsync(server, "signin-oidc", "error=access_denied&state=protected_state");
+
+            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+            Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync());
+            events.ValidateExpectations();
+        }
+
+        [Fact]
+        public async Task OnAccessDenied_Handled_NoMoreEventsRun()
+        {
+            var events = new ExpectedOidcEvents()
+            {
+                ExpectMessageReceived = true,
+                ExpectAccessDenied = true
+            };
+            events.OnAccessDenied = context =>
+            {
+                Assert.Equal("testvalue", context.Properties.Items["testkey"]);
+                context.HandleResponse();
+                context.Response.StatusCode = StatusCodes.Status202Accepted;
+                return Task.FromResult(0);
+            };
+            var server = CreateServer(events, AppNotImpl);
+
+            var response = await PostAsync(server, "signin-oidc", "error=access_denied&state=protected_state");
+
+            Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+            Assert.Equal("", await response.Content.ReadAsStringAsync());
+            events.ValidateExpectations();
+        }
+
         [Fact]
         public async Task OnRemoteFailure_Skip_NoMoreEventsRun()
         {
@@ -1099,6 +1145,9 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
             public bool ExpectTokenValidated { get; set; }
             public bool InvokedTokenValidated { get; set; }
 
+            public bool ExpectAccessDenied { get; set; }
+            public bool InvokedAccessDenied { get; set; }
+
             public bool ExpectRemoteFailure { get; set; }
             public bool InvokedRemoteFailure { get; set; }
 
@@ -1168,6 +1217,12 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
                 return base.TicketReceived(context);
             }
 
+            public override Task AccessDenied(AccessDeniedContext context)
+            {
+                InvokedAccessDenied = true;
+                return base.AccessDenied(context);
+            }
+
             public override Task RemoteFailure(RemoteFailureContext context)
             {
                 InvokedRemoteFailure = true;
@@ -1201,6 +1256,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
                 Assert.Equal(ExpectUserInfoReceived, InvokedUserInfoReceived);
                 Assert.Equal(ExpectAuthenticationFailed, InvokeAuthenticationFailed);
                 Assert.Equal(ExpectTicketReceived, InvokedTicketReceived);
+                Assert.Equal(ExpectAccessDenied, InvokedAccessDenied);
                 Assert.Equal(ExpectRemoteFailure, InvokedRemoteFailure);
                 Assert.Equal(ExpectRedirectForSignOut, InvokedRedirectForSignOut);
                 Assert.Equal(ExpectRemoteSignOut, InvokedRemoteSignOut);
@@ -1248,7 +1304,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
         private Task<HttpResponseMessage> PostAsync(TestServer server, string path, string form)
         {
             var client = server.CreateClient();
-            var cookie = ".AspNetCore.Correlation." + OpenIdConnectDefaults.AuthenticationScheme + ".corrilationId=N";
+            var cookie = ".AspNetCore.Correlation." + OpenIdConnectDefaults.AuthenticationScheme + ".correlationId=N";
             client.DefaultRequestHeaders.Add("Cookie", cookie);
             return client.PostAsync("signin-oidc",
                 new StringContent(form, Encoding.ASCII, "application/x-www-form-urlencoded"));
@@ -1273,7 +1329,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
                 Assert.Equal("protected_state", protectedText);
                 var properties = new AuthenticationProperties(new Dictionary<string, string>()
                 {
-                    { ".xsrf", "corrilationId" },
+                    { ".xsrf", "correlationId" },
                     { OpenIdConnectDefaults.RedirectUriForCodePropertiesKey, "redirect_uri" },
                     { "testkey", "testvalue" }
                 });

+ 92 - 0
src/Security/test/Microsoft.AspNetCore.Authentication.Test/RemoteAuthenticationTests.cs

@@ -0,0 +1,92 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    public abstract class RemoteAuthenticationTests<TOptions> : SharedAuthenticationTests<TOptions> where TOptions : RemoteAuthenticationOptions
+    {
+        protected override string DisplayName => DefaultScheme;
+
+        private TestServer CreateServer(Action<TOptions> configureOptions, Func<HttpContext, Task> testpath = null, bool isDefault = true)
+            => CreateServerWithServices(s =>
+            {
+                var builder = s.AddAuthentication();
+                if (isDefault)
+                {
+                    s.Configure<AuthenticationOptions>(o => o.DefaultScheme = DefaultScheme);
+                }
+                RegisterAuth(builder, configureOptions);
+                s.AddSingleton<ISystemClock>(Clock);
+            }, testpath);
+
+
+        protected virtual TestServer CreateServerWithServices(Action<IServiceCollection> configureServices, Func<HttpContext, Task> testpath = null)
+        {
+            //private static TestServer CreateServer(Action<IApplicationBuilder> configure, Action<IServiceCollection> configureServices, Func<HttpContext, Task<bool>> handler)
+            var builder = new WebHostBuilder()
+                .Configure(app =>
+                {
+                    app.Use(async (context, next) =>
+                    {
+                        if (testpath != null)
+                        {
+                            await testpath(context);
+                        }
+                        await next();
+                    });
+                })
+                .ConfigureServices(configureServices);
+            return new TestServer(builder);
+        }
+
+        protected abstract void ConfigureDefaults(TOptions o);
+
+        [Fact]
+        public async Task VerifySignInSchemeCannotBeSetToSelf()
+        {
+            var server = CreateServer(
+                o => 
+                {
+                    ConfigureDefaults(o);
+                    o.SignInScheme = DefaultScheme;
+                },
+                context => context.ChallengeAsync(DefaultScheme));
+            var error = await Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync("https://example.com/challenge"));
+            Assert.Contains("cannot be set to itself", error.Message);
+        }
+
+        [Fact]
+        public async Task VerifySignInSchemeCannotBeSetToSelfUsingDefaultScheme()
+        {
+            var server = CreateServer(
+                o => o.SignInScheme = null,
+                context => context.ChallengeAsync(DefaultScheme),
+                isDefault: true);
+            var error = await Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync("https://example.com/challenge"));
+            Assert.Contains("cannot be set to itself", error.Message);
+        }
+
+        [Fact]
+        public async Task VerifySignInSchemeCannotBeSetToSelfUsingDefaultSignInScheme()
+        {
+            var server = CreateServerWithServices(
+                services =>
+                {
+                    var builder = services.AddAuthentication(o => o.DefaultSignInScheme = DefaultScheme);
+                    RegisterAuth(builder, o => o.SignInScheme = null);
+                },
+                context => context.ChallengeAsync(DefaultScheme));
+            var error = await Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync("https://example.com/challenge"));
+            Assert.Contains("cannot be set to itself", error.Message);
+        }
+    }
+}

+ 510 - 0
src/Security/test/Microsoft.AspNetCore.Authentication.Test/SharedAuthenticationTests.cs

@@ -0,0 +1,510 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Authentication.Tests;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    public abstract class SharedAuthenticationTests<TOptions> where TOptions : AuthenticationSchemeOptions
+    {
+        protected TestClock Clock { get; } = new TestClock();
+
+        protected abstract string DefaultScheme { get; }
+        protected virtual string DisplayName { get; }
+        protected abstract Type HandlerType { get; }
+
+        protected virtual bool SupportsSignIn { get => true; }
+        protected virtual bool SupportsSignOut { get => true; }
+
+        protected abstract void RegisterAuth(AuthenticationBuilder services, Action<TOptions> configure);
+
+        [Fact]
+        public async Task CanForwardDefault()
+        {
+            var services = new ServiceCollection().AddLogging();
+
+            var builder = services.AddAuthentication(o =>
+            {
+                o.DefaultScheme = DefaultScheme;
+                o.AddScheme<TestHandler>("auth1", "auth1");
+            });
+            RegisterAuth(builder, o => o.ForwardDefault = "auth1");
+
+            var forwardDefault = new TestHandler();
+            services.AddSingleton(forwardDefault);
+
+            var sp = services.BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = sp;
+
+            Assert.Equal(0, forwardDefault.AuthenticateCount);
+            Assert.Equal(0, forwardDefault.ForbidCount);
+            Assert.Equal(0, forwardDefault.ChallengeCount);
+            Assert.Equal(0, forwardDefault.SignInCount);
+            Assert.Equal(0, forwardDefault.SignOutCount);
+
+            await context.AuthenticateAsync();
+            Assert.Equal(1, forwardDefault.AuthenticateCount);
+
+            await context.ForbidAsync();
+            Assert.Equal(1, forwardDefault.ForbidCount);
+
+            await context.ChallengeAsync();
+            Assert.Equal(1, forwardDefault.ChallengeCount);
+
+            if (SupportsSignOut)
+            {
+                await context.SignOutAsync();
+                Assert.Equal(1, forwardDefault.SignOutCount);
+            }
+            else
+            {
+                await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
+            }
+
+            if (SupportsSignIn)
+            {
+                await context.SignInAsync(new ClaimsPrincipal());
+                Assert.Equal(1, forwardDefault.SignInCount);
+            }
+            else
+            {
+                await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
+            }
+        }
+
+        [Fact]
+        public async Task ForwardSignInWinsOverDefault()
+        {
+            if (SupportsSignIn)
+            {
+                var services = new ServiceCollection().AddLogging();
+
+                var builder = services.AddAuthentication(o =>
+                {
+                    o.DefaultScheme = DefaultScheme;
+                    o.AddScheme<TestHandler2>("auth1", "auth1");
+                    o.AddScheme<TestHandler>("specific", "specific");
+                });
+                RegisterAuth(builder, o =>
+                {
+                    o.ForwardDefault = "auth1";
+                    o.ForwardSignIn = "specific";
+                });
+
+                var specific = new TestHandler();
+                services.AddSingleton(specific);
+                var forwardDefault = new TestHandler2();
+                services.AddSingleton(forwardDefault);
+
+                var sp = services.BuildServiceProvider();
+                var context = new DefaultHttpContext();
+                context.RequestServices = sp;
+
+                await context.SignInAsync(new ClaimsPrincipal());
+                Assert.Equal(1, specific.SignInCount);
+                Assert.Equal(0, specific.AuthenticateCount);
+                Assert.Equal(0, specific.ForbidCount);
+                Assert.Equal(0, specific.ChallengeCount);
+                Assert.Equal(0, specific.SignOutCount);
+
+                Assert.Equal(0, forwardDefault.AuthenticateCount);
+                Assert.Equal(0, forwardDefault.ForbidCount);
+                Assert.Equal(0, forwardDefault.ChallengeCount);
+                Assert.Equal(0, forwardDefault.SignInCount);
+                Assert.Equal(0, forwardDefault.SignOutCount);
+            }
+        }
+
+        [Fact]
+        public async Task ForwardSignOutWinsOverDefault()
+        {
+            if (SupportsSignOut)
+            {
+                var services = new ServiceCollection().AddLogging();
+                var builder = services.AddAuthentication(o =>
+                {
+                    o.DefaultScheme = DefaultScheme;
+                    o.AddScheme<TestHandler2>("auth1", "auth1");
+                    o.AddScheme<TestHandler>("specific", "specific");
+                });
+                RegisterAuth(builder, o =>
+                {
+                    o.ForwardDefault = "auth1";
+                    o.ForwardSignOut = "specific";
+                });
+
+                var specific = new TestHandler();
+                services.AddSingleton(specific);
+                var forwardDefault = new TestHandler2();
+                services.AddSingleton(forwardDefault);
+
+                var sp = services.BuildServiceProvider();
+                var context = new DefaultHttpContext();
+                context.RequestServices = sp;
+
+                await context.SignOutAsync();
+                Assert.Equal(1, specific.SignOutCount);
+                Assert.Equal(0, specific.AuthenticateCount);
+                Assert.Equal(0, specific.ForbidCount);
+                Assert.Equal(0, specific.ChallengeCount);
+                Assert.Equal(0, specific.SignInCount);
+
+                Assert.Equal(0, forwardDefault.AuthenticateCount);
+                Assert.Equal(0, forwardDefault.ForbidCount);
+                Assert.Equal(0, forwardDefault.ChallengeCount);
+                Assert.Equal(0, forwardDefault.SignInCount);
+                Assert.Equal(0, forwardDefault.SignOutCount);
+            }
+        }
+
+        [Fact]
+        public async Task ForwardForbidWinsOverDefault()
+        {
+            var services = new ServiceCollection().AddLogging();
+            var builder = services.AddAuthentication(o =>
+            {
+                o.DefaultScheme = DefaultScheme;
+                o.AddScheme<TestHandler2>("auth1", "auth1");
+                o.AddScheme<TestHandler>("specific", "specific");
+            });
+            RegisterAuth(builder, o =>
+            {
+                o.ForwardDefault = "auth1";
+                o.ForwardForbid = "specific";
+            });
+
+            var specific = new TestHandler();
+            services.AddSingleton(specific);
+            var forwardDefault = new TestHandler2();
+            services.AddSingleton(forwardDefault);
+
+            var sp = services.BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = sp;
+
+            await context.ForbidAsync();
+            Assert.Equal(0, specific.SignOutCount);
+            Assert.Equal(0, specific.AuthenticateCount);
+            Assert.Equal(1, specific.ForbidCount);
+            Assert.Equal(0, specific.ChallengeCount);
+            Assert.Equal(0, specific.SignInCount);
+
+            Assert.Equal(0, forwardDefault.AuthenticateCount);
+            Assert.Equal(0, forwardDefault.ForbidCount);
+            Assert.Equal(0, forwardDefault.ChallengeCount);
+            Assert.Equal(0, forwardDefault.SignInCount);
+            Assert.Equal(0, forwardDefault.SignOutCount);
+        }
+
+        [Fact]
+        public async Task ForwardAuthenticateWinsOverDefault()
+        {
+            var services = new ServiceCollection().AddLogging();
+            var builder = services.AddAuthentication(o =>
+            {
+                o.DefaultScheme = DefaultScheme;
+                o.AddScheme<TestHandler2>("auth1", "auth1");
+                o.AddScheme<TestHandler>("specific", "specific");
+            });
+            RegisterAuth(builder, o =>
+            {
+                o.ForwardDefault = "auth1";
+                o.ForwardAuthenticate = "specific";
+            });
+
+            var specific = new TestHandler();
+            services.AddSingleton(specific);
+            var forwardDefault = new TestHandler2();
+            services.AddSingleton(forwardDefault);
+
+            var sp = services.BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = sp;
+
+            await context.AuthenticateAsync();
+            Assert.Equal(0, specific.SignOutCount);
+            Assert.Equal(1, specific.AuthenticateCount);
+            Assert.Equal(0, specific.ForbidCount);
+            Assert.Equal(0, specific.ChallengeCount);
+            Assert.Equal(0, specific.SignInCount);
+
+            Assert.Equal(0, forwardDefault.AuthenticateCount);
+            Assert.Equal(0, forwardDefault.ForbidCount);
+            Assert.Equal(0, forwardDefault.ChallengeCount);
+            Assert.Equal(0, forwardDefault.SignInCount);
+            Assert.Equal(0, forwardDefault.SignOutCount);
+        }
+
+        [Fact]
+        public async Task ForwardChallengeWinsOverDefault()
+        {
+            var services = new ServiceCollection().AddLogging();
+            var builder = services.AddAuthentication(o =>
+            {
+                o.DefaultScheme = DefaultScheme;
+                o.AddScheme<TestHandler2>("auth1", "auth1");
+                o.AddScheme<TestHandler>("specific", "specific");
+            });
+            RegisterAuth(builder, o =>
+            {
+                o.ForwardDefault = "auth1";
+                o.ForwardChallenge = "specific";
+            });
+
+            var specific = new TestHandler();
+            services.AddSingleton(specific);
+            var forwardDefault = new TestHandler2();
+            services.AddSingleton(forwardDefault);
+
+            var sp = services.BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = sp;
+
+            await context.ChallengeAsync();
+            Assert.Equal(0, specific.SignOutCount);
+            Assert.Equal(0, specific.AuthenticateCount);
+            Assert.Equal(0, specific.ForbidCount);
+            Assert.Equal(1, specific.ChallengeCount);
+            Assert.Equal(0, specific.SignInCount);
+
+            Assert.Equal(0, forwardDefault.AuthenticateCount);
+            Assert.Equal(0, forwardDefault.ForbidCount);
+            Assert.Equal(0, forwardDefault.ChallengeCount);
+            Assert.Equal(0, forwardDefault.SignInCount);
+            Assert.Equal(0, forwardDefault.SignOutCount);
+        }
+
+        [Fact]
+        public async Task ForwardSelectorWinsOverDefault()
+        {
+            var services = new ServiceCollection().AddLogging();
+            var builder = services.AddAuthentication(o =>
+            {
+                o.DefaultScheme = DefaultScheme;
+                o.AddScheme<TestHandler2>("auth1", "auth1");
+                o.AddScheme<TestHandler3>("selector", "selector");
+                o.AddScheme<TestHandler>("specific", "specific");
+            });
+            RegisterAuth(builder, o =>
+            {
+                o.ForwardDefault = "auth1";
+                o.ForwardDefaultSelector = _ => "selector";
+            });
+
+            var specific = new TestHandler();
+            services.AddSingleton(specific);
+            var forwardDefault = new TestHandler2();
+            services.AddSingleton(forwardDefault);
+            var selector = new TestHandler3();
+            services.AddSingleton(selector);
+
+            var sp = services.BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = sp;
+
+            await context.AuthenticateAsync();
+            Assert.Equal(1, selector.AuthenticateCount);
+
+            await context.ForbidAsync();
+            Assert.Equal(1, selector.ForbidCount);
+
+            await context.ChallengeAsync();
+            Assert.Equal(1, selector.ChallengeCount);
+
+            if (SupportsSignOut)
+            {
+                await context.SignOutAsync();
+                Assert.Equal(1, selector.SignOutCount);
+            }
+            else
+            {
+                await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
+            }
+
+            if (SupportsSignIn)
+            {
+                await context.SignInAsync(new ClaimsPrincipal());
+                Assert.Equal(1, selector.SignInCount);
+            }
+            else
+            {
+                await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
+            }
+
+            Assert.Equal(0, forwardDefault.AuthenticateCount);
+            Assert.Equal(0, forwardDefault.ForbidCount);
+            Assert.Equal(0, forwardDefault.ChallengeCount);
+            Assert.Equal(0, forwardDefault.SignInCount);
+            Assert.Equal(0, forwardDefault.SignOutCount);
+            Assert.Equal(0, specific.AuthenticateCount);
+            Assert.Equal(0, specific.ForbidCount);
+            Assert.Equal(0, specific.ChallengeCount);
+            Assert.Equal(0, specific.SignInCount);
+            Assert.Equal(0, specific.SignOutCount);
+        }
+
+        [Fact]
+        public async Task NullForwardSelectorUsesDefault()
+        {
+            var services = new ServiceCollection().AddLogging();
+            var builder = services.AddAuthentication(o =>
+            {
+                o.DefaultScheme = DefaultScheme;
+                o.AddScheme<TestHandler2>("auth1", "auth1");
+                o.AddScheme<TestHandler3>("selector", "selector");
+                o.AddScheme<TestHandler>("specific", "specific");
+            });
+            RegisterAuth(builder, o =>
+            {
+                o.ForwardDefault = "auth1";
+                o.ForwardDefaultSelector = _ => null;
+            });
+
+            var specific = new TestHandler();
+            services.AddSingleton(specific);
+            var forwardDefault = new TestHandler2();
+            services.AddSingleton(forwardDefault);
+            var selector = new TestHandler3();
+            services.AddSingleton(selector);
+
+            var sp = services.BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = sp;
+
+            await context.AuthenticateAsync();
+            Assert.Equal(1, forwardDefault.AuthenticateCount);
+
+            await context.ForbidAsync();
+            Assert.Equal(1, forwardDefault.ForbidCount);
+
+            await context.ChallengeAsync();
+            Assert.Equal(1, forwardDefault.ChallengeCount);
+
+            if (SupportsSignOut)
+            {
+                await context.SignOutAsync();
+                Assert.Equal(1, forwardDefault.SignOutCount);
+            }
+            else
+            {
+                await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
+            }
+
+            if (SupportsSignIn)
+            {
+                await context.SignInAsync(new ClaimsPrincipal());
+                Assert.Equal(1, forwardDefault.SignInCount);
+            }
+            else
+            {
+                await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
+            }
+
+            Assert.Equal(0, selector.AuthenticateCount);
+            Assert.Equal(0, selector.ForbidCount);
+            Assert.Equal(0, selector.ChallengeCount);
+            Assert.Equal(0, selector.SignInCount);
+            Assert.Equal(0, selector.SignOutCount);
+            Assert.Equal(0, specific.AuthenticateCount);
+            Assert.Equal(0, specific.ForbidCount);
+            Assert.Equal(0, specific.ChallengeCount);
+            Assert.Equal(0, specific.SignInCount);
+            Assert.Equal(0, specific.SignOutCount);
+        }
+
+        [Fact]
+        public async Task SpecificForwardWinsOverSelectorAndDefault()
+        {
+            var services = new ServiceCollection().AddLogging();
+            var builder = services.AddAuthentication(o =>
+            {
+                o.DefaultScheme = DefaultScheme;
+                o.AddScheme<TestHandler2>("auth1", "auth1");
+                o.AddScheme<TestHandler3>("selector", "selector");
+                o.AddScheme<TestHandler>("specific", "specific");
+            });
+            RegisterAuth(builder, o =>
+            {
+                o.ForwardDefault = "auth1";
+                o.ForwardDefaultSelector = _ => "selector";
+                o.ForwardAuthenticate = "specific";
+                o.ForwardChallenge = "specific";
+                o.ForwardSignIn = "specific";
+                o.ForwardSignOut = "specific";
+                o.ForwardForbid = "specific";
+            });
+
+            var specific = new TestHandler();
+            services.AddSingleton(specific);
+            var forwardDefault = new TestHandler2();
+            services.AddSingleton(forwardDefault);
+            var selector = new TestHandler3();
+            services.AddSingleton(selector);
+
+            var sp = services.BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = sp;
+
+            await context.AuthenticateAsync();
+            Assert.Equal(1, specific.AuthenticateCount);
+
+            await context.ForbidAsync();
+            Assert.Equal(1, specific.ForbidCount);
+
+            await context.ChallengeAsync();
+            Assert.Equal(1, specific.ChallengeCount);
+
+            if (SupportsSignOut)
+            {
+                await context.SignOutAsync();
+                Assert.Equal(1, specific.SignOutCount);
+            }
+            else
+            {
+                await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
+            }
+
+            if (SupportsSignIn)
+            {
+                await context.SignInAsync(new ClaimsPrincipal());
+                Assert.Equal(1, specific.SignInCount);
+            }
+            else
+            {
+                await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
+            }
+
+            Assert.Equal(0, forwardDefault.AuthenticateCount);
+            Assert.Equal(0, forwardDefault.ForbidCount);
+            Assert.Equal(0, forwardDefault.ChallengeCount);
+            Assert.Equal(0, forwardDefault.SignInCount);
+            Assert.Equal(0, forwardDefault.SignOutCount);
+            Assert.Equal(0, selector.AuthenticateCount);
+            Assert.Equal(0, selector.ForbidCount);
+            Assert.Equal(0, selector.ChallengeCount);
+            Assert.Equal(0, selector.SignInCount);
+            Assert.Equal(0, selector.SignOutCount);
+        }
+
+        [Fact]
+        public async Task VerifySchemeDefaults()
+        {
+            var services = new ServiceCollection();
+            var builder = services.AddAuthentication();
+            RegisterAuth(builder, o => { });
+            var sp = services.BuildServiceProvider();
+            var schemeProvider = sp.GetRequiredService<IAuthenticationSchemeProvider>();
+            var scheme = await schemeProvider.GetSchemeAsync(DefaultScheme);
+            Assert.NotNull(scheme);
+            Assert.Equal(HandlerType, scheme.HandlerType);
+            Assert.Equal(DisplayName, scheme.DisplayName);
+        }
+    }
+}

+ 107 - 420
src/Security/test/Microsoft.AspNetCore.Authentication.Test/TwitterTests.cs

@@ -1,5 +1,11 @@
 // Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.
 
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Net.Http.Headers;
 using System;
 using System.Linq;
 using System.Net;
@@ -7,438 +13,31 @@ using System.Net.Http;
 using System.Security.Claims;
 using System.Text;
 using System.Threading.Tasks;
-using Microsoft.AspNetCore.Authentication.Tests;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.TestHost;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Net.Http.Headers;
 using Xunit;
 
 namespace Microsoft.AspNetCore.Authentication.Twitter
 {
-    public class TwitterTests
+    public class TwitterTests : RemoteAuthenticationTests<TwitterOptions>
     {
-        private void ConfigureDefaults(TwitterOptions o)
-        {
-            o.ConsumerKey = "whatever";
-            o.ConsumerSecret = "whatever";
-            o.SignInScheme = "auth1";
-        }
-
-        [Fact]
-        public async Task CanForwardDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = TwitterDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler>("auth1", "auth1");
-            })
-            .AddTwitter(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-            });
-
-            var forwardDefault = new TestHandler();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, forwardDefault.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, forwardDefault.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, forwardDefault.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-        }
-
-        [Fact]
-        public async Task ForwardSignInThrows()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = TwitterDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddTwitter(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardSignOut = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-        }
-
-        [Fact]
-        public async Task ForwardSignOutThrows()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = TwitterDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddTwitter(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardSignOut = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-        }
-
-        [Fact]
-        public async Task ForwardForbidWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = TwitterDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddTwitter(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardForbid = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.ForbidAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(1, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardAuthenticateWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = TwitterDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddTwitter(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardAuthenticate = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(1, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardChallengeWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = TwitterDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler>("specific", "specific");
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-            })
-            .AddTwitter(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardChallenge = "specific";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.ChallengeAsync();
-            Assert.Equal(0, specific.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(1, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-        }
-
-        [Fact]
-        public async Task ForwardSelectorWinsOverDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = TwitterDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddTwitter(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => "selector";
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, selector.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, selector.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, selector.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-            Assert.Equal(0, specific.SignOutCount);
-        }
-
-        [Fact]
-        public async Task NullForwardSelectorUsesDefault()
-        {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = TwitterDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddTwitter(o =>
-            {
-                ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => null;
-            });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, forwardDefault.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, forwardDefault.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, forwardDefault.ChallengeCount);
+        protected override string DefaultScheme => TwitterDefaults.AuthenticationScheme;
+        protected override Type HandlerType => typeof(TwitterHandler);
+        protected override bool SupportsSignIn { get => false; }
+        protected override bool SupportsSignOut { get => false; }
 
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, selector.AuthenticateCount);
-            Assert.Equal(0, selector.ForbidCount);
-            Assert.Equal(0, selector.ChallengeCount);
-            Assert.Equal(0, selector.SignInCount);
-            Assert.Equal(0, selector.SignOutCount);
-            Assert.Equal(0, specific.AuthenticateCount);
-            Assert.Equal(0, specific.ForbidCount);
-            Assert.Equal(0, specific.ChallengeCount);
-            Assert.Equal(0, specific.SignInCount);
-            Assert.Equal(0, specific.SignOutCount);
-        }
-
-        [Fact]
-        public async Task SpecificForwardWinsOverSelectorAndDefault()
+        protected override void RegisterAuth(AuthenticationBuilder services, Action<TwitterOptions> configure)
         {
-            var services = new ServiceCollection().AddLogging();
-            services.AddAuthentication(o =>
-            {
-                o.DefaultScheme = TwitterDefaults.AuthenticationScheme;
-                o.AddScheme<TestHandler2>("auth1", "auth1");
-                o.AddScheme<TestHandler3>("selector", "selector");
-                o.AddScheme<TestHandler>("specific", "specific");
-            })
-            .AddTwitter(o =>
+            services.AddTwitter(o =>
             {
                 ConfigureDefaults(o);
-                o.ForwardDefault = "auth1";
-                o.ForwardDefaultSelector = _ => "selector";
-                o.ForwardAuthenticate = "specific";
-                o.ForwardChallenge = "specific";
-                o.ForwardSignIn = "specific";
-                o.ForwardSignOut = "specific";
-                o.ForwardForbid = "specific";
+                configure.Invoke(o);
             });
-
-            var specific = new TestHandler();
-            services.AddSingleton(specific);
-            var forwardDefault = new TestHandler2();
-            services.AddSingleton(forwardDefault);
-            var selector = new TestHandler3();
-            services.AddSingleton(selector);
-
-            var sp = services.BuildServiceProvider();
-            var context = new DefaultHttpContext();
-            context.RequestServices = sp;
-
-            await context.AuthenticateAsync();
-            Assert.Equal(1, specific.AuthenticateCount);
-
-            await context.ForbidAsync();
-            Assert.Equal(1, specific.ForbidCount);
-
-            await context.ChallengeAsync();
-            Assert.Equal(1, specific.ChallengeCount);
-
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
-            await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
-
-            Assert.Equal(0, forwardDefault.AuthenticateCount);
-            Assert.Equal(0, forwardDefault.ForbidCount);
-            Assert.Equal(0, forwardDefault.ChallengeCount);
-            Assert.Equal(0, forwardDefault.SignInCount);
-            Assert.Equal(0, forwardDefault.SignOutCount);
-            Assert.Equal(0, selector.AuthenticateCount);
-            Assert.Equal(0, selector.ForbidCount);
-            Assert.Equal(0, selector.ChallengeCount);
-            Assert.Equal(0, selector.SignInCount);
-            Assert.Equal(0, selector.SignOutCount);
         }
 
-        [Fact]
-        public async Task VerifySignInSchemeCannotBeSetToSelf()
+        protected override void ConfigureDefaults(TwitterOptions o)
         {
-            var server = CreateServer(o =>
-            {
-                o.ConsumerKey = "Test Consumer Key";
-                o.ConsumerSecret = "Test Consumer Secret";
-                o.SignInScheme = TwitterDefaults.AuthenticationScheme;
-            });
-            var error = await Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync("https://example.com/challenge"));
-            Assert.Contains("cannot be set to itself", error.Message);
-        }
-
-        [Fact]
-        public async Task VerifySchemeDefaults()
-        {
-            var services = new ServiceCollection();
-            services.AddAuthentication().AddTwitter();
-            var sp = services.BuildServiceProvider();
-            var schemeProvider = sp.GetRequiredService<IAuthenticationSchemeProvider>();
-            var scheme = await schemeProvider.GetSchemeAsync(TwitterDefaults.AuthenticationScheme);
-            Assert.NotNull(scheme);
-            Assert.Equal("TwitterHandler", scheme.HandlerType.Name);
-            Assert.Equal(TwitterDefaults.AuthenticationScheme, scheme.DisplayName);
+            o.ConsumerKey = "whatever";
+            o.ConsumerSecret = "whatever";
+            o.SignInScheme = "auth1";
         }
 
         [Fact]
@@ -575,6 +174,94 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
             Assert.Contains("https://api.twitter.com/oauth/authenticate?oauth_token=", location);
         }
 
+        [Fact]
+        public async Task HandleRequestAsync_RedirectsToAccessDeniedPathWhenExplicitlySet()
+        {
+            var server = CreateServer(o =>
+            {
+                o.ConsumerKey = "Test Consumer Key";
+                o.ConsumerSecret = "Test Consumer Secret";
+                o.BackchannelHttpHandler = new TestHttpMessageHandler
+                {
+                    Sender = BackchannelRequestToken
+                };
+                o.AccessDeniedPath = "/access-denied";
+                o.Events.OnRemoteFailure = context => throw new InvalidOperationException("This event should not be called.");
+            },
+            async context =>
+            {
+                var properties = new AuthenticationProperties();
+                properties.Items["testkey"] = "testvalue";
+                await context.ChallengeAsync("Twitter", properties);
+                return true;
+            });
+            var transaction = await server.SendAsync("http://example.com/challenge");
+            Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
+            var location = transaction.Response.Headers.Location.AbsoluteUri;
+            Assert.Contains("https://api.twitter.com/oauth/authenticate?oauth_token=", location);
+            Assert.True(transaction.Response.Headers.TryGetValues(HeaderNames.SetCookie, out var setCookie));
+            Assert.True(SetCookieHeaderValue.TryParseList(setCookie.ToList(), out var setCookieValues));
+            Assert.Single(setCookieValues);
+            var setCookieValue = setCookieValues.Single();
+            var cookie = new CookieHeaderValue(setCookieValue.Name, setCookieValue.Value);
+
+            var request = new HttpRequestMessage(HttpMethod.Get, "/signin-twitter?denied=ABCDEFG");
+            request.Headers.Add(HeaderNames.Cookie, cookie.ToString());
+            var client = server.CreateClient();
+            var response = await client.SendAsync(request);
+
+            Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
+            Assert.Equal("/access-denied?ReturnUrl=%2Fchallenge", response.Headers.Location.ToString());
+        }
+
+        [Fact]
+        public async Task BadCallbackCallsAccessDeniedWithState()
+        {
+            var server = CreateServer(o =>
+            {
+                o.ConsumerKey = "Test Consumer Key";
+                o.ConsumerSecret = "Test Consumer Secret";
+                o.BackchannelHttpHandler = new TestHttpMessageHandler
+                {
+                    Sender = BackchannelRequestToken
+                };
+                o.Events = new TwitterEvents()
+                {
+                    OnAccessDenied = context =>
+                    {
+                        Assert.NotNull(context.Properties);
+                        Assert.Equal("testvalue", context.Properties.Items["testkey"]);
+                        context.Response.StatusCode = StatusCodes.Status406NotAcceptable;
+                        context.HandleResponse();
+                        return Task.CompletedTask;
+                    }
+                };
+            },
+            async context =>
+            {
+                var properties = new AuthenticationProperties();
+                properties.Items["testkey"] = "testvalue";
+                await context.ChallengeAsync("Twitter", properties);
+                return true;
+            });
+            var transaction = await server.SendAsync("http://example.com/challenge");
+            Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
+            var location = transaction.Response.Headers.Location.AbsoluteUri;
+            Assert.Contains("https://api.twitter.com/oauth/authenticate?oauth_token=", location);
+            Assert.True(transaction.Response.Headers.TryGetValues(HeaderNames.SetCookie, out var setCookie));
+            Assert.True(SetCookieHeaderValue.TryParseList(setCookie.ToList(), out var setCookieValues));
+            Assert.Single(setCookieValues);
+            var setCookieValue = setCookieValues.Single();
+            var cookie = new CookieHeaderValue(setCookieValue.Name, setCookieValue.Value);
+
+            var request = new HttpRequestMessage(HttpMethod.Get, "/signin-twitter?denied=ABCDEFG");
+            request.Headers.Add(HeaderNames.Cookie, cookie.ToString());
+            var client = server.CreateClient();
+            var response = await client.SendAsync(request);
+
+            Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode);
+        }
+
         [Fact]
         public async Task BadCallbackCallsRemoteAuthFailedWithState()
         {
@@ -591,7 +278,7 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
                     OnRemoteFailure = context =>
                     {
                         Assert.NotNull(context.Failure);
-                        Assert.Equal("The user denied permissions.", context.Failure.Message);
+                        Assert.Equal("Access was denied by the resource owner or by the remote server.", context.Failure.Message);
                         Assert.NotNull(context.Properties);
                         Assert.Equal("testvalue", context.Properties.Items["testkey"]);
                         context.Response.StatusCode = StatusCodes.Status406NotAcceptable;

+ 65 - 0
src/Security/test/Microsoft.AspNetCore.Authorization.Test/AuthorizationAppBuilderExtensionsTests.cs

@@ -0,0 +1,65 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authorization.Test.TestObjects;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Builder.Internal;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Endpoints;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Authorization.Test
+{
+    public class AuthorizationAppBuilderExtensionsTests
+    {
+        [Fact]
+        public async Task UseAuthorization_RegistersMiddleware()
+        {
+            // Arrange
+            var authenticationService = new TestAuthenticationService();
+            var services = CreateServices(authenticationService);
+
+            var app = new ApplicationBuilder(services);
+
+            app.UseAuthorization();
+
+            var appFunc = app.Build();
+
+            var endpoint = new Endpoint(
+                null,
+                new EndpointMetadataCollection(new AuthorizeAttribute()),
+                "Test endpoint");
+
+            var httpContext = new DefaultHttpContext();
+            httpContext.RequestServices = services;
+            httpContext.SetEndpoint(endpoint);
+
+            // Act
+            await appFunc(httpContext);
+
+            // Assert
+            Assert.True(authenticationService.ChallengeCalled);
+        }
+
+        private IServiceProvider CreateServices(IAuthenticationService authenticationService)
+        {
+            var services = new ServiceCollection();
+
+            services.AddAuthorization(options => { });
+            services.AddAuthorizationPolicyEvaluator();
+            services.AddLogging();
+            services.AddSingleton(authenticationService);
+
+            var serviceProvder = services.BuildServiceProvider();
+
+            return serviceProvder;
+        }
+    }
+}

+ 63 - 0
src/Security/test/Microsoft.AspNetCore.Authorization.Test/AuthorizationEndpointConventionBuilderExtensionsTests.cs

@@ -0,0 +1,63 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Routing.Patterns;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Authorization.Test
+{
+    public class AuthorizationEndpointConventionBuilderExtensionsTests
+    {
+        [Fact]
+        public void RequireAuthorization_IAuthorizeData()
+        {
+            // Arrange
+            var builder = new TestEndpointConventionBuilder();
+            var metadata = new AuthorizeAttribute();
+
+            // Act
+            builder.RequireAuthorization(metadata);
+
+            // Assert
+            var convention = Assert.Single(builder.Conventions);
+
+            var endpointModel = new RouteEndpointModel((context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0);
+            convention(endpointModel);
+
+            Assert.Equal(metadata, Assert.Single(endpointModel.Metadata));
+        }
+
+        [Fact]
+        public void RequireAuthorization_PolicyName()
+        {
+            // Arrange
+            var builder = new TestEndpointConventionBuilder();
+
+            // Act
+            builder.RequireAuthorization("policy");
+
+            // Assert
+            var convention = Assert.Single(builder.Conventions);
+
+            var endpointModel = new RouteEndpointModel((context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0);
+            convention(endpointModel);
+
+            Assert.Equal("policy", Assert.IsAssignableFrom<IAuthorizeData>(Assert.Single(endpointModel.Metadata)).Policy);
+        }
+
+        private class TestEndpointConventionBuilder : IEndpointConventionBuilder
+        {
+            public IList<Action<EndpointModel>> Conventions { get; } = new List<Action<EndpointModel>>();
+
+            public void Apply(Action<EndpointModel> convention)
+            {
+                Conventions.Add(convention);
+            }
+        }
+    }
+}

+ 459 - 0
src/Security/test/Microsoft.AspNetCore.Authorization.Test/AuthorizationMiddlewareTests.cs

@@ -0,0 +1,459 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Authorization.Infrastructure;
+using Microsoft.AspNetCore.Authorization.Test.TestObjects;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Endpoints;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Authorization.Test
+{
+    public class AuthorizationMiddlewareTests
+    {
+        [Fact]
+        public async Task NoEndpoint_AnonymousUser_Allows()
+        {
+            // Arrange
+            var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
+            var next = new TestRequestDelegate();
+
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var context = GetHttpContext(anonymous: true);
+
+            // Act
+            await middleware.Invoke(context);
+
+            // Assert
+            Assert.True(next.Called);
+        }
+
+        [Fact]
+        public async Task NoEndpointWithRequired_AnonymousUser_Challenges()
+        {
+            // Arrange
+            var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            policyProvider.Setup(p => p.GetRequiredPolicyAsync()).ReturnsAsync(policy);
+            var next = new TestRequestDelegate();
+
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var context = GetHttpContext(anonymous: true);
+
+            // Act
+            await middleware.Invoke(context);
+
+            // Assert
+            Assert.False(next.Called);
+        }
+
+        [Fact]
+        public async Task HasEndpointWithoutAuth_AnonymousUser_Allows()
+        {
+            // Arrange
+            var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
+            var next = new TestRequestDelegate();
+
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint());
+
+            // Act
+            await middleware.Invoke(context);
+
+            // Assert
+            Assert.True(next.Called);
+        }
+
+        [Fact]
+        public async Task HasEndpointWithRequiredWithoutAuth_AnonymousUser_Challenges()
+        {
+            // Arrange
+            var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
+            policyProvider.Setup(p => p.GetRequiredPolicyAsync()).ReturnsAsync(policy);
+            var next = new TestRequestDelegate();
+
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint());
+
+            // Act
+            await middleware.Invoke(context);
+
+            // Assert
+            Assert.False(next.Called);
+        }
+
+        [Fact]
+        public async Task HasEndpointWithAuth_AnonymousUser_Challenges()
+        {
+            // Arrange
+            var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
+            var next = new TestRequestDelegate();
+            var authenticationService = new TestAuthenticationService();
+
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService);
+
+            // Act
+            await middleware.Invoke(context);
+
+            // Assert
+            Assert.False(next.Called);
+            Assert.True(authenticationService.ChallengeCalled);
+        }
+
+        [Fact]
+        public async Task HasEndpointWithAuth_AnonymousUser_ChallengePerScheme()
+        {
+            // Arrange
+            var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().AddAuthenticationSchemes("schema1", "schema2").Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
+            var next = new TestRequestDelegate();
+            var authenticationService = new TestAuthenticationService();
+
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService);
+
+            // Act
+            await middleware.Invoke(context);
+
+            // Assert
+            Assert.False(next.Called);
+            Assert.Equal(2, authenticationService.ChallengeCount);
+        }
+
+        [Fact]
+        public async Task OnAuthorizationAsync_WillCallPolicyProvider()
+        {
+            // Arrange
+            var policy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            var getPolicyCount = 0;
+            var getRequiredPolicyCount = 0;
+            policyProvider.Setup(p => p.GetPolicyAsync(It.IsAny<string>())).ReturnsAsync(policy)
+                .Callback(() => getPolicyCount++);
+            policyProvider.Setup(p => p.GetRequiredPolicyAsync()).ReturnsAsync(policy)
+                .Callback(() => getRequiredPolicyCount++);
+            var next = new TestRequestDelegate();
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint(new AuthorizeAttribute("whatever")));
+
+            // Act & Assert
+            await middleware.Invoke(context);
+            Assert.Equal(1, getPolicyCount);
+            Assert.Equal(1, getRequiredPolicyCount);
+            Assert.Equal(1, next.CalledCount);
+
+            await middleware.Invoke(context);
+            Assert.Equal(2, getPolicyCount);
+            Assert.Equal(2, getRequiredPolicyCount);
+            Assert.Equal(2, next.CalledCount);
+
+            await middleware.Invoke(context);
+            Assert.Equal(3, getPolicyCount);
+            Assert.Equal(3, getRequiredPolicyCount);
+            Assert.Equal(3, next.CalledCount);
+        }
+
+        [Fact]
+        public async Task Invoke_ValidClaimShouldNotFail()
+        {
+            // Arrange
+            var policy = new AuthorizationPolicyBuilder().RequireClaim("Permission", "CanViewPage").Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
+            var next = new TestRequestDelegate();
+
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var context = GetHttpContext(endpoint: CreateEndpoint(new AuthorizeAttribute()));
+
+            // Act
+            await middleware.Invoke(context);
+
+            // Assert
+            Assert.True(next.Called);
+        }
+
+        [Fact]
+        public async Task HasEndpointWithAuthAndAllowAnonymous_AnonymousUser_Allows()
+        {
+            // Arrange
+            var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
+            var next = new TestRequestDelegate();
+            var authenticationService = new TestAuthenticationService();
+
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint(new AuthorizeAttribute(), new AllowAnonymousAttribute()), authenticationService: authenticationService);
+
+            // Act
+            await middleware.Invoke(context);
+
+            // Assert
+            Assert.True(next.Called);
+            Assert.False(authenticationService.ChallengeCalled);
+        }
+
+        [Fact]
+        public async Task HasEndpointWithAuth_AuthenticatedUser_Allows()
+        {
+            // Arrange
+            var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
+            var next = new TestRequestDelegate();
+            var authenticationService = new TestAuthenticationService();
+
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var context = GetHttpContext(endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService);
+
+            // Act
+            await middleware.Invoke(context);
+
+            // Assert
+            Assert.True(next.Called);
+            Assert.False(authenticationService.ChallengeCalled);
+        }
+
+        [Fact]
+        public async Task Invoke_AuthSchemesFailShouldSetEmptyPrincipalOnContext()
+        {
+            // Arrange
+            var policy = new AuthorizationPolicyBuilder("Fails").RequireAuthenticatedUser().Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
+            var next = new TestRequestDelegate();
+            var authenticationService = new TestAuthenticationService();
+
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var context = GetHttpContext(endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService);
+
+            // Act
+            await middleware.Invoke(context);
+
+            // Assert
+            Assert.False(next.Called);
+            Assert.NotNull(context.User?.Identity);
+            Assert.True(authenticationService.AuthenticateCalled);
+            Assert.True(authenticationService.ChallengeCalled);
+        }
+
+        [Fact]
+        public async Task Invoke_SingleValidClaimShouldSucceed()
+        {
+            // Arrange
+            var policy = new AuthorizationPolicyBuilder().RequireClaim("Permission", "CanViewComment", "CanViewPage").Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
+            var next = new TestRequestDelegate();
+
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var context = GetHttpContext(endpoint: CreateEndpoint(new AuthorizeAttribute()));
+
+            // Act
+            await middleware.Invoke(context);
+
+            // Assert
+            Assert.True(next.Called);
+        }
+
+        [Fact]
+        public async Task AuthZResourceShouldBeEndpoint()
+        {
+            // Arrange
+            object resource = null;
+            var policy = new AuthorizationPolicyBuilder().RequireAssertion(c =>
+            {
+                resource = c.Resource;
+                return true;
+            }).Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
+            var next = new TestRequestDelegate();
+
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var endpoint = CreateEndpoint(new AuthorizeAttribute());
+            var context = GetHttpContext(endpoint: endpoint);
+
+            // Act
+            await middleware.Invoke(context);
+
+            // Assert
+            Assert.Equal(endpoint, resource);
+        }
+
+        [Fact]
+        public async Task Invoke_RequireUnknownRoleShouldForbid()
+        {
+            // Arrange
+            var policy = new AuthorizationPolicyBuilder().RequireRole("Wut").Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
+            var next = new TestRequestDelegate();
+            var authenticationService = new TestAuthenticationService();
+
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var context = GetHttpContext(endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService);
+
+            // Act
+            await middleware.Invoke(context);
+
+            // Assert
+            Assert.False(next.Called);
+            Assert.False(authenticationService.ChallengeCalled);
+            Assert.True(authenticationService.ForbidCalled);
+        }
+
+        [Fact]
+        public async Task Invoke_RequireUnknownRole_ForbidPerScheme()
+        {
+            // Arrange
+            var policy = new AuthorizationPolicyBuilder().RequireRole("Wut").AddAuthenticationSchemes("Basic", "Bearer").Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
+            var next = new TestRequestDelegate();
+            var authenticationService = new TestAuthenticationService();
+
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var context = GetHttpContext(endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService);
+
+            // Act
+            await middleware.Invoke(context);
+
+            // Assert
+            Assert.False(next.Called);
+            Assert.Equal(2, authenticationService.ForbidCount);
+        }
+
+        [Fact]
+        public async Task Invoke_InvalidClaimShouldForbid()
+        {
+            // Arrange
+            var policy = new AuthorizationPolicyBuilder()
+                .RequireClaim("Permission", "CanViewComment")
+                .Build();
+            var policyProvider = new Mock<IAuthorizationPolicyProvider>();
+            policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
+            var next = new TestRequestDelegate();
+            var authenticationService = new TestAuthenticationService();
+
+            var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
+            var context = GetHttpContext(endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService);
+
+            // Act
+            await middleware.Invoke(context);
+
+            // Assert
+            Assert.False(next.Called);
+            Assert.False(authenticationService.ChallengeCalled);
+            Assert.True(authenticationService.ForbidCalled);
+        }
+
+        private AuthorizationMiddleware CreateMiddleware(RequestDelegate requestDelegate = null, IAuthorizationPolicyProvider policyProvider = null)
+        {
+            requestDelegate = requestDelegate ?? ((context) => Task.CompletedTask);
+
+            return new AuthorizationMiddleware(requestDelegate, policyProvider);
+        }
+
+        private Endpoint CreateEndpoint(params object[] metadata)
+        {
+            return new Endpoint(context => Task.CompletedTask, new EndpointMetadataCollection(metadata), "Test endpoint");
+        }
+
+        private HttpContext GetHttpContext(
+            bool anonymous = false,
+            Action<IServiceCollection> registerServices = null,
+            Endpoint endpoint = null,
+            IAuthenticationService authenticationService = null)
+        {
+            var basicPrincipal = new ClaimsPrincipal(
+                new ClaimsIdentity(
+                    new Claim[] {
+                        new Claim("Permission", "CanViewPage"),
+                        new Claim(ClaimTypes.Role, "Administrator"),
+                        new Claim(ClaimTypes.Role, "User"),
+                        new Claim(ClaimTypes.NameIdentifier, "John")},
+                        "Basic"));
+
+            var validUser = basicPrincipal;
+
+            var bearerIdentity = new ClaimsIdentity(
+                    new Claim[] {
+                        new Claim("Permission", "CupBearer"),
+                        new Claim(ClaimTypes.Role, "Token"),
+                        new Claim(ClaimTypes.NameIdentifier, "John Bear")},
+                        "Bearer");
+
+            validUser.AddIdentity(bearerIdentity);
+
+            // ServiceProvider
+            var serviceCollection = new ServiceCollection();
+
+            authenticationService = authenticationService ?? Mock.Of<IAuthenticationService>();
+
+            serviceCollection.AddSingleton(authenticationService);
+            serviceCollection.AddOptions();
+            serviceCollection.AddLogging();
+            serviceCollection.AddAuthorization();
+            serviceCollection.AddAuthorizationPolicyEvaluator();
+            registerServices?.Invoke(serviceCollection);
+
+            var serviceProvider = serviceCollection.BuildServiceProvider();
+
+            //// HttpContext
+            var httpContext = new DefaultHttpContext();
+            if (endpoint != null)
+            {
+                httpContext.SetEndpoint(endpoint);
+            }
+            httpContext.RequestServices = serviceProvider;
+            if (!anonymous)
+            {
+                httpContext.User = validUser;
+            }
+
+            return httpContext;
+        }
+
+        private class TestRequestDelegate
+        {
+            private readonly int _statusCode;
+
+            public bool Called => CalledCount > 0;
+            public int CalledCount { get; private set; }
+
+            public TestRequestDelegate(int statusCode = 200)
+            {
+                _statusCode = statusCode;
+            }
+
+            public Task Invoke(HttpContext context)
+            {
+                CalledCount++;
+                context.Response.StatusCode = _statusCode;
+                return Task.CompletedTask;
+            }
+        }
+    }
+}

+ 10 - 0
src/Security/test/Microsoft.AspNetCore.Authorization.Test/DefaultAuthorizationServiceTests.cs

@@ -1025,6 +1025,11 @@ namespace Microsoft.AspNetCore.Authorization.Test
                 return Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
             }
 
+            public Task<AuthorizationPolicy> GetRequiredPolicyAsync()
+            {
+                return Task.FromResult<AuthorizationPolicy>(null);
+            }
+
             public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
             {
                 return Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
@@ -1059,6 +1064,11 @@ namespace Microsoft.AspNetCore.Authorization.Test
                 return Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
             }
 
+            public Task<AuthorizationPolicy> GetRequiredPolicyAsync()
+            {
+                return Task.FromResult<AuthorizationPolicy>(null);
+            }
+
             public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
             {
                 return Task.FromResult(new AuthorizationPolicyBuilder().RequireClaim(policyName).Build());

+ 4 - 1
src/Security/test/Microsoft.AspNetCore.Authorization.Test/Microsoft.AspNetCore.Authorization.Test.csproj

@@ -1,7 +1,8 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+    <NoWarn>$(NoWarn);NU1605</NoWarn>
   </PropertyGroup>
 
   <ItemGroup>
@@ -11,8 +12,10 @@
 
   <ItemGroup>
     <PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpPackageVersion)" />
+    <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="$(MicrosoftAspNetCoreHttpAbstractionsPackageVersion)" />
     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
     <PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
+    <PackageReference Include="Moq" Version="$(MoqPackageVersion)" />
   </ItemGroup>
 
 </Project>

+ 58 - 0
src/Security/test/Microsoft.AspNetCore.Authorization.Test/TestObjects/TestAuthenticationService.cs

@@ -0,0 +1,58 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authorization.Test.TestObjects
+{
+    public class TestAuthenticationService : IAuthenticationService
+    {
+        public bool ChallengeCalled => ChallengeCount > 0;
+        public bool ForbidCalled => ForbidCount > 0;
+        public bool AuthenticateCalled => AuthenticateCount > 0;
+
+        public int ChallengeCount { get; private set; }
+        public int ForbidCount { get; private set; }
+        public int AuthenticateCount { get; private set; }
+
+        public Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme)
+        {
+            AuthenticateCount++;
+
+            var identity = context.User.Identities.SingleOrDefault(i => i.AuthenticationType == scheme);
+            if (identity != null)
+            {
+                return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(identity), scheme)));
+            }
+
+            return Task.FromResult(AuthenticateResult.Fail("Denied"));
+        }
+
+        public Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties)
+        {
+            ChallengeCount++;
+            return Task.CompletedTask;
+        }
+
+        public Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties)
+        {
+            ForbidCount++;
+            return Task.CompletedTask;
+        }
+
+        public Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 1 - 1
src/Security/test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
   </PropertyGroup>
 
   <ItemGroup>

+ 1 - 1
src/Security/test/Microsoft.AspNetCore.CookiePolicy.Test/Microsoft.AspNetCore.CookiePolicy.Test.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
   </PropertyGroup>
 
   <ItemGroup>

+ 5 - 0
src/Security/test/Microsoft.Owin.Security.Interop.Test/CookieInteropTests.cs

@@ -1,6 +1,10 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
+/* See https://github.com/aspnet/AspNetCore/issues/4074.
+
+This test is was disabled as a part of changing frameworks. This test will need to be re-written using separate .NET Core and .NET Framework processes.
+
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
@@ -330,3 +334,4 @@ namespace Microsoft.Owin.Security.Interop
     }
 }
 
+*/

+ 2 - 1
src/Security/test/Microsoft.Owin.Security.Interop.Test/Microsoft.Owin.Security.Interop.Test.csproj

@@ -2,10 +2,11 @@
 
   <PropertyGroup>
     <TargetFramework>net461</TargetFramework>
+    <!-- Temporarily disables tests for this project. They need to be refactored now that aspnetcore only supports netcoreapp3.0. -->
+    <IsTestProject>false</IsTestProject>
   </PropertyGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.Cookies\Microsoft.AspNetCore.Authentication.Cookies.csproj" />
     <ProjectReference Include="..\..\src\Microsoft.Owin.Security.Interop\Microsoft.Owin.Security.Interop.csproj" />
   </ItemGroup>
 

+ 3 - 0
src/Security/test/Microsoft.Owin.Security.Interop.Test/TicketInteropTests.cs

@@ -1,6 +1,8 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
+/*
+
 using System;
 using System.Linq;
 using System.Security.Claims;
@@ -87,5 +89,6 @@ namespace Microsoft.Owin.Security.Interop.Test
         }
     }
 }
+*/
 
 

+ 3 - 3
src/Security/version.props

@@ -1,7 +1,7 @@
-<Project>
+<Project>
   <PropertyGroup>
-    <VersionPrefix>2.2.0</VersionPrefix>
-    <VersionSuffix>rtm</VersionSuffix>
+    <VersionPrefix>3.0.0</VersionPrefix>
+    <VersionSuffix>alpha1</VersionSuffix>
     <PackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(VersionSuffix)' == 'rtm' ">$(VersionPrefix)</PackageVersion>
     <PackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(VersionSuffix)' != 'rtm' ">$(VersionPrefix)-$(VersionSuffix)-final</PackageVersion>
     <BuildNumber Condition="'$(BuildNumber)' == ''">t000</BuildNumber>