Fix up how OIDC errors flow (#4520)

* Add regression test for #4384
* Fix up how OIDC errors flow #4384
This commit is contained in:
Chris Ross 2019-01-15 20:59:40 -08:00 committed by GitHub
parent 86071c9db4
commit d838165642
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 128 additions and 68 deletions

View File

@ -73,6 +73,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.TestHo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authorization", "..\Security\Authorization\Core\src\Microsoft.AspNetCore.Authorization.csproj", "{C57DFBC2-A887-44B4-A149-7ABFA6D98F7E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.OpenIdConnect", "..\Security\Authentication\OpenIdConnect\src\Microsoft.AspNetCore.Authentication.OpenIdConnect.csproj", "{F44054A2-DAC9-467F-B899-F35F9DCDAE9C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.OAuth", "..\Security\Authentication\OAuth\src\Microsoft.AspNetCore.Authentication.OAuth.csproj", "{406DF28A-0B58-408E-96B0-2D373EE36352}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication", "..\Security\Authentication\Core\src\Microsoft.AspNetCore.Authentication.csproj", "{A5E7BA46-B76B-467A-88FA-38E04D0A42FC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -383,6 +389,42 @@ Global
{C57DFBC2-A887-44B4-A149-7ABFA6D98F7E}.Release|x64.Build.0 = Release|Any CPU
{C57DFBC2-A887-44B4-A149-7ABFA6D98F7E}.Release|x86.ActiveCfg = Release|Any CPU
{C57DFBC2-A887-44B4-A149-7ABFA6D98F7E}.Release|x86.Build.0 = Release|Any CPU
{F44054A2-DAC9-467F-B899-F35F9DCDAE9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F44054A2-DAC9-467F-B899-F35F9DCDAE9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F44054A2-DAC9-467F-B899-F35F9DCDAE9C}.Debug|x64.ActiveCfg = Debug|Any CPU
{F44054A2-DAC9-467F-B899-F35F9DCDAE9C}.Debug|x64.Build.0 = Debug|Any CPU
{F44054A2-DAC9-467F-B899-F35F9DCDAE9C}.Debug|x86.ActiveCfg = Debug|Any CPU
{F44054A2-DAC9-467F-B899-F35F9DCDAE9C}.Debug|x86.Build.0 = Debug|Any CPU
{F44054A2-DAC9-467F-B899-F35F9DCDAE9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F44054A2-DAC9-467F-B899-F35F9DCDAE9C}.Release|Any CPU.Build.0 = Release|Any CPU
{F44054A2-DAC9-467F-B899-F35F9DCDAE9C}.Release|x64.ActiveCfg = Release|Any CPU
{F44054A2-DAC9-467F-B899-F35F9DCDAE9C}.Release|x64.Build.0 = Release|Any CPU
{F44054A2-DAC9-467F-B899-F35F9DCDAE9C}.Release|x86.ActiveCfg = Release|Any CPU
{F44054A2-DAC9-467F-B899-F35F9DCDAE9C}.Release|x86.Build.0 = Release|Any CPU
{406DF28A-0B58-408E-96B0-2D373EE36352}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{406DF28A-0B58-408E-96B0-2D373EE36352}.Debug|Any CPU.Build.0 = Debug|Any CPU
{406DF28A-0B58-408E-96B0-2D373EE36352}.Debug|x64.ActiveCfg = Debug|Any CPU
{406DF28A-0B58-408E-96B0-2D373EE36352}.Debug|x64.Build.0 = Debug|Any CPU
{406DF28A-0B58-408E-96B0-2D373EE36352}.Debug|x86.ActiveCfg = Debug|Any CPU
{406DF28A-0B58-408E-96B0-2D373EE36352}.Debug|x86.Build.0 = Debug|Any CPU
{406DF28A-0B58-408E-96B0-2D373EE36352}.Release|Any CPU.ActiveCfg = Release|Any CPU
{406DF28A-0B58-408E-96B0-2D373EE36352}.Release|Any CPU.Build.0 = Release|Any CPU
{406DF28A-0B58-408E-96B0-2D373EE36352}.Release|x64.ActiveCfg = Release|Any CPU
{406DF28A-0B58-408E-96B0-2D373EE36352}.Release|x64.Build.0 = Release|Any CPU
{406DF28A-0B58-408E-96B0-2D373EE36352}.Release|x86.ActiveCfg = Release|Any CPU
{406DF28A-0B58-408E-96B0-2D373EE36352}.Release|x86.Build.0 = Release|Any CPU
{A5E7BA46-B76B-467A-88FA-38E04D0A42FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5E7BA46-B76B-467A-88FA-38E04D0A42FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5E7BA46-B76B-467A-88FA-38E04D0A42FC}.Debug|x64.ActiveCfg = Debug|Any CPU
{A5E7BA46-B76B-467A-88FA-38E04D0A42FC}.Debug|x64.Build.0 = Debug|Any CPU
{A5E7BA46-B76B-467A-88FA-38E04D0A42FC}.Debug|x86.ActiveCfg = Debug|Any CPU
{A5E7BA46-B76B-467A-88FA-38E04D0A42FC}.Debug|x86.Build.0 = Debug|Any CPU
{A5E7BA46-B76B-467A-88FA-38E04D0A42FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5E7BA46-B76B-467A-88FA-38E04D0A42FC}.Release|Any CPU.Build.0 = Release|Any CPU
{A5E7BA46-B76B-467A-88FA-38E04D0A42FC}.Release|x64.ActiveCfg = Release|Any CPU
{A5E7BA46-B76B-467A-88FA-38E04D0A42FC}.Release|x64.Build.0 = Release|Any CPU
{A5E7BA46-B76B-467A-88FA-38E04D0A42FC}.Release|x86.ActiveCfg = Release|Any CPU
{A5E7BA46-B76B-467A-88FA-38E04D0A42FC}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -418,6 +460,9 @@ Global
{38027842-48A7-4A64-A44F-004BAF0AB450} = {84622717-F98A-4DE2-806E-1EF89C45C0EB}
{C520D48E-87A0-463D-B4CF-3E6B68F8F4D0} = {84622717-F98A-4DE2-806E-1EF89C45C0EB}
{C57DFBC2-A887-44B4-A149-7ABFA6D98F7E} = {84622717-F98A-4DE2-806E-1EF89C45C0EB}
{F44054A2-DAC9-467F-B899-F35F9DCDAE9C} = {84622717-F98A-4DE2-806E-1EF89C45C0EB}
{406DF28A-0B58-408E-96B0-2D373EE36352} = {84622717-F98A-4DE2-806E-1EF89C45C0EB}
{A5E7BA46-B76B-467A-88FA-38E04D0A42FC} = {84622717-F98A-4DE2-806E-1EF89C45C0EB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {81AADD49-473B-43ED-8A08-F6B7A058AA39}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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.Authorization;
using System.Net;
@ -11,7 +11,9 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Xunit;
@ -158,5 +160,60 @@ namespace Microsoft.AspNetCore.Authentication.AzureAD.FunctionalTests
// Assert
Assert.Equal(expectedStatusCode, response.StatusCode);
}
[Fact]
public async Task ADB2C_EndToEnd_PasswordReset()
{
var client = Factory.WithWebHostBuilder(builder => builder.ConfigureTestServices(
services =>
{
services
.AddAuthentication(AzureADB2CDefaults.AuthenticationScheme)
.AddAzureADB2C(o =>
{
o.Instance = "https://login.microsoftonline.com/tfp/";
o.ClientId = "ClientId";
o.CallbackPath = "/signin-oidc";
o.Domain = "test.onmicrosoft.com";
o.SignUpSignInPolicyId = "B2C_1_SiUpIn";
o.ResetPasswordPolicyId = "B2C_1_SSPR";
o.EditProfilePolicyId = "B2C_1_SiPe";
});
services.Configure<OpenIdConnectOptions>(AzureADB2CDefaults.OpenIdScheme, o =>
{
o.Configuration = new OpenIdConnectConfiguration()
{
Issuer = "https://www.example.com",
TokenEndpoint = "https://www.example.com/token",
AuthorizationEndpoint = "https://www.example.com/authorize",
EndSessionEndpoint = "https://www.example.com/logout"
};
// CookieContainer doesn't allow cookies from other paths
o.CorrelationCookie.Path = "/";
o.NonceCookie.Path = "/";
});
services.AddMvc(o => o.Filters.Add(
new AuthorizeFilter(new AuthorizationPolicyBuilder(new[] { AzureADB2CDefaults.AuthenticationScheme })
.RequireAuthenticatedUser().Build())));
})).CreateClient(new WebApplicationFactoryClientOptions() { AllowAutoRedirect = false });
var response = await client.GetAsync("/api/get");
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
var location = response.Headers.Location;
Assert.StartsWith("https://www.example.com/authorize", location.AbsoluteUri);
var queryString = location.Query;
var query = QueryHelpers.ParseQuery(queryString);
var state = query["state"];
Assert.False(StringValues.IsNullOrEmpty(state));
// Mock Authorization response
response = await client.GetAsync($"/signin-oidc?error=access_denied&error_description=AADB2C90118&state={state}");
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/AzureADB2C/Account/ResetPassword/AzureADB2C", response.Headers.Location.OriginalString);
}
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
@ -9,16 +9,8 @@
<Reference Include="Microsoft.AspNetCore.Authentication.AzureADB2C.UI" />
<Reference Include="Microsoft.AspNetCore.Authorization" />
<Reference Include="Microsoft.AspNetCore.DataProtection.Extensions" />
<Reference Include="Microsoft.AspNetCore.Hosting" />
<Reference Include="Microsoft.AspNetCore" />
<Reference Include="Microsoft.AspNetCore.Mvc" />
<Reference Include="Microsoft.AspNetCore.Server.IISIntegration" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
<Reference Include="Microsoft.Extensions.Configuration.CommandLine" />
<Reference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<Reference Include="Microsoft.Extensions.Configuration.UserSecrets" />
<Reference Include="Microsoft.Extensions.Logging.Configuration" />
<Reference Include="Microsoft.Extensions.Logging.Console" />
<Reference Include="Microsoft.Extensions.Logging.Debug" />
<Reference Include="Microsoft.NET.Sdk.Razor" PrivateAssets="All" />
</ItemGroup>

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;
@ -23,55 +23,8 @@ namespace AzureAD.WebSite
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
var builder = new WebHostBuilder()
.UseKestrel((builderContext, options) =>
{
options.Configure(builderContext.Configuration.GetSection("Kestrel"));
})
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
if (env.IsDevelopment())
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
.UseIISIntegration()
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
});
if (args != null)
{
builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build());
}
builder.UseStartup<Startup>();
return builder;
return WebHost.CreateDefaultBuilder()
.UseStartup<Startup>();
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;
@ -92,5 +92,10 @@ namespace Microsoft.AspNetCore.Authentication
{
return new HandleRequestResult() { Skipped = true };
}
public new static HandleRequestResult NoResult()
{
return new HandleRequestResult() { None = true };
}
}
}

View File

@ -283,7 +283,7 @@ namespace Microsoft.AspNetCore.Authentication
return HandleRequestResult.Handle();
}
return HandleRequestResult.Fail("Access was denied by the resource owner or by the remote server.", properties);
return HandleRequestResult.NoResult();
}
}
}
}

View File

@ -71,7 +71,9 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
// 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 result = await HandleAccessDeniedErrorAsync(properties);
return !result.None ? result
: HandleRequestResult.Fail("Access was denied by the resource owner or by the remote server.", properties);
}
var failureMessage = new StringBuilder();

View File

@ -560,7 +560,11 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
// 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);
var result = await HandleAccessDeniedErrorAsync(properties);
if (!result.None)
{
return result;
}
}
return HandleRequestResult.Fail(CreateOpenIdConnectProtocolException(authorizationResponse, response: null), properties);

View File

@ -62,7 +62,9 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
// 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 result = await HandleAccessDeniedErrorAsync(properties);
return !result.None ? result
: HandleRequestResult.Fail("Access was denied by the resource owner or by the remote server.", properties);
}
var returnedToken = query["oauth_token"];
@ -311,4 +313,4 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
}
}
}
}
}