diff --git a/Mvc.sln b/Mvc.sln
index 807bbd13c7..24156e20b1 100644
--- a/Mvc.sln
+++ b/Mvc.sln
@@ -123,6 +123,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MvcSandbox", "samples\MvcSa
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SimpleWebSite", "test\WebSites\SimpleWebSite\SimpleWebSite.xproj", "{396B40D7-AC70-49A7-B33C-ED42129FEBE3}"
EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SecurityWebSite", "test\WebSites\SecurityWebSite\SecurityWebSite.xproj", "{D28CAC79-7004-4B69-993B-EDEB4653BFA8}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -727,6 +729,18 @@ Global
{396B40D7-AC70-49A7-B33C-ED42129FEBE3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{396B40D7-AC70-49A7-B33C-ED42129FEBE3}.Release|x86.ActiveCfg = Release|Any CPU
{396B40D7-AC70-49A7-B33C-ED42129FEBE3}.Release|x86.Build.0 = Release|Any CPU
+ {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Debug|x86.Build.0 = Debug|Any CPU
+ {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Release|x86.ActiveCfg = Release|Any CPU
+ {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -787,5 +801,6 @@ Global
{9879B5D5-2325-4A81-B4DF-F279FE8FEEB4} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{14ED4476-9F24-4776-8417-EA6927F6C9C9} = {DAAE4C74-D06F-4874-A166-33305D2643CE}
{396B40D7-AC70-49A7-B33C-ED42129FEBE3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
+ {D28CAC79-7004-4B69-993B-EDEB4653BFA8} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
EndGlobalSection
EndGlobal
diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/AutoValidateAntiforgeryTokenAttribute.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/AutoValidateAntiforgeryTokenAttribute.cs
index e40fea31ad..64015f7d8b 100644
--- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/AutoValidateAntiforgeryTokenAttribute.cs
+++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/AutoValidateAntiforgeryTokenAttribute.cs
@@ -21,8 +21,23 @@ namespace Microsoft.AspNetCore.Mvc
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AutoValidateAntiforgeryTokenAttribute : Attribute, IFilterFactory, IOrderedFilter
{
- ///
- public int Order { get; set; }
+ ///
+ /// Gets the order value for determining the order of execution of filters. Filters execute in
+ /// ascending numeric value of the property.
+ ///
+ ///
+ ///
+ /// Filters are executed in an ordering determined by an ascending sort of the property.
+ ///
+ ///
+ /// The default Order for this attribute is 1000 because it must run after any filter which does authentication
+ /// or login in order to allow them to behave as expected (ie Unauthenticated or Redirect instead of 400).
+ ///
+ ///
+ /// Look at for more detailed info.
+ ///
+ ///
+ public int Order { get; set; } = 1000;
///
public bool IsReusable => true;
diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ValidateAntiForgeryTokenAttribute.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ValidateAntiForgeryTokenAttribute.cs
index 938f9c160d..fcf1bd3f8f 100644
--- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ValidateAntiForgeryTokenAttribute.cs
+++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ValidateAntiForgeryTokenAttribute.cs
@@ -20,8 +20,23 @@ namespace Microsoft.AspNetCore.Mvc
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ValidateAntiForgeryTokenAttribute : Attribute, IFilterFactory, IOrderedFilter
{
- ///
- public int Order { get; set; }
+ ///
+ /// Gets the order value for determining the order of execution of filters. Filters execute in
+ /// ascending numeric value of the property.
+ ///
+ ///
+ ///
+ /// Filters are executed in an ordering determined by an ascending sort of the property.
+ ///
+ ///
+ /// The default Order for this attribute is 1000 because it must run after any filter which does authentication
+ /// or login in order to allow them to behave as expected (ie Unauthenticated or Redirect instead of 400).
+ ///
+ ///
+ /// Look at for more detailed info.
+ ///
+ ///
+ public int Order { get; set; } = 1000;
///
public bool IsReusable => true;
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryAuthTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryAuthTests.cs
new file mode 100644
index 0000000000..5f19fbe851
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryAuthTests.cs
@@ -0,0 +1,45 @@
+// 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.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using SecurityWebSite;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.FunctionalTests
+{
+ public class AntiforgeryAuthTests : IClassFixture>
+ {
+ public AntiforgeryAuthTests(MvcTestFixture fixture)
+ {
+ Client = fixture.Client;
+ }
+
+ public HttpClient Client { get; }
+
+ [Fact]
+ public async Task AutomaticAuthenticationBeforeAntiforgery()
+ {
+ // Arrange & Act
+ var response = await Client.PostAsync("http://localhost/Home/AutoAntiforgery", null);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
+ Assert.Equal("/Home/Login", response.Headers.Location.AbsolutePath, StringComparer.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public async Task AuthBeforeAntiforgery()
+ {
+ // Arrange & Act
+ var response = await Client.GetAsync("http://localhost/Home/Antiforgery");
+
+ // Assert
+ // Redirected to login page, Antiforgery didn't fail yet
+ Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
+ Assert.Equal("/Home/Login", response.Headers.Location.AbsolutePath, StringComparer.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/project.json b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/project.json
index 521939699d..c076a84f48 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/project.json
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/project.json
@@ -10,6 +10,7 @@
"warningsAsErrors": true
},
"dependencies": {
+ "SecurityWebSite": "1.0.0",
"ApiExplorerWebSite": "1.0.0",
"ApplicationModelWebSite": "1.0.0",
"BasicWebSite": "1.0.0",
diff --git a/test/WebSites/SecurityWebSite/Controllers/HomeController.cs b/test/WebSites/SecurityWebSite/Controllers/HomeController.cs
new file mode 100644
index 0000000000..2a639ae399
--- /dev/null
+++ b/test/WebSites/SecurityWebSite/Controllers/HomeController.cs
@@ -0,0 +1,41 @@
+// 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 Microsoft.AspNetCore.Mvc;
+
+namespace SecurityWebSite.Controllers
+{
+ public class HomeController : Controller
+ {
+ public IActionResult Index()
+ {
+ return View();
+ }
+
+ [AutoValidateAntiforgeryToken]
+ [Authorize]
+ [HttpPost]
+ public IActionResult AutoAntiforgery()
+ {
+ return Content("Automaticaly doesn't matter");
+ }
+
+ [Authorize]
+ [ValidateAntiForgeryToken]
+ public IActionResult Antiforgery()
+ {
+ return Content("Doesn't matter");
+ }
+
+ public IActionResult Login()
+ {
+ return Content("Login!");
+ }
+
+ public IActionResult Logout()
+ {
+ return Content("Logout!");
+ }
+ }
+}
diff --git a/test/WebSites/SecurityWebSite/Program.cs b/test/WebSites/SecurityWebSite/Program.cs
new file mode 100644
index 0000000000..e9748d7609
--- /dev/null
+++ b/test/WebSites/SecurityWebSite/Program.cs
@@ -0,0 +1,22 @@
+// 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.IO;
+using Microsoft.AspNetCore.Hosting;
+
+namespace SecurityWebSite
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var host = new WebHostBuilder()
+ .UseKestrel()
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .UseStartup()
+ .Build();
+
+ host.Run();
+ }
+ }
+}
diff --git a/test/WebSites/SecurityWebSite/SecurityWebSite.xproj b/test/WebSites/SecurityWebSite/SecurityWebSite.xproj
new file mode 100644
index 0000000000..fc7b205954
--- /dev/null
+++ b/test/WebSites/SecurityWebSite/SecurityWebSite.xproj
@@ -0,0 +1,23 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ d28cac79-7004-4b69-993b-edeb4653bfa8
+ AjaxAntiForgeryValidation
+ .\obj
+ .\bin\
+ v4.5.2
+
+
+ 2.0
+
+
+
+
+
+
+
diff --git a/test/WebSites/SecurityWebSite/Startup.cs b/test/WebSites/SecurityWebSite/Startup.cs
new file mode 100644
index 0000000000..fe7fa7b9f2
--- /dev/null
+++ b/test/WebSites/SecurityWebSite/Startup.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 Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace SecurityWebSite
+{
+ public class Startup
+ {
+ public Startup(IHostingEnvironment env)
+ {
+ var builder = new ConfigurationBuilder()
+ .SetBasePath(env.ContentRootPath)
+ .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
+ .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
+ .AddEnvironmentVariables();
+ Configuration = builder.Build();
+ }
+
+ public IConfigurationRoot Configuration { get; }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ // Add framework services.
+ services.AddMvc();
+ services.AddAntiforgery();
+ services.AddAuthentication();
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
+ {
+ loggerFactory.AddConsole(Configuration.GetSection("Logging"));
+ loggerFactory.AddDebug();
+
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+
+ app.UseStaticFiles();
+ app.UseCookieAuthentication(new CookieAuthenticationOptions
+ {
+ LoginPath = "/Home/Login",
+ LogoutPath = "/Home/Logout",
+ AutomaticAuthenticate = true,
+ AutomaticChallenge = true
+ });
+
+ app.UseMvc(routes =>
+ {
+ routes.MapRoute(
+ name: "default",
+ template: "{controller=Home}/{action=Index}/{id?}");
+ });
+ }
+ }
+}
diff --git a/test/WebSites/SecurityWebSite/Views/Home/Index.cshtml b/test/WebSites/SecurityWebSite/Views/Home/Index.cshtml
new file mode 100644
index 0000000000..cb0dbdf126
--- /dev/null
+++ b/test/WebSites/SecurityWebSite/Views/Home/Index.cshtml
@@ -0,0 +1,21 @@
+@{
+ ViewData["Title"] = "Home Page";
+}
+Hello!
+
+
diff --git a/test/WebSites/SecurityWebSite/Views/Shared/_Layout.cshtml b/test/WebSites/SecurityWebSite/Views/Shared/_Layout.cshtml
new file mode 100644
index 0000000000..dfa4dbe9f1
--- /dev/null
+++ b/test/WebSites/SecurityWebSite/Views/Shared/_Layout.cshtml
@@ -0,0 +1,37 @@
+
+
+
+
+
+ @ViewData["Title"] - SecurityWebSite
+
+
+
+
+ @RenderBody()
+
+
+
+
+
diff --git a/test/WebSites/SecurityWebSite/Views/_ViewImports.cshtml b/test/WebSites/SecurityWebSite/Views/_ViewImports.cshtml
new file mode 100644
index 0000000000..f65eca4041
--- /dev/null
+++ b/test/WebSites/SecurityWebSite/Views/_ViewImports.cshtml
@@ -0,0 +1,2 @@
+@using SecurityWebSite
+ @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
diff --git a/test/WebSites/SecurityWebSite/Views/_ViewStart.cshtml b/test/WebSites/SecurityWebSite/Views/_ViewStart.cshtml
new file mode 100644
index 0000000000..a5f10045db
--- /dev/null
+++ b/test/WebSites/SecurityWebSite/Views/_ViewStart.cshtml
@@ -0,0 +1,3 @@
+@{
+ Layout = "_Layout";
+}
diff --git a/test/WebSites/SecurityWebSite/appsettings.json b/test/WebSites/SecurityWebSite/appsettings.json
new file mode 100644
index 0000000000..fa8ce71a97
--- /dev/null
+++ b/test/WebSites/SecurityWebSite/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "IncludeScopes": false,
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ }
+}
diff --git a/test/WebSites/SecurityWebSite/project.json b/test/WebSites/SecurityWebSite/project.json
new file mode 100644
index 0000000000..a1bfa558d5
--- /dev/null
+++ b/test/WebSites/SecurityWebSite/project.json
@@ -0,0 +1,48 @@
+{
+ "buildOptions": {
+ "emitEntryPoint": true,
+ "preserveCompilationContext": true
+ },
+ "dependencies": {
+ "Microsoft.Extensions.Logging.Debug": "1.1.0-*",
+ "Microsoft.Extensions.Logging.Console": "1.1.0-*",
+ "Microsoft.Extensions.Configuration.Json": "1.1.0-*",
+ "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0-*",
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0-*",
+ "Microsoft.Extensions.Configuration": "1.1.0-*",
+ "Microsoft.AspNetCore.StaticFiles": "1.1.0-*",
+ "Microsoft.AspNetCore.Server.Kestrel": "1.1.0-*",
+ "Microsoft.AspNetCore.Mvc": "1.1.0-*",
+ "Microsoft.AspNetCore.Mvc.ViewFeatures": "1.1.0-*",
+ "Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.1.0-*",
+ "Microsoft.AspNetCore.Identity": "1.1.0-*",
+ "Microsoft.AspNetCore.Hosting": "1.1.0-*",
+ "Microsoft.AspNetCore.Diagnostics": "1.1.0-*"
+ },
+ "frameworks": {
+ "netcoreapp1.1": {
+ "dependencies": {
+ "Microsoft.NETCore.App": {
+ "version": "1.1.0-*",
+ "type": "platform"
+ }
+ },
+ "imports": "portable-net451+win8"
+ },
+ "net451": {}
+ },
+ "publishOptions": {
+ "include": [
+ "wwwroot",
+ "appsettings.json",
+ "web.config"
+ ]
+ },
+ "scripts": {
+ "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
+ },
+ "tools": {
+ "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final",
+ "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
+ }
+}
diff --git a/test/WebSites/SecurityWebSite/web.config b/test/WebSites/SecurityWebSite/web.config
new file mode 100644
index 0000000000..dc0514fca5
--- /dev/null
+++ b/test/WebSites/SecurityWebSite/web.config
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+