diff --git a/src/Antiforgery/.gitignore b/src/Antiforgery/.gitignore
new file mode 100644
index 0000000000..6da3c6a3e9
--- /dev/null
+++ b/src/Antiforgery/.gitignore
@@ -0,0 +1,40 @@
+[Oo]bj/
+[Bb]in/
+TestResults/
+.nuget/
+*.sln.ide/
+_ReSharper.*/
+packages/
+artifacts/
+PublishProfiles/
+.vs/
+bower_components/
+node_modules/
+**/wwwroot/lib/
+debugSettings.json
+project.lock.json
+*.user
+*.suo
+*.cache
+*.docstates
+_ReSharper.*
+nuget.exe
+*net45.csproj
+*net451.csproj
+*k10.csproj
+*.psess
+*.vsp
+*.pidb
+*.userprefs
+*DS_Store
+*.ncrunchsolution
+*.*sdf
+*.ipch
+.settings
+*.sln.ide
+node_modules
+**/[Cc]ompiler/[Rr]esources/**/*.js
+*launchSettings.json
+.build/
+.testPublish/
+global.json
diff --git a/src/Antiforgery/Antiforgery.sln b/src/Antiforgery/Antiforgery.sln
new file mode 100644
index 0000000000..a4f13419cd
--- /dev/null
+++ b/src/Antiforgery/Antiforgery.sln
@@ -0,0 +1,35 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26208.0
+MinimumVisualStudioVersion = 15.0.26730.03
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{71D070C4-B325-48F7-9F25-DD4E91C2BBCA}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{6EDD8B57-4DE8-4246-A6A3-47ECD92740B4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Antiforgery", "src\Microsoft.AspNetCore.Antiforgery\Microsoft.AspNetCore.Antiforgery.csproj", "{46FB03FB-7A44-4106-BDDE-D6F5417544AB}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Antiforgery.Test", "test\Microsoft.AspNetCore.Antiforgery.Test\Microsoft.AspNetCore.Antiforgery.Test.csproj", "{415E83F8-6002-47E4-AA8E-CD5169C06F28}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {46FB03FB-7A44-4106-BDDE-D6F5417544AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {46FB03FB-7A44-4106-BDDE-D6F5417544AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {46FB03FB-7A44-4106-BDDE-D6F5417544AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {46FB03FB-7A44-4106-BDDE-D6F5417544AB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {415E83F8-6002-47E4-AA8E-CD5169C06F28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {415E83F8-6002-47E4-AA8E-CD5169C06F28}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {415E83F8-6002-47E4-AA8E-CD5169C06F28}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {415E83F8-6002-47E4-AA8E-CD5169C06F28}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {46FB03FB-7A44-4106-BDDE-D6F5417544AB} = {71D070C4-B325-48F7-9F25-DD4E91C2BBCA}
+ {415E83F8-6002-47E4-AA8E-CD5169C06F28} = {6EDD8B57-4DE8-4246-A6A3-47ECD92740B4}
+ EndGlobalSection
+EndGlobal
diff --git a/src/Antiforgery/Directory.Build.props b/src/Antiforgery/Directory.Build.props
new file mode 100644
index 0000000000..1008441f18
--- /dev/null
+++ b/src/Antiforgery/Directory.Build.props
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+ Microsoft ASP.NET Core
+ https://github.com/aspnet/Antiforgery
+ git
+ $(MSBuildThisFileDirectory)
+ $(MSBuildThisFileDirectory)build\Key.snk
+ true
+ true
+ true
+
+
+
+
+
+
diff --git a/src/Antiforgery/Directory.Build.targets b/src/Antiforgery/Directory.Build.targets
new file mode 100644
index 0000000000..53b3f6e1da
--- /dev/null
+++ b/src/Antiforgery/Directory.Build.targets
@@ -0,0 +1,7 @@
+
+
+ $(MicrosoftNETCoreApp20PackageVersion)
+ $(MicrosoftNETCoreApp21PackageVersion)
+ $(NETStandardLibrary20PackageVersion)
+
+
diff --git a/src/Antiforgery/NuGetPackageVerifier.json b/src/Antiforgery/NuGetPackageVerifier.json
new file mode 100644
index 0000000000..b153ab1515
--- /dev/null
+++ b/src/Antiforgery/NuGetPackageVerifier.json
@@ -0,0 +1,7 @@
+{
+ "Default": {
+ "rules": [
+ "DefaultCompositeRule"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/README.md b/src/Antiforgery/README.md
new file mode 100644
index 0000000000..692348309d
--- /dev/null
+++ b/src/Antiforgery/README.md
@@ -0,0 +1,14 @@
+Antiforgery
+===========
+
+AppVeyor: [](https://ci.appveyor.com/project/aspnetci/Antiforgery/branch/dev)
+
+Travis: [](https://travis-ci.org/aspnet/Antiforgery)
+
+Antiforgery system for generating secure tokens to prevent Cross-Site Request Forgery attacks.
+
+This project is part of ASP.NET Core. You can find documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo.
+
+Samples can be found in [Entropy](https://github.com/aspnet/Entropy).
+The [MVC](https://github.com/aspnet/Entropy/tree/dev/samples/Antiforgery.MvcWithAuthAndAjax) sample shows how to use Antiforgery in MVC when making AJAX requests.
+The [Angular](https://github.com/aspnet/Entropy/tree/dev/samples/Antiforgery.Angular1) sample shows how to use Antiforgery with Angular 1.
diff --git a/src/Antiforgery/build/Key.snk b/src/Antiforgery/build/Key.snk
new file mode 100644
index 0000000000..e10e4889c1
Binary files /dev/null and b/src/Antiforgery/build/Key.snk differ
diff --git a/src/Antiforgery/build/dependencies.props b/src/Antiforgery/build/dependencies.props
new file mode 100644
index 0000000000..733502bfdc
--- /dev/null
+++ b/src/Antiforgery/build/dependencies.props
@@ -0,0 +1,35 @@
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+
+
+ 2.1.3-rtm-15802
+ 2.0.0
+ 2.1.2
+ 15.6.1
+ 4.7.49
+ 2.0.3
+ 2.3.1
+ 2.4.0-beta.1.build3945
+
+
+
+
+
+
+
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.0
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+
+
\ No newline at end of file
diff --git a/src/Antiforgery/build/repo.props b/src/Antiforgery/build/repo.props
new file mode 100644
index 0000000000..6c9c88ab01
--- /dev/null
+++ b/src/Antiforgery/build/repo.props
@@ -0,0 +1,14 @@
+
+
+
+
+ Internal.AspNetCore.Universe.Lineup
+ 2.1.0-rc1-*
+ https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json
+
+
+
+
+
+
+
diff --git a/src/Antiforgery/build/sources.props b/src/Antiforgery/build/sources.props
new file mode 100644
index 0000000000..9215df9751
--- /dev/null
+++ b/src/Antiforgery/build/sources.props
@@ -0,0 +1,17 @@
+
+
+
+
+ $(DotNetRestoreSources)
+
+ $(RestoreSources);
+ https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
+ https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json;
+ https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json;
+
+
+ $(RestoreSources);
+ https://api.nuget.org/v3/index.json;
+
+
+
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryOptions.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryOptions.cs
new file mode 100644
index 0000000000..e58f3f73c7
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryOptions.cs
@@ -0,0 +1,150 @@
+// 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.Http;
+
+namespace Microsoft.AspNetCore.Antiforgery
+{
+ ///
+ /// Provides programmatic configuration for the antiforgery token system.
+ ///
+ public class AntiforgeryOptions
+ {
+ private const string AntiforgeryTokenFieldName = "__RequestVerificationToken";
+ private const string AntiforgeryTokenHeaderName = "RequestVerificationToken";
+
+ private string _formFieldName = AntiforgeryTokenFieldName;
+
+ private CookieBuilder _cookieBuilder = new CookieBuilder
+ {
+ SameSite = SameSiteMode.Strict,
+ HttpOnly = true,
+
+ // Check the comment on CookieBuilder for more details
+ IsEssential = true,
+
+ // Some browsers do not allow non-secure endpoints to set cookies with a 'secure' flag or overwrite cookies
+ // whose 'secure' flag is set (http://httpwg.org/http-extensions/draft-ietf-httpbis-cookie-alone.html).
+ // Since mixing secure and non-secure endpoints is a common scenario in applications, we are relaxing the
+ // restriction on secure policy on some cookies by setting to 'None'. Cookies related to authentication or
+ // authorization use a stronger policy than 'None'.
+ SecurePolicy = CookieSecurePolicy.None,
+ };
+
+ ///
+ /// The default cookie prefix, which is ".AspNetCore.Antiforgery.".
+ ///
+ public static readonly string DefaultCookiePrefix = ".AspNetCore.Antiforgery.";
+
+ ///
+ /// Determines the settings used to create the antiforgery cookies.
+ ///
+ ///
+ ///
+ /// If an explicit is not provided, the system will automatically generate a
+ /// unique name that begins with .
+ ///
+ ///
+ /// defaults to .
+ /// defaults to true.
+ /// defaults to true. The cookie used by the antiforgery system
+ /// is part of a security system that is necessary when using cookie-based authentication. It should be
+ /// considered required for the application to function.
+ /// defaults to .
+ ///
+ ///
+ public CookieBuilder Cookie
+ {
+ get => _cookieBuilder;
+ set => _cookieBuilder = value ?? throw new ArgumentNullException(nameof(value));
+ }
+
+ ///
+ /// Specifies the name of the antiforgery token field that is used by the antiforgery system.
+ ///
+ public string FormFieldName
+ {
+ get => _formFieldName;
+ set => _formFieldName = value ?? throw new ArgumentNullException(nameof(value));
+ }
+
+ ///
+ /// Specifies the name of the header value that is used by the antiforgery system. If null then
+ /// antiforgery validation will only consider form data.
+ ///
+ public string HeaderName { get; set; } = AntiforgeryTokenHeaderName;
+
+ ///
+ /// Specifies whether to suppress the generation of X-Frame-Options header
+ /// which is used to prevent ClickJacking. By default, the X-Frame-Options
+ /// header is generated with the value SAMEORIGIN. If this setting is 'true',
+ /// the X-Frame-Options header will not be generated for the response.
+ ///
+ public bool SuppressXFrameOptionsHeader { get; set; }
+
+ #region Obsolete API
+ ///
+ ///
+ /// This property is obsolete and will be removed in a future version. The recommended alternative is on .
+ ///
+ ///
+ /// Specifies the name of the cookie that is used by the antiforgery system.
+ ///
+ ///
+ ///
+ /// If an explicit name is not provided, the system will automatically generate a
+ /// unique name that begins with .
+ ///
+ [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; }
+
+ ///
+ ///
+ /// This property is obsolete and will be removed in a future version. The recommended alternative is on .
+ ///
+ ///
+ /// The path set on the cookie. If set to null, the "path" attribute on the cookie is set to the current
+ /// request's value. If the value of is
+ /// null or empty, then the "path" attribute is set to the value of .
+ ///
+ ///
+ [Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is " + nameof(Cookie) + "." + nameof(CookieBuilder.Path) + ".")]
+ public PathString? CookiePath { get => Cookie.Path; set => Cookie.Path = value; }
+
+ ///
+ ///
+ /// This property is obsolete and will be removed in a future version. The recommended alternative is on .
+ ///
+ ///
+ /// The domain set on the cookie. By default its null which results in the "domain" attribute not being set.
+ ///
+ ///
+ [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; }
+
+
+ ///
+ ///
+ /// This property is obsolete and will be removed in a future version.
+ /// The recommended alternative is to set on .
+ ///
+ ///
+ /// true is equivalent to .
+ /// false is equivalent to .
+ ///
+ ///
+ /// Specifies whether SSL is required for the antiforgery system
+ /// to operate. If this setting is 'true' and a non-SSL request
+ /// comes into the system, all antiforgery APIs will fail.
+ ///
+ ///
+ [Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is to set " + nameof(Cookie) + "." + nameof(CookieBuilder.SecurePolicy) + ".")]
+ public bool RequireSsl
+ {
+ get => Cookie.SecurePolicy == CookieSecurePolicy.Always;
+ set => Cookie.SecurePolicy = value ? CookieSecurePolicy.Always : CookieSecurePolicy.None;
+ }
+ #endregion
+ }
+}
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryServiceCollectionExtensions.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryServiceCollectionExtensions.cs
new file mode 100644
index 0000000000..a82851b3e2
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryServiceCollectionExtensions.cs
@@ -0,0 +1,76 @@
+// 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.Antiforgery;
+using Microsoft.AspNetCore.Antiforgery.Internal;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.ObjectPool;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ /// Extension methods for setting up antiforgery services in an .
+ ///
+ public static class AntiforgeryServiceCollectionExtensions
+ {
+ ///
+ /// Adds antiforgery services to the specified .
+ ///
+ /// The to add services to.
+ /// The so that additional calls can be chained.
+ public static IServiceCollection AddAntiforgery(this IServiceCollection services)
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ services.AddDataProtection();
+
+ // Don't overwrite any options setups that a user may have added.
+ services.TryAddEnumerable(
+ ServiceDescriptor.Transient, AntiforgeryOptionsSetup>());
+
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+
+ services.TryAddSingleton>(serviceProvider =>
+ {
+ var provider = serviceProvider.GetRequiredService();
+ var policy = new AntiforgerySerializationContextPooledObjectPolicy();
+ return provider.Create(policy);
+ });
+
+ return services;
+ }
+
+ ///
+ /// Adds antiforgery services to the specified .
+ ///
+ /// The to add services to.
+ /// An to configure the provided .
+ /// The so that additional calls can be chained.
+ public static IServiceCollection AddAntiforgery(this IServiceCollection services, Action setupAction)
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ if (setupAction == null)
+ {
+ throw new ArgumentNullException(nameof(setupAction));
+ }
+
+ services.AddAntiforgery();
+ services.Configure(setupAction);
+ return services;
+ }
+ }
+}
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryTokenSet.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryTokenSet.cs
new file mode 100644
index 0000000000..033e5e0731
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryTokenSet.cs
@@ -0,0 +1,57 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Antiforgery
+{
+ ///
+ /// The antiforgery token pair (cookie and request token) for a request.
+ ///
+ public class AntiforgeryTokenSet
+ {
+ ///
+ /// Creates the antiforgery token pair (cookie and request token) for a request.
+ ///
+ /// The token that is supplied in the request.
+ /// The token that is supplied in the request cookie.
+ /// The name of the form field used for the request token.
+ /// The name of the header used for the request token.
+ public AntiforgeryTokenSet(
+ string requestToken,
+ string cookieToken,
+ string formFieldName,
+ string headerName)
+ {
+ if (formFieldName == null)
+ {
+ throw new ArgumentNullException(nameof(formFieldName));
+ }
+
+ RequestToken = requestToken;
+ CookieToken = cookieToken;
+ FormFieldName = formFieldName;
+ HeaderName = headerName;
+ }
+
+ ///
+ /// Gets the request token.
+ ///
+ public string RequestToken { get; }
+
+ ///
+ /// Gets the name of the form field used for the request token.
+ ///
+ public string FormFieldName { get; }
+
+ ///
+ /// Gets the name of the header used for the request token.
+ ///
+ public string HeaderName { get; }
+
+ ///
+ /// Gets the cookie token.
+ ///
+ public string CookieToken { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryValidationException.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryValidationException.cs
new file mode 100644
index 0000000000..f1ade05d34
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryValidationException.cs
@@ -0,0 +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;
+
+namespace Microsoft.AspNetCore.Antiforgery
+{
+ ///
+ /// The that is thrown when the antiforgery token validation fails.
+ ///
+ public class AntiforgeryValidationException : Exception
+ {
+ ///
+ /// Creates a new instance of with the specified
+ /// exception message.
+ ///
+ /// The message that describes the error.
+ public AntiforgeryValidationException(string message)
+ : base(message)
+ {
+ }
+
+ ///
+ /// Creates a new instance of with the specified
+ /// exception message and inner exception.
+ ///
+ /// The message that describes the error.
+ /// The inner .
+ public AntiforgeryValidationException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+ }
+}
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/IAntiforgery.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/IAntiforgery.cs
new file mode 100644
index 0000000000..89630a46d0
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/IAntiforgery.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.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Antiforgery
+{
+ ///
+ /// Provides access to the antiforgery system, which provides protection against
+ /// Cross-site Request Forgery (XSRF, also called CSRF) attacks.
+ ///
+ public interface IAntiforgery
+ {
+ ///
+ /// Generates an for this request and stores the cookie token
+ /// in the response. This operation also sets the "Cache-control" and "Pragma" headers to "no-cache" and
+ /// the "X-Frame-Options" header to "SAMEORIGIN".
+ ///
+ /// The associated with the current request.
+ /// An with tokens for the response.
+ ///
+ /// This method has a side effect:
+ /// A response cookie is set if there is no valid cookie associated with the request.
+ ///
+ AntiforgeryTokenSet GetAndStoreTokens(HttpContext httpContext);
+
+ ///
+ /// Generates an for this request.
+ ///
+ /// The associated with the current request.
+ ///
+ /// Unlike , this method has no side effect. The caller
+ /// is responsible for setting the response cookie and injecting the returned
+ /// form token as appropriate.
+ ///
+ AntiforgeryTokenSet GetTokens(HttpContext httpContext);
+
+ ///
+ /// Asynchronously returns a value indicating whether the request passes antiforgery validation. If the
+ /// request uses a safe HTTP method (GET, HEAD, OPTIONS, TRACE), the antiforgery token is not validated.
+ ///
+ /// The associated with the current request.
+ ///
+ /// A that, when completed, returns true if the request uses a safe HTTP
+ /// method or contains a valid antiforgery token, otherwise returns false.
+ ///
+ Task IsRequestValidAsync(HttpContext httpContext);
+
+ ///
+ /// Validates an antiforgery token that was supplied as part of the request.
+ ///
+ /// The associated with the current request.
+ ///
+ /// Thrown when the request does not include a valid antiforgery token.
+ ///
+ Task ValidateRequestAsync(HttpContext httpContext);
+
+ ///
+ /// Generates and stores an antiforgery cookie token if one is not available or not valid.
+ ///
+ /// The associated with the current request.
+ void SetCookieTokenAndHeader(HttpContext httpContext);
+ }
+}
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/IAntiforgeryAdditionalDataProvider.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/IAntiforgeryAdditionalDataProvider.cs
new file mode 100644
index 0000000000..d66b6245db
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/IAntiforgeryAdditionalDataProvider.cs
@@ -0,0 +1,39 @@
+// 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.Antiforgery
+{
+ ///
+ /// Allows providing or validating additional custom data for antiforgery tokens.
+ /// For example, the developer could use this to supply a nonce when the token is
+ /// generated, then he could validate the nonce when the token is validated.
+ ///
+ ///
+ /// The antiforgery system already embeds the client's username within the
+ /// generated tokens. This interface provides and consumes supplemental
+ /// data. If an incoming antiforgery token contains supplemental data but no
+ /// additional data provider is configured, the supplemental data will not be
+ /// validated.
+ ///
+ public interface IAntiforgeryAdditionalDataProvider
+ {
+ ///
+ /// Provides additional data to be stored for the antiforgery tokens generated
+ /// during this request.
+ ///
+ /// Information about the current request.
+ /// Supplemental data to embed within the antiforgery token.
+ string GetAdditionalData(HttpContext context);
+
+ ///
+ /// Validates additional data that was embedded inside an incoming antiforgery
+ /// token.
+ ///
+ /// Information about the current request.
+ /// Supplemental data that was embedded within the token.
+ /// True if the data is valid; false if the data is invalid.
+ bool ValidateAdditionalData(HttpContext context, string additionalData);
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgeryFeature.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgeryFeature.cs
new file mode 100644
index 0000000000..ad2a38501d
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgeryFeature.cs
@@ -0,0 +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.
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ ///
+ /// Used to hold per-request state.
+ ///
+ public class AntiforgeryFeature : IAntiforgeryFeature
+ {
+ public bool HaveDeserializedCookieToken { get; set; }
+
+ public AntiforgeryToken CookieToken { get; set; }
+
+ public bool HaveDeserializedRequestToken { get; set; }
+
+ public AntiforgeryToken RequestToken { get; set; }
+
+ public bool HaveGeneratedNewCookieToken { get; set; }
+
+ // After HaveGeneratedNewCookieToken is true, remains null if CookieToken is valid.
+ public AntiforgeryToken NewCookieToken { get; set; }
+
+ // After HaveGeneratedNewCookieToken is true, remains null if CookieToken is valid.
+ public string NewCookieTokenString { get; set; }
+
+ public AntiforgeryToken NewRequestToken { get; set; }
+
+ public string NewRequestTokenString { get; set; }
+
+ // Always false if NewCookieToken is null. Never store null cookie token or re-store cookie token from request.
+ public bool HaveStoredNewCookieToken { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgeryLoggerExtensions.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgeryLoggerExtensions.cs
new file mode 100644
index 0000000000..232279e4be
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgeryLoggerExtensions.cs
@@ -0,0 +1,109 @@
+// 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.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ internal static class AntiforgeryLoggerExtensions
+ {
+ private static readonly Action _failedToDeserialzeTokens;
+ private static readonly Action _validationFailed;
+ private static readonly Action _validated;
+ private static readonly Action _missingCookieToken;
+ private static readonly Action _missingRequestToken;
+ private static readonly Action _newCookieToken;
+ private static readonly Action _reusedCookieToken;
+ private static readonly Action _tokenDeserializeException;
+ private static readonly Action _responseCacheHeadersOverridenToNoCache;
+
+ static AntiforgeryLoggerExtensions()
+ {
+ _validationFailed = LoggerMessage.Define(
+ LogLevel.Warning,
+ 1,
+ "Antiforgery validation failed with message '{Message}'.");
+ _validated = LoggerMessage.Define(
+ LogLevel.Debug,
+ 2,
+ "Antiforgery successfully validated a request.");
+ _missingCookieToken = LoggerMessage.Define(
+ LogLevel.Warning,
+ 3,
+ "The required antiforgery cookie '{CookieName}' is not present.");
+ _missingRequestToken = LoggerMessage.Define(
+ LogLevel.Warning,
+ 4,
+ "The required antiforgery request token was not provided in either form field '{FormFieldName}' "
+ + "or header '{HeaderName}'.");
+ _newCookieToken = LoggerMessage.Define(
+ LogLevel.Debug,
+ 5,
+ "A new antiforgery cookie token was created.");
+ _reusedCookieToken = LoggerMessage.Define(
+ LogLevel.Debug,
+ 6,
+ "An antiforgery cookie token was reused.");
+ _tokenDeserializeException = LoggerMessage.Define(
+ LogLevel.Error,
+ 7,
+ "An exception was thrown while deserializing the token.");
+ _responseCacheHeadersOverridenToNoCache = LoggerMessage.Define(
+ LogLevel.Warning,
+ 8,
+ "The 'Cache-Control' and 'Pragma' headers have been overridden and set to 'no-cache, no-store' and " +
+ "'no-cache' respectively to prevent caching of this response. Any response that uses antiforgery " +
+ "should not be cached.");
+ _failedToDeserialzeTokens = LoggerMessage.Define(
+ LogLevel.Debug,
+ 9,
+ "Failed to deserialize antiforgery tokens.");
+ }
+
+ public static void ValidationFailed(this ILogger logger, string message)
+ {
+ _validationFailed(logger, message, null);
+ }
+
+ public static void ValidatedAntiforgeryToken(this ILogger logger)
+ {
+ _validated(logger, null);
+ }
+
+ public static void MissingCookieToken(this ILogger logger, string cookieName)
+ {
+ _missingCookieToken(logger, cookieName, null);
+ }
+
+ public static void MissingRequestToken(this ILogger logger, string formFieldName, string headerName)
+ {
+ _missingRequestToken(logger, formFieldName, headerName, null);
+ }
+
+ public static void NewCookieToken(this ILogger logger)
+ {
+ _newCookieToken(logger, null);
+ }
+
+ public static void ReusedCookieToken(this ILogger logger)
+ {
+ _reusedCookieToken(logger, null);
+ }
+
+ public static void TokenDeserializeException(this ILogger logger, Exception exception)
+ {
+ _tokenDeserializeException(logger, exception);
+ }
+
+ public static void ResponseCacheHeadersOverridenToNoCache(this ILogger logger)
+ {
+ _responseCacheHeadersOverridenToNoCache(logger, null);
+ }
+
+ public static void FailedToDeserialzeTokens(this ILogger logger, Exception exception)
+ {
+ _failedToDeserialzeTokens(logger, exception);
+ }
+ }
+}
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgeryOptionsSetup.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgeryOptionsSetup.cs
new file mode 100644
index 0000000000..a6bc826351
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgeryOptionsSetup.cs
@@ -0,0 +1,38 @@
+// 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.Linq;
+using System.Text;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class AntiforgeryOptionsSetup : ConfigureOptions
+ {
+ public AntiforgeryOptionsSetup(IOptions dataProtectionOptionsAccessor)
+ : base((options) => ConfigureOptions(options, dataProtectionOptionsAccessor.Value))
+ {
+ }
+
+ public static void ConfigureOptions(AntiforgeryOptions options, DataProtectionOptions dataProtectionOptions)
+ {
+ if (options.Cookie.Name == null)
+ {
+ var applicationId = dataProtectionOptions.ApplicationDiscriminator ?? string.Empty;
+ options.Cookie.Name = AntiforgeryOptions.DefaultCookiePrefix + ComputeCookieName(applicationId);
+ }
+ }
+
+ private static string ComputeCookieName(string applicationId)
+ {
+ using (var sha256 = CryptographyAlgorithms.CreateSHA256())
+ {
+ var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(applicationId));
+ var subHash = hash.Take(8).ToArray();
+ return WebEncoders.Base64UrlEncode(subHash);
+ }
+ }
+ }
+}
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgerySerializationContext.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgerySerializationContext.cs
new file mode 100644
index 0000000000..6d697fa0da
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgerySerializationContext.cs
@@ -0,0 +1,141 @@
+// 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 System.Security.Cryptography;
+using System.Text;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class AntiforgerySerializationContext
+ {
+ // Avoid allocating 256 bytes (the default) and using 18 (the AntiforgeryToken minimum). 64 bytes is enough for
+ // a short username or claim UID and some additional data. MemoryStream bumps capacity to 256 if exceeded.
+ private const int InitialStreamSize = 64;
+
+ // Don't let the MemoryStream grow beyond 1 MB.
+ private const int MaximumStreamSize = 0x100000;
+
+ // Start _chars off with length 256 (18 bytes is protected into 116 bytes then encoded into 156 characters).
+ // Double length from there if necessary.
+ private const int InitialCharsLength = 256;
+
+ // Don't let _chars grow beyond 512k characters.
+ private const int MaximumCharsLength = 0x80000;
+
+ private char[] _chars;
+ private MemoryStream _stream;
+ private BinaryReader _reader;
+ private BinaryWriter _writer;
+ private SHA256 _sha256;
+
+ public MemoryStream Stream
+ {
+ get
+ {
+ if (_stream == null)
+ {
+ _stream = new MemoryStream(InitialStreamSize);
+ }
+
+ return _stream;
+ }
+ private set
+ {
+ _stream = value;
+ }
+ }
+
+ public BinaryReader Reader
+ {
+ get
+ {
+ if (_reader == null)
+ {
+ // Leave open to clean up correctly even if only one of the reader or writer has been created.
+ _reader = new BinaryReader(Stream, Encoding.UTF8, leaveOpen: true);
+ }
+
+ return _reader;
+ }
+ private set
+ {
+ _reader = value;
+ }
+ }
+
+ public BinaryWriter Writer
+ {
+ get
+ {
+ if (_writer == null)
+ {
+ // Leave open to clean up correctly even if only one of the reader or writer has been created.
+ _writer = new BinaryWriter(Stream, Encoding.UTF8, leaveOpen: true);
+ }
+
+ return _writer;
+ }
+ private set
+ {
+ _writer = value;
+ }
+ }
+
+ public SHA256 Sha256
+ {
+ get
+ {
+ if (_sha256 == null)
+ {
+ _sha256 = CryptographyAlgorithms.CreateSHA256();
+ }
+
+ return _sha256;
+ }
+ private set
+ {
+ _sha256 = value;
+ }
+ }
+
+ public char[] GetChars(int count)
+ {
+ if (_chars == null || _chars.Length < count)
+ {
+ var newLength = _chars == null ? InitialCharsLength : checked(_chars.Length * 2);
+ while (newLength < count)
+ {
+ newLength = checked(newLength * 2);
+ }
+
+ _chars = new char[newLength];
+ }
+
+ return _chars;
+ }
+
+ public void Reset()
+ {
+ if (_chars != null && _chars.Length > MaximumCharsLength)
+ {
+ _chars = null;
+ }
+
+ if (_stream != null)
+ {
+ if (Stream.Capacity > MaximumStreamSize)
+ {
+ Stream = null;
+ Reader = null;
+ Writer = null;
+ }
+ else
+ {
+ Stream.Position = 0L;
+ Stream.SetLength(0L);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgerySerializationContextPooledObjectPolicy.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgerySerializationContextPooledObjectPolicy.cs
new file mode 100644
index 0000000000..0a6163141b
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgerySerializationContextPooledObjectPolicy.cs
@@ -0,0 +1,23 @@
+// 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.Extensions.ObjectPool;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class AntiforgerySerializationContextPooledObjectPolicy
+ : IPooledObjectPolicy
+ {
+ public AntiforgerySerializationContext Create()
+ {
+ return new AntiforgerySerializationContext();
+ }
+
+ public bool Return(AntiforgerySerializationContext obj)
+ {
+ obj.Reset();
+
+ return true;
+ }
+ }
+}
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgeryToken.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgeryToken.cs
new file mode 100644
index 0000000000..78294f730c
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgeryToken.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.
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public sealed class AntiforgeryToken
+ {
+ internal const int SecurityTokenBitLength = 128;
+ internal const int ClaimUidBitLength = 256;
+
+ private string _additionalData = string.Empty;
+ private string _username = string.Empty;
+ private BinaryBlob _securityToken;
+
+ public string AdditionalData
+ {
+ get { return _additionalData; }
+ set
+ {
+ _additionalData = value ?? string.Empty;
+ }
+ }
+
+ public BinaryBlob ClaimUid { get; set; }
+
+ public bool IsCookieToken { get; set; }
+
+ public BinaryBlob SecurityToken
+ {
+ get
+ {
+ if (_securityToken == null)
+ {
+ _securityToken = new BinaryBlob(SecurityTokenBitLength);
+ }
+ return _securityToken;
+ }
+ set
+ {
+ _securityToken = value;
+ }
+ }
+
+ public string Username
+ {
+ get { return _username; }
+ set
+ {
+ _username = value ?? string.Empty;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/BinaryBlob.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/BinaryBlob.cs
new file mode 100644
index 0000000000..88c34571c0
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/BinaryBlob.cs
@@ -0,0 +1,117 @@
+// 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.Diagnostics;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ // Represents a binary blob (token) that contains random data.
+ // Useful for binary data inside a serialized stream.
+ [DebuggerDisplay("{DebuggerString}")]
+ public sealed class BinaryBlob : IEquatable
+ {
+ private static readonly RandomNumberGenerator _randomNumberGenerator = RandomNumberGenerator.Create();
+ private readonly byte[] _data;
+
+ // Generates a new token using a specified bit length.
+ public BinaryBlob(int bitLength)
+ : this(bitLength, GenerateNewToken(bitLength))
+ {
+ }
+
+ // Generates a token using an existing binary value.
+ public BinaryBlob(int bitLength, byte[] data)
+ {
+ if (bitLength < 32 || bitLength % 8 != 0)
+ {
+ throw new ArgumentOutOfRangeException("bitLength");
+ }
+ if (data == null || data.Length != bitLength / 8)
+ {
+ throw new ArgumentOutOfRangeException("data");
+ }
+
+ _data = data;
+ }
+
+ public int BitLength
+ {
+ get
+ {
+ return checked(_data.Length * 8);
+ }
+ }
+
+ private string DebuggerString
+ {
+ get
+ {
+ var sb = new StringBuilder("0x", 2 + (_data.Length * 2));
+ for (var i = 0; i < _data.Length; i++)
+ {
+ sb.AppendFormat(CultureInfo.InvariantCulture, "{0:x2}", _data[i]);
+ }
+ return sb.ToString();
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as BinaryBlob);
+ }
+
+ public bool Equals(BinaryBlob other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ Debug.Assert(_data.Length == other._data.Length);
+ return AreByteArraysEqual(_data, other._data);
+ }
+
+ public byte[] GetData()
+ {
+ return _data;
+ }
+
+ public override int GetHashCode()
+ {
+ // Since data should contain uniformly-distributed entropy, the
+ // first 32 bits can serve as the hash code.
+ Debug.Assert(_data != null && _data.Length >= (32 / 8));
+ return BitConverter.ToInt32(_data, 0);
+ }
+
+ private static byte[] GenerateNewToken(int bitLength)
+ {
+ var data = new byte[bitLength / 8];
+ _randomNumberGenerator.GetBytes(data);
+ return data;
+ }
+
+ // Need to mark it with NoInlining and NoOptimization attributes to ensure that the
+ // operation runs in constant time.
+ [MethodImplAttribute(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+ private static bool AreByteArraysEqual(byte[] a, byte[] b)
+ {
+ if (a == null || b == null || a.Length != b.Length)
+ {
+ return false;
+ }
+
+ var areEqual = true;
+ for (var i = 0; i < a.Length; i++)
+ {
+ areEqual &= (a[i] == b[i]);
+ }
+ return areEqual;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/CryptographyAlgorithms.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/CryptographyAlgorithms.cs
new file mode 100644
index 0000000000..644b4e6234
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/CryptographyAlgorithms.cs
@@ -0,0 +1,25 @@
+// 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.Security.Cryptography;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public static class CryptographyAlgorithms
+ {
+ public static SHA256 CreateSHA256()
+ {
+ try
+ {
+ return SHA256.Create();
+ }
+ // SHA256.Create is documented to throw this exception on FIPS compliant machines.
+ // See: https://msdn.microsoft.com/en-us/library/z08hz7ad%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
+ catch (System.Reflection.TargetInvocationException)
+ {
+ // Fallback to a FIPS compliant SHA256 algorithm.
+ return new SHA256CryptoServiceProvider();
+ }
+ }
+ }
+}
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgery.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgery.cs
new file mode 100644
index 0000000000..cef46be893
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgery.cs
@@ -0,0 +1,497 @@
+// 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.Diagnostics;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ ///
+ /// Provides access to the antiforgery system, which provides protection against
+ /// Cross-site Request Forgery (XSRF, also called CSRF) attacks.
+ ///
+ public class DefaultAntiforgery : IAntiforgery
+ {
+ private readonly AntiforgeryOptions _options;
+ private readonly IAntiforgeryTokenGenerator _tokenGenerator;
+ private readonly IAntiforgeryTokenSerializer _tokenSerializer;
+ private readonly IAntiforgeryTokenStore _tokenStore;
+ private readonly ILogger _logger;
+
+ public DefaultAntiforgery(
+ IOptions antiforgeryOptionsAccessor,
+ IAntiforgeryTokenGenerator tokenGenerator,
+ IAntiforgeryTokenSerializer tokenSerializer,
+ IAntiforgeryTokenStore tokenStore,
+ ILoggerFactory loggerFactory)
+ {
+ _options = antiforgeryOptionsAccessor.Value;
+ _tokenGenerator = tokenGenerator;
+ _tokenSerializer = tokenSerializer;
+ _tokenStore = tokenStore;
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ ///
+ public AntiforgeryTokenSet GetAndStoreTokens(HttpContext httpContext)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ CheckSSLConfig(httpContext);
+
+ var antiforgeryFeature = GetTokensInternal(httpContext);
+ var tokenSet = Serialize(antiforgeryFeature);
+
+ if (!antiforgeryFeature.HaveStoredNewCookieToken)
+ {
+ if (antiforgeryFeature.NewCookieToken != null)
+ {
+ // Serialize handles the new cookie token string.
+ Debug.Assert(antiforgeryFeature.NewCookieTokenString != null);
+
+ SaveCookieTokenAndHeader(httpContext, antiforgeryFeature.NewCookieTokenString);
+ antiforgeryFeature.HaveStoredNewCookieToken = true;
+ _logger.NewCookieToken();
+ }
+ else
+ {
+ _logger.ReusedCookieToken();
+ }
+ }
+
+ if (!httpContext.Response.HasStarted)
+ {
+ // Explicitly set the cache headers to 'no-cache'. This could override any user set value but this is fine
+ // as a response with antiforgery token must never be cached.
+ SetDoNotCacheHeaders(httpContext);
+ }
+
+ return tokenSet;
+ }
+
+ ///
+ public AntiforgeryTokenSet GetTokens(HttpContext httpContext)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ CheckSSLConfig(httpContext);
+
+ var antiforgeryFeature = GetTokensInternal(httpContext);
+ return Serialize(antiforgeryFeature);
+ }
+
+ ///
+ public async Task IsRequestValidAsync(HttpContext httpContext)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ CheckSSLConfig(httpContext);
+
+ var method = httpContext.Request.Method;
+ if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(method, "HEAD", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(method, "OPTIONS", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(method, "TRACE", StringComparison.OrdinalIgnoreCase))
+ {
+ // Validation not needed for these request types.
+ return true;
+ }
+
+ var tokens = await _tokenStore.GetRequestTokensAsync(httpContext);
+ if (tokens.CookieToken == null)
+ {
+ _logger.MissingCookieToken(_options.Cookie.Name);
+ return false;
+ }
+
+ if (tokens.RequestToken == null)
+ {
+ _logger.MissingRequestToken(_options.FormFieldName, _options.HeaderName);
+ return false;
+ }
+
+ // Extract cookie & request tokens
+ AntiforgeryToken deserializedCookieToken;
+ AntiforgeryToken deserializedRequestToken;
+ if (!TryDeserializeTokens(httpContext, tokens, out deserializedCookieToken, out deserializedRequestToken))
+ {
+ return false;
+ }
+
+ // Validate
+ string message;
+ var result = _tokenGenerator.TryValidateTokenSet(
+ httpContext,
+ deserializedCookieToken,
+ deserializedRequestToken,
+ out message);
+
+ if (result)
+ {
+ _logger.ValidatedAntiforgeryToken();
+ }
+ else
+ {
+ _logger.ValidationFailed(message);
+ }
+
+ return result;
+ }
+
+ ///
+ public async Task ValidateRequestAsync(HttpContext httpContext)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ CheckSSLConfig(httpContext);
+
+ var tokens = await _tokenStore.GetRequestTokensAsync(httpContext);
+ if (tokens.CookieToken == null)
+ {
+ throw new AntiforgeryValidationException(
+ Resources.FormatAntiforgery_CookieToken_MustBeProvided(_options.Cookie.Name));
+ }
+
+ if (tokens.RequestToken == null)
+ {
+ if (_options.HeaderName == null)
+ {
+ var message = Resources.FormatAntiforgery_FormToken_MustBeProvided(_options.FormFieldName);
+ throw new AntiforgeryValidationException(message);
+ }
+ else if (!httpContext.Request.HasFormContentType)
+ {
+ var message = Resources.FormatAntiforgery_HeaderToken_MustBeProvided(_options.HeaderName);
+ throw new AntiforgeryValidationException(message);
+ }
+ else
+ {
+ var message = Resources.FormatAntiforgery_RequestToken_MustBeProvided(
+ _options.FormFieldName,
+ _options.HeaderName);
+ throw new AntiforgeryValidationException(message);
+ }
+ }
+
+ ValidateTokens(httpContext, tokens);
+
+ _logger.ValidatedAntiforgeryToken();
+ }
+
+ private void ValidateTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet)
+ {
+ Debug.Assert(!string.IsNullOrEmpty(antiforgeryTokenSet.CookieToken));
+ Debug.Assert(!string.IsNullOrEmpty(antiforgeryTokenSet.RequestToken));
+
+ // Extract cookie & request tokens
+ AntiforgeryToken deserializedCookieToken;
+ AntiforgeryToken deserializedRequestToken;
+
+ DeserializeTokens(
+ httpContext,
+ antiforgeryTokenSet,
+ out deserializedCookieToken,
+ out deserializedRequestToken);
+
+ // Validate
+ string message;
+ if (!_tokenGenerator.TryValidateTokenSet(
+ httpContext,
+ deserializedCookieToken,
+ deserializedRequestToken,
+ out message))
+ {
+ throw new AntiforgeryValidationException(message);
+ }
+ }
+
+ ///
+ public void SetCookieTokenAndHeader(HttpContext httpContext)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ CheckSSLConfig(httpContext);
+
+ var antiforgeryFeature = GetCookieTokens(httpContext);
+ if (!antiforgeryFeature.HaveStoredNewCookieToken && antiforgeryFeature.NewCookieToken != null)
+ {
+ if (antiforgeryFeature.NewCookieTokenString == null)
+ {
+ antiforgeryFeature.NewCookieTokenString =
+ _tokenSerializer.Serialize(antiforgeryFeature.NewCookieToken);
+ }
+
+ SaveCookieTokenAndHeader(httpContext, antiforgeryFeature.NewCookieTokenString);
+ antiforgeryFeature.HaveStoredNewCookieToken = true;
+ _logger.NewCookieToken();
+ }
+ else
+ {
+ _logger.ReusedCookieToken();
+ }
+
+ if (!httpContext.Response.HasStarted)
+ {
+ SetDoNotCacheHeaders(httpContext);
+ }
+ }
+
+ private void SaveCookieTokenAndHeader(HttpContext httpContext, string cookieToken)
+ {
+ if (cookieToken != null)
+ {
+ // Persist the new cookie if it is not null.
+ _tokenStore.SaveCookieToken(httpContext, cookieToken);
+ }
+
+ if (!_options.SuppressXFrameOptionsHeader && !httpContext.Response.Headers.ContainsKey("X-Frame-Options"))
+ {
+ // Adding X-Frame-Options header to prevent ClickJacking. See
+ // http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-10
+ // for more information.
+ httpContext.Response.Headers["X-Frame-Options"] = "SAMEORIGIN";
+ }
+ }
+
+ private void CheckSSLConfig(HttpContext context)
+ {
+ if (_options.Cookie.SecurePolicy == CookieSecurePolicy.Always && !context.Request.IsHttps)
+ {
+ throw new InvalidOperationException(Resources.FormatAntiforgery_RequiresSSL(
+ string.Join(".", nameof(AntiforgeryOptions), nameof(AntiforgeryOptions.Cookie), nameof(CookieBuilder.SecurePolicy)),
+ nameof(CookieSecurePolicy.Always)));
+ }
+ }
+
+ private static IAntiforgeryFeature GetAntiforgeryFeature(HttpContext httpContext)
+ {
+ var antiforgeryFeature = httpContext.Features.Get();
+ if (antiforgeryFeature == null)
+ {
+ antiforgeryFeature = new AntiforgeryFeature();
+ httpContext.Features.Set(antiforgeryFeature);
+ }
+
+ return antiforgeryFeature;
+ }
+
+ private IAntiforgeryFeature GetCookieTokens(HttpContext httpContext)
+ {
+ var antiforgeryFeature = GetAntiforgeryFeature(httpContext);
+
+ if (antiforgeryFeature.HaveGeneratedNewCookieToken)
+ {
+ Debug.Assert(antiforgeryFeature.HaveDeserializedCookieToken);
+
+ // Have executed this method earlier in the context of this request.
+ return antiforgeryFeature;
+ }
+
+ AntiforgeryToken cookieToken;
+ if (antiforgeryFeature.HaveDeserializedCookieToken)
+ {
+ cookieToken = antiforgeryFeature.CookieToken;
+ }
+ else
+ {
+ cookieToken = GetCookieTokenDoesNotThrow(httpContext);
+
+ antiforgeryFeature.CookieToken = cookieToken;
+ antiforgeryFeature.HaveDeserializedCookieToken = true;
+ }
+
+ AntiforgeryToken newCookieToken;
+ if (_tokenGenerator.IsCookieTokenValid(cookieToken))
+ {
+ // No need for the cookie token from the request after it has been verified.
+ newCookieToken = null;
+ }
+ else
+ {
+ // Need to make sure we're always operating with a good cookie token.
+ newCookieToken = _tokenGenerator.GenerateCookieToken();
+ Debug.Assert(_tokenGenerator.IsCookieTokenValid(newCookieToken));
+ }
+
+ antiforgeryFeature.HaveGeneratedNewCookieToken = true;
+ antiforgeryFeature.NewCookieToken = newCookieToken;
+
+ return antiforgeryFeature;
+ }
+
+ private AntiforgeryToken GetCookieTokenDoesNotThrow(HttpContext httpContext)
+ {
+ try
+ {
+ var serializedToken = _tokenStore.GetCookieToken(httpContext);
+
+ if (serializedToken != null)
+ {
+ var token = _tokenSerializer.Deserialize(serializedToken);
+ return token;
+ }
+ }
+ catch (Exception ex)
+ {
+ // ignore failures since we'll just generate a new token
+ _logger.TokenDeserializeException(ex);
+ }
+
+ return null;
+ }
+
+ private IAntiforgeryFeature GetTokensInternal(HttpContext httpContext)
+ {
+ var antiforgeryFeature = GetCookieTokens(httpContext);
+ if (antiforgeryFeature.NewRequestToken == null)
+ {
+ var cookieToken = antiforgeryFeature.NewCookieToken ?? antiforgeryFeature.CookieToken;
+ antiforgeryFeature.NewRequestToken = _tokenGenerator.GenerateRequestToken(
+ httpContext,
+ cookieToken);
+ }
+
+ return antiforgeryFeature;
+ }
+
+ ///
+ /// Sets the 'Cache-Control' header to 'no-cache, no-store' and 'Pragma' header to 'no-cache' overriding any user set value.
+ ///
+ /// The .
+ protected virtual void SetDoNotCacheHeaders(HttpContext httpContext)
+ {
+ // Since antifogery token generation is not very obvious to the end users (ex: MVC's form tag generates them
+ // by default), log a warning to let users know of the change in behavior to any cache headers they might
+ // have set explicitly.
+ LogCacheHeaderOverrideWarning(httpContext.Response);
+
+ httpContext.Response.Headers[HeaderNames.CacheControl] = "no-cache, no-store";
+ httpContext.Response.Headers[HeaderNames.Pragma] = "no-cache";
+ }
+
+ private void LogCacheHeaderOverrideWarning(HttpResponse response)
+ {
+ var logWarning = false;
+ CacheControlHeaderValue cacheControlHeaderValue;
+ if (CacheControlHeaderValue.TryParse(response.Headers[HeaderNames.CacheControl].ToString(), out cacheControlHeaderValue))
+ {
+ if (!cacheControlHeaderValue.NoCache)
+ {
+ logWarning = true;
+ }
+ }
+
+ var pragmaHeader = response.Headers[HeaderNames.Pragma];
+ if (!logWarning
+ && !string.IsNullOrEmpty(pragmaHeader)
+ && string.Compare(pragmaHeader, "no-cache", ignoreCase: true) != 0)
+ {
+ logWarning = true;
+ }
+
+ if (logWarning)
+ {
+ _logger.ResponseCacheHeadersOverridenToNoCache();
+ }
+ }
+
+ private AntiforgeryTokenSet Serialize(IAntiforgeryFeature antiforgeryFeature)
+ {
+ // Should only be called after new tokens have been generated.
+ Debug.Assert(antiforgeryFeature.HaveGeneratedNewCookieToken);
+ Debug.Assert(antiforgeryFeature.NewRequestToken != null);
+
+ if (antiforgeryFeature.NewRequestTokenString == null)
+ {
+ antiforgeryFeature.NewRequestTokenString =
+ _tokenSerializer.Serialize(antiforgeryFeature.NewRequestToken);
+ }
+
+ if (antiforgeryFeature.NewCookieTokenString == null && antiforgeryFeature.NewCookieToken != null)
+ {
+ antiforgeryFeature.NewCookieTokenString =
+ _tokenSerializer.Serialize(antiforgeryFeature.NewCookieToken);
+ }
+
+ return new AntiforgeryTokenSet(
+ antiforgeryFeature.NewRequestTokenString,
+ antiforgeryFeature.NewCookieTokenString,
+ _options.FormFieldName,
+ _options.HeaderName);
+ }
+
+ private bool TryDeserializeTokens(
+ HttpContext httpContext,
+ AntiforgeryTokenSet antiforgeryTokenSet,
+ out AntiforgeryToken cookieToken,
+ out AntiforgeryToken requestToken)
+ {
+ try
+ {
+ DeserializeTokens(httpContext, antiforgeryTokenSet, out cookieToken, out requestToken);
+ return true;
+ }
+ catch (AntiforgeryValidationException ex)
+ {
+ _logger.FailedToDeserialzeTokens(ex);
+
+ cookieToken = null;
+ requestToken = null;
+ return false;
+ }
+ }
+
+ private void DeserializeTokens(
+ HttpContext httpContext,
+ AntiforgeryTokenSet antiforgeryTokenSet,
+ out AntiforgeryToken cookieToken,
+ out AntiforgeryToken requestToken)
+ {
+ var antiforgeryFeature = GetAntiforgeryFeature(httpContext);
+
+ if (antiforgeryFeature.HaveDeserializedCookieToken)
+ {
+ cookieToken = antiforgeryFeature.CookieToken;
+ }
+ else
+ {
+ cookieToken = _tokenSerializer.Deserialize(antiforgeryTokenSet.CookieToken);
+
+ antiforgeryFeature.CookieToken = cookieToken;
+ antiforgeryFeature.HaveDeserializedCookieToken = true;
+ }
+
+ if (antiforgeryFeature.HaveDeserializedRequestToken)
+ {
+ requestToken = antiforgeryFeature.RequestToken;
+ }
+ else
+ {
+ requestToken = _tokenSerializer.Deserialize(antiforgeryTokenSet.RequestToken);
+
+ antiforgeryFeature.RequestToken = requestToken;
+ antiforgeryFeature.HaveDeserializedRequestToken = true;
+ }
+ }
+ }
+}
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryAdditionalDataProvider.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryAdditionalDataProvider.cs
new file mode 100644
index 0000000000..ad28453495
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryAdditionalDataProvider.cs
@@ -0,0 +1,26 @@
+// 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.Antiforgery.Internal
+{
+ ///
+ /// A default implementation.
+ ///
+ public class DefaultAntiforgeryAdditionalDataProvider : IAntiforgeryAdditionalDataProvider
+ {
+ ///
+ public virtual string GetAdditionalData(HttpContext context)
+ {
+ return string.Empty;
+ }
+
+ ///
+ public virtual bool ValidateAdditionalData(HttpContext context, string additionalData)
+ {
+ // Default implementation does not understand anything but empty data.
+ return string.IsNullOrEmpty(additionalData);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenGenerator.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenGenerator.cs
new file mode 100644
index 0000000000..872f7ed18c
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenGenerator.cs
@@ -0,0 +1,238 @@
+// 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.Security.Claims;
+using System.Security.Principal;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class DefaultAntiforgeryTokenGenerator : IAntiforgeryTokenGenerator
+ {
+ private readonly IClaimUidExtractor _claimUidExtractor;
+ private readonly IAntiforgeryAdditionalDataProvider _additionalDataProvider;
+
+ public DefaultAntiforgeryTokenGenerator(
+ IClaimUidExtractor claimUidExtractor,
+ IAntiforgeryAdditionalDataProvider additionalDataProvider)
+ {
+ _claimUidExtractor = claimUidExtractor;
+ _additionalDataProvider = additionalDataProvider;
+ }
+
+ ///
+ public AntiforgeryToken GenerateCookieToken()
+ {
+ return new AntiforgeryToken()
+ {
+ // SecurityToken will be populated automatically.
+ IsCookieToken = true
+ };
+ }
+
+ ///
+ public AntiforgeryToken GenerateRequestToken(
+ HttpContext httpContext,
+ AntiforgeryToken cookieToken)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ if (cookieToken == null)
+ {
+ throw new ArgumentNullException(nameof(cookieToken));
+ }
+
+ if (!IsCookieTokenValid(cookieToken))
+ {
+ throw new ArgumentException(
+ Resources.Antiforgery_CookieToken_IsInvalid,
+ nameof(cookieToken));
+ }
+
+ var requestToken = new AntiforgeryToken()
+ {
+ SecurityToken = cookieToken.SecurityToken,
+ IsCookieToken = false
+ };
+
+ var isIdentityAuthenticated = false;
+
+ // populate Username and ClaimUid
+ var authenticatedIdentity = GetAuthenticatedIdentity(httpContext.User);
+ if (authenticatedIdentity != null)
+ {
+ isIdentityAuthenticated = true;
+ requestToken.ClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(httpContext.User));
+
+ if (requestToken.ClaimUid == null)
+ {
+ requestToken.Username = authenticatedIdentity.Name;
+ }
+ }
+
+ // populate AdditionalData
+ if (_additionalDataProvider != null)
+ {
+ requestToken.AdditionalData = _additionalDataProvider.GetAdditionalData(httpContext);
+ }
+
+ if (isIdentityAuthenticated
+ && string.IsNullOrEmpty(requestToken.Username)
+ && requestToken.ClaimUid == null
+ && string.IsNullOrEmpty(requestToken.AdditionalData))
+ {
+ // Application says user is authenticated, but we have no identifier for the user.
+ throw new InvalidOperationException(
+ Resources.FormatAntiforgeryTokenValidator_AuthenticatedUserWithoutUsername(
+ authenticatedIdentity.GetType(),
+ nameof(IIdentity.IsAuthenticated),
+ "true",
+ nameof(IIdentity.Name),
+ nameof(IAntiforgeryAdditionalDataProvider),
+ nameof(DefaultAntiforgeryAdditionalDataProvider)));
+ }
+
+ return requestToken;
+ }
+
+ ///
+ public bool IsCookieTokenValid(AntiforgeryToken cookieToken)
+ {
+ return cookieToken != null && cookieToken.IsCookieToken;
+ }
+
+ ///
+ public bool TryValidateTokenSet(
+ HttpContext httpContext,
+ AntiforgeryToken cookieToken,
+ AntiforgeryToken requestToken,
+ out string message)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ if (cookieToken == null)
+ {
+ throw new ArgumentNullException(
+ nameof(cookieToken),
+ Resources.Antiforgery_CookieToken_MustBeProvided_Generic);
+ }
+
+ if (requestToken == null)
+ {
+ throw new ArgumentNullException(
+ nameof(requestToken),
+ Resources.Antiforgery_RequestToken_MustBeProvided_Generic);
+ }
+
+ // Do the tokens have the correct format?
+ if (!cookieToken.IsCookieToken || requestToken.IsCookieToken)
+ {
+ message = Resources.AntiforgeryToken_TokensSwapped;
+ return false;
+ }
+
+ // Are the security tokens embedded in each incoming token identical?
+ if (!object.Equals(cookieToken.SecurityToken, requestToken.SecurityToken))
+ {
+ message = Resources.AntiforgeryToken_SecurityTokenMismatch;
+ return false;
+ }
+
+ // Is the incoming token meant for the current user?
+ var currentUsername = string.Empty;
+ BinaryBlob currentClaimUid = null;
+
+ var authenticatedIdentity = GetAuthenticatedIdentity(httpContext.User);
+ if (authenticatedIdentity != null)
+ {
+ currentClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(httpContext.User));
+ if (currentClaimUid == null)
+ {
+ currentUsername = authenticatedIdentity.Name ?? string.Empty;
+ }
+ }
+
+ // OpenID and other similar authentication schemes use URIs for the username.
+ // These should be treated as case-sensitive.
+ var comparer = StringComparer.OrdinalIgnoreCase;
+ if (currentUsername.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
+ currentUsername.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
+ {
+ comparer = StringComparer.Ordinal;
+ }
+
+ if (!comparer.Equals(requestToken.Username, currentUsername))
+ {
+ message = Resources.FormatAntiforgeryToken_UsernameMismatch(requestToken.Username, currentUsername);
+ return false;
+ }
+
+ if (!object.Equals(requestToken.ClaimUid, currentClaimUid))
+ {
+ message = Resources.AntiforgeryToken_ClaimUidMismatch;
+ return false;
+ }
+
+ // Is the AdditionalData valid?
+ if (_additionalDataProvider != null &&
+ !_additionalDataProvider.ValidateAdditionalData(httpContext, requestToken.AdditionalData))
+ {
+ message = Resources.AntiforgeryToken_AdditionalDataCheckFailed;
+ return false;
+ }
+
+ message = null;
+ return true;
+ }
+
+ private static BinaryBlob GetClaimUidBlob(string base64ClaimUid)
+ {
+ if (base64ClaimUid == null)
+ {
+ return null;
+ }
+
+ return new BinaryBlob(256, Convert.FromBase64String(base64ClaimUid));
+ }
+
+ private static ClaimsIdentity GetAuthenticatedIdentity(ClaimsPrincipal claimsPrincipal)
+ {
+ if (claimsPrincipal == null)
+ {
+ return null;
+ }
+
+ var identitiesList = claimsPrincipal.Identities as List;
+ if (identitiesList != null)
+ {
+ for (var i = 0; i < identitiesList.Count; i++)
+ {
+ if (identitiesList[i].IsAuthenticated)
+ {
+ return identitiesList[i];
+ }
+ }
+ }
+ else
+ {
+ foreach (var identity in claimsPrincipal.Identities)
+ {
+ if (identity.IsAuthenticated)
+ {
+ return identity;
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenSerializer.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenSerializer.cs
new file mode 100644
index 0000000000..d71f2a2185
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenSerializer.cs
@@ -0,0 +1,188 @@
+// 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.IO;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class DefaultAntiforgeryTokenSerializer : IAntiforgeryTokenSerializer
+ {
+ private static readonly string Purpose = "Microsoft.AspNetCore.Antiforgery.AntiforgeryToken.v1";
+ private const byte TokenVersion = 0x01;
+
+ private readonly IDataProtector _cryptoSystem;
+ private readonly ObjectPool _pool;
+
+ public DefaultAntiforgeryTokenSerializer(
+ IDataProtectionProvider provider,
+ ObjectPool pool)
+ {
+ if (provider == null)
+ {
+ throw new ArgumentNullException(nameof(provider));
+ }
+
+ if (pool == null)
+ {
+ throw new ArgumentNullException(nameof(pool));
+ }
+
+ _cryptoSystem = provider.CreateProtector(Purpose);
+ _pool = pool;
+ }
+
+ public AntiforgeryToken Deserialize(string serializedToken)
+ {
+ var serializationContext = _pool.Get();
+
+ Exception innerException = null;
+ try
+ {
+ var count = serializedToken.Length;
+ var charsRequired = WebEncoders.GetArraySizeRequiredToDecode(count);
+ var chars = serializationContext.GetChars(charsRequired);
+ var tokenBytes = WebEncoders.Base64UrlDecode(
+ serializedToken,
+ offset: 0,
+ buffer: chars,
+ bufferOffset: 0,
+ count: count);
+
+ var unprotectedBytes = _cryptoSystem.Unprotect(tokenBytes);
+ var stream = serializationContext.Stream;
+ stream.Write(unprotectedBytes, offset: 0, count: unprotectedBytes.Length);
+ stream.Position = 0L;
+
+ var reader = serializationContext.Reader;
+ var token = Deserialize(reader);
+ if (token != null)
+ {
+ return token;
+ }
+ }
+ catch (Exception ex)
+ {
+ // swallow all exceptions - homogenize error if something went wrong
+ innerException = ex;
+ }
+ finally
+ {
+ _pool.Return(serializationContext);
+ }
+
+ // if we reached this point, something went wrong deserializing
+ throw new AntiforgeryValidationException(Resources.AntiforgeryToken_DeserializationFailed, innerException);
+ }
+
+ /* The serialized format of the anti-XSRF token is as follows:
+ * Version: 1 byte integer
+ * SecurityToken: 16 byte binary blob
+ * IsCookieToken: 1 byte Boolean
+ * [if IsCookieToken != true]
+ * +- IsClaimsBased: 1 byte Boolean
+ * | [if IsClaimsBased = true]
+ * | `- ClaimUid: 32 byte binary blob
+ * | [if IsClaimsBased = false]
+ * | `- Username: UTF-8 string with 7-bit integer length prefix
+ * `- AdditionalData: UTF-8 string with 7-bit integer length prefix
+ */
+ private static AntiforgeryToken Deserialize(BinaryReader reader)
+ {
+ // we can only consume tokens of the same serialized version that we generate
+ var embeddedVersion = reader.ReadByte();
+ if (embeddedVersion != TokenVersion)
+ {
+ return null;
+ }
+
+ var deserializedToken = new AntiforgeryToken();
+ var securityTokenBytes = reader.ReadBytes(AntiforgeryToken.SecurityTokenBitLength / 8);
+ deserializedToken.SecurityToken =
+ new BinaryBlob(AntiforgeryToken.SecurityTokenBitLength, securityTokenBytes);
+ deserializedToken.IsCookieToken = reader.ReadBoolean();
+
+ if (!deserializedToken.IsCookieToken)
+ {
+ var isClaimsBased = reader.ReadBoolean();
+ if (isClaimsBased)
+ {
+ var claimUidBytes = reader.ReadBytes(AntiforgeryToken.ClaimUidBitLength / 8);
+ deserializedToken.ClaimUid = new BinaryBlob(AntiforgeryToken.ClaimUidBitLength, claimUidBytes);
+ }
+ else
+ {
+ deserializedToken.Username = reader.ReadString();
+ }
+
+ deserializedToken.AdditionalData = reader.ReadString();
+ }
+
+ // if there's still unconsumed data in the stream, fail
+ if (reader.BaseStream.ReadByte() != -1)
+ {
+ return null;
+ }
+
+ // success
+ return deserializedToken;
+ }
+
+ public string Serialize(AntiforgeryToken token)
+ {
+ if (token == null)
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ var serializationContext = _pool.Get();
+
+ try
+ {
+ var writer = serializationContext.Writer;
+ writer.Write(TokenVersion);
+ writer.Write(token.SecurityToken.GetData());
+ writer.Write(token.IsCookieToken);
+
+ if (!token.IsCookieToken)
+ {
+ if (token.ClaimUid != null)
+ {
+ writer.Write(true /* isClaimsBased */);
+ writer.Write(token.ClaimUid.GetData());
+ }
+ else
+ {
+ writer.Write(false /* isClaimsBased */);
+ writer.Write(token.Username);
+ }
+
+ writer.Write(token.AdditionalData);
+ }
+
+ writer.Flush();
+ var stream = serializationContext.Stream;
+ var bytes = _cryptoSystem.Protect(stream.ToArray());
+
+ var count = bytes.Length;
+ var charsRequired = WebEncoders.GetArraySizeRequiredToEncode(count);
+ var chars = serializationContext.GetChars(charsRequired);
+ var outputLength = WebEncoders.Base64UrlEncode(
+ bytes,
+ offset: 0,
+ output: chars,
+ outputOffset: 0,
+ count: count);
+
+ return new string(chars, startIndex: 0, length: outputLength);
+ }
+ finally
+ {
+ _pool.Return(serializationContext);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenStore.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenStore.cs
new file mode 100644
index 0000000000..95e6d6f1bc
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenStore.cs
@@ -0,0 +1,90 @@
+// 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.Diagnostics;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class DefaultAntiforgeryTokenStore : IAntiforgeryTokenStore
+ {
+ private readonly AntiforgeryOptions _options;
+
+ public DefaultAntiforgeryTokenStore(IOptions optionsAccessor)
+ {
+ if (optionsAccessor == null)
+ {
+ throw new ArgumentNullException(nameof(optionsAccessor));
+ }
+
+ _options = optionsAccessor.Value;
+ }
+
+ public string GetCookieToken(HttpContext httpContext)
+ {
+ Debug.Assert(httpContext != null);
+
+ var requestCookie = httpContext.Request.Cookies[_options.Cookie.Name];
+ if (string.IsNullOrEmpty(requestCookie))
+ {
+ // unable to find the cookie.
+ return null;
+ }
+
+ return requestCookie;
+ }
+
+ public async Task GetRequestTokensAsync(HttpContext httpContext)
+ {
+ Debug.Assert(httpContext != null);
+
+ var cookieToken = httpContext.Request.Cookies[_options.Cookie.Name];
+
+ // We want to delay reading the form as much as possible, for example in case of large file uploads,
+ // request token could be part of the header.
+ StringValues requestToken;
+ if (_options.HeaderName != null)
+ {
+ requestToken = httpContext.Request.Headers[_options.HeaderName];
+ }
+
+ // Fall back to reading form instead
+ if (requestToken.Count == 0 && httpContext.Request.HasFormContentType)
+ {
+ // Check the content-type before accessing the form collection to make sure
+ // we report errors gracefully.
+ var form = await httpContext.Request.ReadFormAsync();
+ requestToken = form[_options.FormFieldName];
+ }
+
+ return new AntiforgeryTokenSet(requestToken, cookieToken, _options.FormFieldName, _options.HeaderName);
+ }
+
+ public void SaveCookieToken(HttpContext httpContext, string token)
+ {
+ Debug.Assert(httpContext != null);
+ Debug.Assert(token != null);
+
+ var options = _options.Cookie.Build(httpContext);
+
+ if (_options.Cookie.Path != null)
+ {
+ options.Path = _options.Cookie.Path.ToString();
+ }
+ else
+ {
+ var pathBase = httpContext.Request.PathBase.ToString();
+ if (!string.IsNullOrEmpty(pathBase))
+ {
+ options.Path = pathBase;
+ }
+ }
+
+ httpContext.Response.Cookies.Append(_options.Cookie.Name, token, options);
+ }
+ }
+}
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultClaimUidExtractor.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultClaimUidExtractor.cs
new file mode 100644
index 0000000000..1a7ef394a0
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultClaimUidExtractor.cs
@@ -0,0 +1,149 @@
+// 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.Diagnostics;
+using System.Security.Claims;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ ///
+ /// Default implementation of .
+ ///
+ public class DefaultClaimUidExtractor : IClaimUidExtractor
+ {
+ private readonly ObjectPool _pool;
+
+ public DefaultClaimUidExtractor(ObjectPool pool)
+ {
+ _pool = pool;
+ }
+
+ ///
+ public string ExtractClaimUid(ClaimsPrincipal claimsPrincipal)
+ {
+ Debug.Assert(claimsPrincipal != null);
+
+ var uniqueIdentifierParameters = GetUniqueIdentifierParameters(claimsPrincipal.Identities);
+ if (uniqueIdentifierParameters == null)
+ {
+ // No authenticated identities containing claims found.
+ return null;
+ }
+
+ var claimUidBytes = ComputeSha256(uniqueIdentifierParameters);
+ return Convert.ToBase64String(claimUidBytes);
+ }
+
+ public static IList GetUniqueIdentifierParameters(IEnumerable claimsIdentities)
+ {
+ var identitiesList = claimsIdentities as List;
+ if (identitiesList == null)
+ {
+ identitiesList = new List(claimsIdentities);
+ }
+
+ for (var i = 0; i < identitiesList.Count; i++)
+ {
+ var identity = identitiesList[i];
+ if (!identity.IsAuthenticated)
+ {
+ continue;
+ }
+
+ var subClaim = identity.FindFirst(
+ claim => string.Equals("sub", claim.Type, StringComparison.Ordinal));
+ if (subClaim != null && !string.IsNullOrEmpty(subClaim.Value))
+ {
+ return new string[]
+ {
+ subClaim.Type,
+ subClaim.Value,
+ subClaim.Issuer
+ };
+ }
+
+ var nameIdentifierClaim = identity.FindFirst(
+ claim => string.Equals(ClaimTypes.NameIdentifier, claim.Type, StringComparison.Ordinal));
+ if (nameIdentifierClaim != null && !string.IsNullOrEmpty(nameIdentifierClaim.Value))
+ {
+ return new string[]
+ {
+ nameIdentifierClaim.Type,
+ nameIdentifierClaim.Value,
+ nameIdentifierClaim.Issuer
+ };
+ }
+
+ var upnClaim = identity.FindFirst(
+ claim => string.Equals(ClaimTypes.Upn, claim.Type, StringComparison.Ordinal));
+ if (upnClaim != null && !string.IsNullOrEmpty(upnClaim.Value))
+ {
+ return new string[]
+ {
+ upnClaim.Type,
+ upnClaim.Value,
+ upnClaim.Issuer
+ };
+ }
+ }
+
+ // We do not understand any of the ClaimsIdentity instances, fallback on serializing all claims in every claims Identity.
+ var allClaims = new List();
+ for (var i = 0; i < identitiesList.Count; i++)
+ {
+ if (identitiesList[i].IsAuthenticated)
+ {
+ allClaims.AddRange(identitiesList[i].Claims);
+ }
+ }
+
+ if (allClaims.Count == 0)
+ {
+ // No authenticated identities containing claims found.
+ return null;
+ }
+
+ allClaims.Sort((a, b) => string.Compare(a.Type, b.Type, StringComparison.Ordinal));
+
+ var identifierParameters = new List(allClaims.Count * 3);
+ for (var i = 0; i < allClaims.Count; i++)
+ {
+ var claim = allClaims[i];
+ identifierParameters.Add(claim.Type);
+ identifierParameters.Add(claim.Value);
+ identifierParameters.Add(claim.Issuer);
+ }
+
+ return identifierParameters;
+ }
+
+ private byte[] ComputeSha256(IEnumerable parameters)
+ {
+ var serializationContext = _pool.Get();
+
+ try
+ {
+ var writer = serializationContext.Writer;
+ foreach (string parameter in parameters)
+ {
+ writer.Write(parameter); // also writes the length as a prefix; unambiguous
+ }
+
+ writer.Flush();
+
+ var sha256 = serializationContext.Sha256;
+ var stream = serializationContext.Stream;
+ var bytes = sha256.ComputeHash(stream.ToArray(), 0, checked((int)stream.Length));
+
+ return bytes;
+ }
+ finally
+ {
+ _pool.Return(serializationContext);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryFeature.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryFeature.cs
new file mode 100644
index 0000000000..2404359de7
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryFeature.cs
@@ -0,0 +1,25 @@
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public interface IAntiforgeryFeature
+ {
+ AntiforgeryToken CookieToken { get; set; }
+
+ bool HaveDeserializedCookieToken { get; set; }
+
+ bool HaveDeserializedRequestToken { get; set; }
+
+ bool HaveGeneratedNewCookieToken { get; set; }
+
+ bool HaveStoredNewCookieToken { get; set; }
+
+ AntiforgeryToken NewCookieToken { get; set; }
+
+ string NewCookieTokenString { get; set; }
+
+ AntiforgeryToken NewRequestToken { get; set; }
+
+ string NewRequestTokenString { get; set; }
+
+ AntiforgeryToken RequestToken { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryTokenGenerator.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryTokenGenerator.cs
new file mode 100644
index 0000000000..c0dff86047
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryTokenGenerator.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 Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ ///
+ /// Generates and validates antiforgery tokens.
+ ///
+ public interface IAntiforgeryTokenGenerator
+ {
+ ///
+ /// Generates a new random cookie token.
+ ///
+ /// An .
+ AntiforgeryToken GenerateCookieToken();
+
+ ///
+ /// Generates a request token corresponding to .
+ ///
+ /// The associated with the current request.
+ /// A valid cookie token.
+ /// An .
+ AntiforgeryToken GenerateRequestToken(HttpContext httpContext, AntiforgeryToken cookieToken);
+
+ ///
+ /// Attempts to validate a cookie token.
+ ///
+ /// A valid cookie token.
+ /// true if the cookie token is valid, otherwise false.
+ bool IsCookieTokenValid(AntiforgeryToken cookieToken);
+
+ ///
+ /// Attempts to validate a cookie and request token set for the given .
+ ///
+ /// The associated with the current request.
+ /// A cookie token.
+ /// A request token.
+ ///
+ /// Will be set to the validation message if the tokens are invalid, otherwise null.
+ ///
+ /// true if the tokens are valid, otherwise false.
+ bool TryValidateTokenSet(
+ HttpContext httpContext,
+ AntiforgeryToken cookieToken,
+ AntiforgeryToken requestToken,
+ out string message);
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryTokenSerializer.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryTokenSerializer.cs
new file mode 100644
index 0000000000..134516e8c9
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryTokenSerializer.cs
@@ -0,0 +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.
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ // Abstracts out the serialization process for an antiforgery token
+ public interface IAntiforgeryTokenSerializer
+ {
+ AntiforgeryToken Deserialize(string serializedToken);
+ string Serialize(AntiforgeryToken token);
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryTokenStore.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryTokenStore.cs
new file mode 100644
index 0000000000..1b4aa8ec05
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryTokenStore.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.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public interface IAntiforgeryTokenStore
+ {
+ string GetCookieToken(HttpContext httpContext);
+
+ ///
+ /// Gets the cookie and request tokens from the request.
+ ///
+ /// The for the current request.
+ /// The .
+ Task GetRequestTokensAsync(HttpContext httpContext);
+
+ void SaveCookieToken(HttpContext httpContext, string token);
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/IClaimUidExtractor.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/IClaimUidExtractor.cs
new file mode 100644
index 0000000000..72ab230fb4
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Internal/IClaimUidExtractor.cs
@@ -0,0 +1,21 @@
+// 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.Collections.Generic;
+using System.Security.Claims;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ ///
+ /// This interface can extract unique identifers for a .
+ ///
+ public interface IClaimUidExtractor
+ {
+ ///
+ /// Extracts claims identifier.
+ ///
+ /// The .
+ /// The claims identifier.
+ string ExtractClaimUid(ClaimsPrincipal claimsPrincipal);
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Microsoft.AspNetCore.Antiforgery.csproj b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Microsoft.AspNetCore.Antiforgery.csproj
new file mode 100644
index 0000000000..e196bf7f56
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Microsoft.AspNetCore.Antiforgery.csproj
@@ -0,0 +1,19 @@
+
+
+
+ An antiforgery system for ASP.NET Core designed to generate and validate tokens to prevent Cross-Site Request Forgery attacks.
+ netstandard2.0
+ $(NoWarn);CS1591
+ true
+ aspnetcore;antiforgery
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Properties/AssemblyInfo.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..490fb19533
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Properties/AssemblyInfo.cs
@@ -0,0 +1,7 @@
+// 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.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Antiforgery.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Properties/Resources.Designer.cs b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..83811ea2dc
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Properties/Resources.Designer.cs
@@ -0,0 +1,254 @@
+//
+namespace Microsoft.AspNetCore.Antiforgery
+{
+ using System.Globalization;
+ using System.Reflection;
+ using System.Resources;
+
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.AspNetCore.Antiforgery.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// The provided identity of type '{0}' is marked {1} = {2} but does not have a value for {3}. By default, the antiforgery system requires that all authenticated identities have a unique {3}. If it is not possible to provide a unique {3} for this identity, consider extending {4} by overriding the {5} or a custom type that can provide some form of unique identifier for the current user.
+ ///
+ internal static string AntiforgeryTokenValidator_AuthenticatedUserWithoutUsername
+ {
+ get => GetString("AntiforgeryTokenValidator_AuthenticatedUserWithoutUsername");
+ }
+
+ ///
+ /// The provided identity of type '{0}' is marked {1} = {2} but does not have a value for {3}. By default, the antiforgery system requires that all authenticated identities have a unique {3}. If it is not possible to provide a unique {3} for this identity, consider extending {4} by overriding the {5} or a custom type that can provide some form of unique identifier for the current user.
+ ///
+ internal static string FormatAntiforgeryTokenValidator_AuthenticatedUserWithoutUsername(object p0, object p1, object p2, object p3, object p4, object p5)
+ => string.Format(CultureInfo.CurrentCulture, GetString("AntiforgeryTokenValidator_AuthenticatedUserWithoutUsername"), p0, p1, p2, p3, p4, p5);
+
+ ///
+ /// The provided antiforgery token failed a custom data check.
+ ///
+ internal static string AntiforgeryToken_AdditionalDataCheckFailed
+ {
+ get => GetString("AntiforgeryToken_AdditionalDataCheckFailed");
+ }
+
+ ///
+ /// The provided antiforgery token failed a custom data check.
+ ///
+ internal static string FormatAntiforgeryToken_AdditionalDataCheckFailed()
+ => GetString("AntiforgeryToken_AdditionalDataCheckFailed");
+
+ ///
+ /// The provided antiforgery token was meant for a different claims-based user than the current user.
+ ///
+ internal static string AntiforgeryToken_ClaimUidMismatch
+ {
+ get => GetString("AntiforgeryToken_ClaimUidMismatch");
+ }
+
+ ///
+ /// The provided antiforgery token was meant for a different claims-based user than the current user.
+ ///
+ internal static string FormatAntiforgeryToken_ClaimUidMismatch()
+ => GetString("AntiforgeryToken_ClaimUidMismatch");
+
+ ///
+ /// The antiforgery token could not be decrypted.
+ ///
+ internal static string AntiforgeryToken_DeserializationFailed
+ {
+ get => GetString("AntiforgeryToken_DeserializationFailed");
+ }
+
+ ///
+ /// The antiforgery token could not be decrypted.
+ ///
+ internal static string FormatAntiforgeryToken_DeserializationFailed()
+ => GetString("AntiforgeryToken_DeserializationFailed");
+
+ ///
+ /// The antiforgery cookie token and request token do not match.
+ ///
+ internal static string AntiforgeryToken_SecurityTokenMismatch
+ {
+ get => GetString("AntiforgeryToken_SecurityTokenMismatch");
+ }
+
+ ///
+ /// The antiforgery cookie token and request token do not match.
+ ///
+ internal static string FormatAntiforgeryToken_SecurityTokenMismatch()
+ => GetString("AntiforgeryToken_SecurityTokenMismatch");
+
+ ///
+ /// Validation of the provided antiforgery token failed. The cookie token and the request token were swapped.
+ ///
+ internal static string AntiforgeryToken_TokensSwapped
+ {
+ get => GetString("AntiforgeryToken_TokensSwapped");
+ }
+
+ ///
+ /// Validation of the provided antiforgery token failed. The cookie token and the request token were swapped.
+ ///
+ internal static string FormatAntiforgeryToken_TokensSwapped()
+ => GetString("AntiforgeryToken_TokensSwapped");
+
+ ///
+ /// The provided antiforgery token was meant for user "{0}", but the current user is "{1}".
+ ///
+ internal static string AntiforgeryToken_UsernameMismatch
+ {
+ get => GetString("AntiforgeryToken_UsernameMismatch");
+ }
+
+ ///
+ /// The provided antiforgery token was meant for user "{0}", but the current user is "{1}".
+ ///
+ internal static string FormatAntiforgeryToken_UsernameMismatch(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("AntiforgeryToken_UsernameMismatch"), p0, p1);
+
+ ///
+ /// The antiforgery cookie token is invalid.
+ ///
+ internal static string Antiforgery_CookieToken_IsInvalid
+ {
+ get => GetString("Antiforgery_CookieToken_IsInvalid");
+ }
+
+ ///
+ /// The antiforgery cookie token is invalid.
+ ///
+ internal static string FormatAntiforgery_CookieToken_IsInvalid()
+ => GetString("Antiforgery_CookieToken_IsInvalid");
+
+ ///
+ /// The required antiforgery cookie "{0}" is not present.
+ ///
+ internal static string Antiforgery_CookieToken_MustBeProvided
+ {
+ get => GetString("Antiforgery_CookieToken_MustBeProvided");
+ }
+
+ ///
+ /// The required antiforgery cookie "{0}" is not present.
+ ///
+ internal static string FormatAntiforgery_CookieToken_MustBeProvided(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("Antiforgery_CookieToken_MustBeProvided"), p0);
+
+ ///
+ /// The required antiforgery cookie token must be provided.
+ ///
+ internal static string Antiforgery_CookieToken_MustBeProvided_Generic
+ {
+ get => GetString("Antiforgery_CookieToken_MustBeProvided_Generic");
+ }
+
+ ///
+ /// The required antiforgery cookie token must be provided.
+ ///
+ internal static string FormatAntiforgery_CookieToken_MustBeProvided_Generic()
+ => GetString("Antiforgery_CookieToken_MustBeProvided_Generic");
+
+ ///
+ /// The required antiforgery form field "{0}" is not present.
+ ///
+ internal static string Antiforgery_FormToken_MustBeProvided
+ {
+ get => GetString("Antiforgery_FormToken_MustBeProvided");
+ }
+
+ ///
+ /// The required antiforgery form field "{0}" is not present.
+ ///
+ internal static string FormatAntiforgery_FormToken_MustBeProvided(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("Antiforgery_FormToken_MustBeProvided"), p0);
+
+ ///
+ /// The required antiforgery header value "{0}" is not present.
+ ///
+ internal static string Antiforgery_HeaderToken_MustBeProvided
+ {
+ get => GetString("Antiforgery_HeaderToken_MustBeProvided");
+ }
+
+ ///
+ /// The required antiforgery header value "{0}" is not present.
+ ///
+ internal static string FormatAntiforgery_HeaderToken_MustBeProvided(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("Antiforgery_HeaderToken_MustBeProvided"), p0);
+
+ ///
+ /// The required antiforgery request token was not provided in either form field "{0}" or header value "{1}".
+ ///
+ internal static string Antiforgery_RequestToken_MustBeProvided
+ {
+ get => GetString("Antiforgery_RequestToken_MustBeProvided");
+ }
+
+ ///
+ /// The required antiforgery request token was not provided in either form field "{0}" or header value "{1}".
+ ///
+ internal static string FormatAntiforgery_RequestToken_MustBeProvided(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("Antiforgery_RequestToken_MustBeProvided"), p0, p1);
+
+ ///
+ /// The required antiforgery request token must be provided.
+ ///
+ internal static string Antiforgery_RequestToken_MustBeProvided_Generic
+ {
+ get => GetString("Antiforgery_RequestToken_MustBeProvided_Generic");
+ }
+
+ ///
+ /// The required antiforgery request token must be provided.
+ ///
+ internal static string FormatAntiforgery_RequestToken_MustBeProvided_Generic()
+ => GetString("Antiforgery_RequestToken_MustBeProvided_Generic");
+
+ ///
+ /// The antiforgery system has the configuration value {optionName} = {value}, but the current request is not an SSL request.
+ ///
+ internal static string Antiforgery_RequiresSSL
+ {
+ get => GetString("Antiforgery_RequiresSSL");
+ }
+
+ ///
+ /// The antiforgery system has the configuration value {optionName} = {value}, but the current request is not an SSL request.
+ ///
+ internal static string FormatAntiforgery_RequiresSSL(object optionName, object value)
+ => string.Format(CultureInfo.CurrentCulture, GetString("Antiforgery_RequiresSSL", "optionName", "value"), optionName, value);
+
+ ///
+ /// Value cannot be null or empty.
+ ///
+ internal static string ArgumentCannotBeNullOrEmpty
+ {
+ get => GetString("ArgumentCannotBeNullOrEmpty");
+ }
+
+ ///
+ /// Value cannot be null or empty.
+ ///
+ internal static string FormatArgumentCannotBeNullOrEmpty()
+ => GetString("ArgumentCannotBeNullOrEmpty");
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+
+ System.Diagnostics.Debug.Assert(value != null);
+
+ if (formatterNames != null)
+ {
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Resources.resx b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Resources.resx
new file mode 100644
index 0000000000..eeda70bc63
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Resources.resx
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ The provided identity of type '{0}' is marked {1} = {2} but does not have a value for {3}. By default, the antiforgery system requires that all authenticated identities have a unique {3}. If it is not possible to provide a unique {3} for this identity, consider extending {4} by overriding the {5} or a custom type that can provide some form of unique identifier for the current user.
+ 0 = typeof(identity), 1 = nameof(IsAuthenticated), 2 = bool.TrueString, 3 = nameof(Name), 4 = nameof(IAdditionalDataProvider), 5 = nameof(DefaultAdditionalDataProvider)
+
+
+ The provided antiforgery token failed a custom data check.
+
+
+ The provided antiforgery token was meant for a different claims-based user than the current user.
+
+
+ The antiforgery token could not be decrypted.
+
+
+ The antiforgery cookie token and request token do not match.
+
+
+ Validation of the provided antiforgery token failed. The cookie token and the request token were swapped.
+
+
+ The provided antiforgery token was meant for user "{0}", but the current user is "{1}".
+
+
+ The antiforgery cookie token is invalid.
+
+
+ The required antiforgery cookie "{0}" is not present.
+
+
+ The required antiforgery cookie token must be provided.
+
+
+ The required antiforgery form field "{0}" is not present.
+
+
+ The required antiforgery header value "{0}" is not present.
+
+
+ The required antiforgery request token was not provided in either form field "{0}" or header value "{1}".
+
+
+ The required antiforgery request token must be provided.
+
+
+ The antiforgery system has the configuration value {optionName} = {value}, but the current request is not an SSL request.
+
+
+ Value cannot be null or empty.
+
+
\ No newline at end of file
diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/baseline.netcore.json b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/baseline.netcore.json
new file mode 100644
index 0000000000..eaa03254ea
--- /dev/null
+++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/baseline.netcore.json
@@ -0,0 +1,456 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.Antiforgery, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.Extensions.DependencyInjection.AntiforgeryServiceCollectionExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "AddAntiforgery",
+ "Parameters": [
+ {
+ "Name": "services",
+ "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
+ }
+ ],
+ "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AddAntiforgery",
+ "Parameters": [
+ {
+ "Name": "services",
+ "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
+ },
+ {
+ "Name": "setupAction",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Antiforgery.AntiforgeryOptions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Cookie",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Http.CookieBuilder",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Cookie",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Microsoft.AspNetCore.Http.CookieBuilder"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_FormFieldName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_FormFieldName",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_HeaderName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_HeaderName",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_SuppressXFrameOptionsHeader",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_SuppressXFrameOptionsHeader",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Boolean"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_CookieName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_CookieName",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_CookiePath",
+ "Parameters": [],
+ "ReturnType": "System.Nullable",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_CookiePath",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Nullable"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_CookieDomain",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_CookieDomain",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_RequireSsl",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_RequireSsl",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Boolean"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "DefaultCookiePrefix",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Antiforgery.AntiforgeryTokenSet",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_RequestToken",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_FormFieldName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_HeaderName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_CookieToken",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "requestToken",
+ "Type": "System.String"
+ },
+ {
+ "Name": "cookieToken",
+ "Type": "System.String"
+ },
+ {
+ "Name": "formFieldName",
+ "Type": "System.String"
+ },
+ {
+ "Name": "headerName",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "System.Exception",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "message",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "message",
+ "Type": "System.String"
+ },
+ {
+ "Name": "innerException",
+ "Type": "System.Exception"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Antiforgery.IAntiforgery",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "GetAndStoreTokens",
+ "Parameters": [
+ {
+ "Name": "httpContext",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Antiforgery.AntiforgeryTokenSet",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetTokens",
+ "Parameters": [
+ {
+ "Name": "httpContext",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Antiforgery.AntiforgeryTokenSet",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "IsRequestValidAsync",
+ "Parameters": [
+ {
+ "Name": "httpContext",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ValidateRequestAsync",
+ "Parameters": [
+ {
+ "Name": "httpContext",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SetCookieTokenAndHeader",
+ "Parameters": [
+ {
+ "Name": "httpContext",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Antiforgery.IAntiforgeryAdditionalDataProvider",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "GetAdditionalData",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ }
+ ],
+ "ReturnType": "System.String",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ValidateAdditionalData",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "additionalData",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Boolean",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Antiforgery/test/Directory.Build.props b/src/Antiforgery/test/Directory.Build.props
new file mode 100644
index 0000000000..eb4ed371f3
--- /dev/null
+++ b/src/Antiforgery/test/Directory.Build.props
@@ -0,0 +1,10 @@
+
+
+
+
+ netcoreapp2.1
+ $(DeveloperBuildTestTfms)
+ netcoreapp2.1;netcoreapp2.0
+ $(StandardTestTfms);net461
+
+
\ No newline at end of file
diff --git a/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/AntiforgeryOptionsSetupTest.cs b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/AntiforgeryOptionsSetupTest.cs
new file mode 100644
index 0000000000..b0acff572d
--- /dev/null
+++ b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/AntiforgeryOptionsSetupTest.cs
@@ -0,0 +1,73 @@
+// 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.DataProtection;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class AntiforgeryOptionsSetupTest
+ {
+ [Theory]
+ [InlineData("HelloWorldApp", ".AspNetCore.Antiforgery.tGmK82_ckDw")]
+ [InlineData("TodoCalendar", ".AspNetCore.Antiforgery.7mK1hBEBwYs")]
+ public void AntiforgeryOptionsSetup_SetsDefaultCookieName_BasedOnApplicationId(
+ string applicationId,
+ string expectedCookieName)
+ {
+ // Arrange
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddAntiforgery();
+ serviceCollection
+ .AddDataProtection()
+ .SetApplicationName(applicationId);
+
+ var services = serviceCollection.BuildServiceProvider();
+ var options = services.GetRequiredService>();
+
+ // Act
+ var cookieName = options.Value.Cookie.Name;
+
+ // Assert
+ Assert.Equal(expectedCookieName, cookieName);
+ }
+
+ [Fact]
+ public void AntiforgeryOptionsSetup_UserOptionsSetup_CanSetCookieName()
+ {
+ // Arrange
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.Configure(o =>
+ {
+ Assert.Null(o.Cookie.Name);
+ o.Cookie.Name = "antiforgery";
+ });
+ serviceCollection.AddAntiforgery();
+ serviceCollection
+ .AddDataProtection()
+ .SetApplicationName("HelloWorldApp");
+
+ var services = serviceCollection.BuildServiceProvider();
+ var options = services.GetRequiredService>();
+
+ // Act
+ var cookieName = options.Value.Cookie.Name;
+
+ // Assert
+ Assert.Equal("antiforgery", cookieName);
+ }
+
+ [Fact]
+ public void AntiforgeryOptions_SetsCookieSecurePolicy_ToNone_ByDefault()
+ {
+ // Arrange & Act
+ var options = new AntiforgeryOptions();
+
+ // Assert
+ Assert.Equal(CookieSecurePolicy.None, options.Cookie.SecurePolicy);
+ }
+ }
+}
diff --git a/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/AntiforgeryTokenTest.cs b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/AntiforgeryTokenTest.cs
new file mode 100644
index 0000000000..9cafd306b0
--- /dev/null
+++ b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/AntiforgeryTokenTest.cs
@@ -0,0 +1,132 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class AntiforgeryTokenTest
+ {
+ [Fact]
+ public void AdditionalDataProperty()
+ {
+ // Arrange
+ var token = new AntiforgeryToken();
+
+ // Act & assert - 1
+ Assert.Equal("", token.AdditionalData);
+
+ // Act & assert - 2
+ token.AdditionalData = "additional data";
+ Assert.Equal("additional data", token.AdditionalData);
+
+ // Act & assert - 3
+ token.AdditionalData = null;
+ Assert.Equal("", token.AdditionalData);
+ }
+
+ [Fact]
+ public void ClaimUidProperty()
+ {
+ // Arrange
+ var token = new AntiforgeryToken();
+
+ // Act & assert - 1
+ Assert.Null(token.ClaimUid);
+
+ // Act & assert - 2
+ BinaryBlob blob = new BinaryBlob(32);
+ token.ClaimUid = blob;
+ Assert.Equal(blob, token.ClaimUid);
+
+ // Act & assert - 3
+ token.ClaimUid = null;
+ Assert.Null(token.ClaimUid);
+ }
+
+ [Fact]
+ public void IsCookieTokenProperty()
+ {
+ // Arrange
+ var token = new AntiforgeryToken();
+
+ // Act & assert - 1
+ Assert.False(token.IsCookieToken);
+
+ // Act & assert - 2
+ token.IsCookieToken = true;
+ Assert.True(token.IsCookieToken);
+
+ // Act & assert - 3
+ token.IsCookieToken = false;
+ Assert.False(token.IsCookieToken);
+ }
+
+ [Fact]
+ public void UsernameProperty()
+ {
+ // Arrange
+ var token = new AntiforgeryToken();
+
+ // Act & assert - 1
+ Assert.Equal("", token.Username);
+
+ // Act & assert - 2
+ token.Username = "my username";
+ Assert.Equal("my username", token.Username);
+
+ // Act & assert - 3
+ token.Username = null;
+ Assert.Equal("", token.Username);
+ }
+
+ [Fact]
+ public void SecurityTokenProperty_GetsAutopopulated()
+ {
+ // Arrange
+ var token = new AntiforgeryToken();
+
+ // Act
+ var securityToken = token.SecurityToken;
+
+ // Assert
+ Assert.NotNull(securityToken);
+ Assert.Equal(AntiforgeryToken.SecurityTokenBitLength, securityToken.BitLength);
+
+ // check that we're not making a new one each property call
+ Assert.Equal(securityToken, token.SecurityToken);
+ }
+
+ [Fact]
+ public void SecurityTokenProperty_PropertySetter_DoesNotUseDefaults()
+ {
+ // Arrange
+ var token = new AntiforgeryToken();
+
+ // Act
+ var securityToken = new BinaryBlob(64);
+ token.SecurityToken = securityToken;
+
+ // Assert
+ Assert.Equal(securityToken, token.SecurityToken);
+ }
+
+ [Fact]
+ public void SecurityTokenProperty_PropertySetter_DoesNotAllowNulls()
+ {
+ // Arrange
+ var token = new AntiforgeryToken();
+
+ // Act
+ token.SecurityToken = null;
+ var securityToken = token.SecurityToken;
+
+ // Assert
+ Assert.NotNull(securityToken);
+ Assert.Equal(AntiforgeryToken.SecurityTokenBitLength, securityToken.BitLength);
+
+ // check that we're not making a new one each property call
+ Assert.Equal(securityToken, token.SecurityToken);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/BinaryBlobTest.cs b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/BinaryBlobTest.cs
new file mode 100644
index 0000000000..2ab5b12fc1
--- /dev/null
+++ b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/BinaryBlobTest.cs
@@ -0,0 +1,129 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class BinaryBlobTest
+ {
+ [Fact]
+ public void Ctor_BitLength()
+ {
+ // Act
+ var blob = new BinaryBlob(bitLength: 64);
+ var data = blob.GetData();
+
+ // Assert
+ Assert.Equal(64, blob.BitLength);
+ Assert.Equal(64 / 8, data.Length);
+ Assert.NotEqual(new byte[64 / 8], data); // should not be a zero-filled array
+ }
+
+ [Theory]
+ [InlineData(24)]
+ [InlineData(33)]
+ public void Ctor_BitLength_Bad(int bitLength)
+ {
+ // Act & assert
+ var ex = Assert.Throws(() => new BinaryBlob(bitLength));
+ Assert.Equal("bitLength", ex.ParamName);
+ }
+
+ [Fact]
+ public void Ctor_BitLength_ProducesDifferentValues()
+ {
+ // Act
+ var blobA = new BinaryBlob(bitLength: 64);
+ var blobB = new BinaryBlob(bitLength: 64);
+
+ // Assert
+ Assert.NotEqual(blobA.GetData(), blobB.GetData());
+ }
+
+ [Fact]
+ public void Ctor_Data()
+ {
+ // Arrange
+ var expectedData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+
+ // Act
+ var blob = new BinaryBlob(32, expectedData);
+
+ // Assert
+ Assert.Equal(32, blob.BitLength);
+ Assert.Equal(expectedData, blob.GetData());
+ }
+
+ [Theory]
+ [InlineData((object[])null)]
+ [InlineData(new byte[] { 0x01, 0x02, 0x03 })]
+ public void Ctor_Data_Bad(byte[] data)
+ {
+ // Act & assert
+ var ex = Assert.Throws(() => new BinaryBlob(32, data));
+ Assert.Equal("data", ex.ParamName);
+ }
+
+ [Fact]
+ public void Equals_DifferentData_ReturnsFalse()
+ {
+ // Arrange
+ object blobA = new BinaryBlob(32, new byte[] { 0x01, 0x02, 0x03, 0x04 });
+ object blobB = new BinaryBlob(32, new byte[] { 0x04, 0x03, 0x02, 0x01 });
+
+ // Act & assert
+ Assert.NotEqual(blobA, blobB);
+ }
+
+ [Fact]
+ public void Equals_NotABlob_ReturnsFalse()
+ {
+ // Arrange
+ object blobA = new BinaryBlob(32);
+ object blobB = "hello";
+
+ // Act & assert
+ Assert.NotEqual(blobA, blobB);
+ }
+
+ [Fact]
+ public void Equals_Null_ReturnsFalse()
+ {
+ // Arrange
+ object blobA = new BinaryBlob(32);
+ object blobB = null;
+
+ // Act & assert
+ Assert.NotEqual(blobA, blobB);
+ }
+
+ [Fact]
+ public void Equals_SameData_ReturnsTrue()
+ {
+ // Arrange
+ object blobA = new BinaryBlob(32, new byte[] { 0x01, 0x02, 0x03, 0x04 });
+ object blobB = new BinaryBlob(32, new byte[] { 0x01, 0x02, 0x03, 0x04 });
+
+ // Act & assert
+ Assert.Equal(blobA, blobB);
+ }
+
+ [Fact]
+ public void GetHashCodeTest()
+ {
+ // Arrange
+ var blobData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+ var expectedHashCode = BitConverter.ToInt32(blobData, 0);
+
+ var blob = new BinaryBlob(32, blobData);
+
+ // Act
+ var actualHashCode = blob.GetHashCode();
+
+ // Assert
+ Assert.Equal(expectedHashCode, actualHashCode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTest.cs b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTest.cs
new file mode 100644
index 0000000000..faf895d524
--- /dev/null
+++ b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTest.cs
@@ -0,0 +1,1497 @@
+// 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.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Testing;
+using Microsoft.Extensions.Options;
+using Microsoft.Net.Http.Headers;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class DefaultAntiforgeryTest
+ {
+ private const string ResponseCacheHeadersOverrideWarningMessage =
+ "The 'Cache-Control' and 'Pragma' headers have been overridden and set to 'no-cache, no-store' and " +
+ "'no-cache' respectively to prevent caching of this response. Any response that uses antiforgery " +
+ "should not be cached.";
+
+ [Fact]
+ public async Task ChecksSSL_ValidateRequestAsync_Throws()
+ {
+ // Arrange
+ var httpContext = GetHttpContext();
+ var options = new AntiforgeryOptions
+ {
+#pragma warning disable CS0618
+ // obsolete property still forwards to correctly to the new API
+ RequireSsl = true
+#pragma warning restore CS0618
+ };
+ var antiforgery = GetAntiforgery(httpContext, options);
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(
+ () => antiforgery.ValidateRequestAsync(httpContext));
+ Assert.Equal(
+ @"The antiforgery system has the configuration value AntiforgeryOptions.Cookie.SecurePolicy = Always, " +
+ "but the current request is not an SSL request.",
+ exception.Message);
+ }
+
+ [Fact]
+ public async Task ChecksSSL_IsRequestValidAsync_Throws()
+ {
+ // Arrange
+ var httpContext = GetHttpContext();
+ var options = new AntiforgeryOptions()
+ {
+ Cookie = { SecurePolicy = CookieSecurePolicy.Always }
+ };
+
+ var antiforgery = GetAntiforgery(httpContext, options);
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(
+ () => antiforgery.IsRequestValidAsync(httpContext));
+ Assert.Equal(
+ @"The antiforgery system has the configuration value AntiforgeryOptions.Cookie.SecurePolicy = Always, " +
+ "but the current request is not an SSL request.",
+ exception.Message);
+ }
+
+ [Fact]
+ public void ChecksSSL_GetAndStoreTokens_Throws()
+ {
+ // Arrange
+ var httpContext = GetHttpContext();
+ var options = new AntiforgeryOptions()
+ {
+ Cookie = { SecurePolicy = CookieSecurePolicy.Always }
+ };
+
+ var antiforgery = GetAntiforgery(httpContext, options);
+
+ // Act & Assert
+ var exception = Assert.Throws(
+ () => antiforgery.GetAndStoreTokens(httpContext));
+ Assert.Equal(
+ @"The antiforgery system has the configuration value AntiforgeryOptions.Cookie.SecurePolicy = Always, " +
+ "but the current request is not an SSL request.",
+ exception.Message);
+ }
+
+ [Fact]
+ public void ChecksSSL_GetTokens_Throws()
+ {
+ // Arrange
+ var httpContext = GetHttpContext();
+ var options = new AntiforgeryOptions()
+ {
+ Cookie = { SecurePolicy = CookieSecurePolicy.Always }
+ };
+
+ var antiforgery = GetAntiforgery(httpContext, options);
+
+ // Act & Assert
+ var exception = Assert.Throws(
+ () => antiforgery.GetTokens(httpContext));
+ Assert.Equal(
+ @"The antiforgery system has the configuration value AntiforgeryOptions.Cookie.SecurePolicy = Always, " +
+ "but the current request is not an SSL request.",
+ exception.Message);
+ }
+
+ [Fact]
+ public void ChecksSSL_SetCookieTokenAndHeader_Throws()
+ {
+ // Arrange
+ var httpContext = GetHttpContext();
+ var options = new AntiforgeryOptions()
+ {
+ Cookie = { SecurePolicy = CookieSecurePolicy.Always }
+ };
+
+ var antiforgery = GetAntiforgery(httpContext, options);
+
+ // Act & Assert
+ var exception = Assert.Throws(
+ () => antiforgery.SetCookieTokenAndHeader(httpContext));
+ Assert.Equal(
+ @"The antiforgery system has the configuration value AntiforgeryOptions.Cookie.SecurePolicy = Always, " +
+ "but the current request is not an SSL request.",
+ exception.Message);
+ }
+
+ [Fact]
+ public void GetTokens_ExistingInvalidCookieToken_GeneratesANewCookieTokenAndANewFormToken()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature();
+ // Generate a new cookie.
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: false,
+ isOldCookieValid: false,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var tokenset = antiforgery.GetTokens(context.HttpContext);
+
+ // Assert
+ Assert.Equal(context.TestTokenSet.NewCookieTokenString, tokenset.CookieToken);
+ Assert.Equal(context.TestTokenSet.FormTokenString, tokenset.RequestToken);
+
+ Assert.NotNull(antiforgeryFeature);
+ Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
+ Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
+ Assert.True(antiforgeryFeature.HaveGeneratedNewCookieToken);
+ Assert.Equal(context.TestTokenSet.NewCookieToken, antiforgeryFeature.NewCookieToken);
+ Assert.Equal(context.TestTokenSet.NewCookieTokenString, antiforgeryFeature.NewCookieTokenString);
+ Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.NewRequestToken);
+ Assert.Equal(context.TestTokenSet.FormTokenString, antiforgeryFeature.NewRequestTokenString);
+ }
+
+ [Fact]
+ public void GetTokens_ExistingInvalidCookieToken_SwallowsExceptions()
+ {
+ // Arrange
+ // Make sure the existing cookie is invalid.
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: false,
+ isOldCookieValid: false);
+
+ // Exception will cause the cookieToken to be null.
+ context.TokenSerializer
+ .Setup(o => o.Deserialize(context.TestTokenSet.OldCookieTokenString))
+ .Throws(new Exception("should be swallowed"));
+ context.TokenGenerator
+ .Setup(o => o.IsCookieTokenValid(null))
+ .Returns(false);
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var tokenset = antiforgery.GetTokens(context.HttpContext);
+
+ // Assert
+ Assert.Equal(context.TestTokenSet.NewCookieTokenString, tokenset.CookieToken);
+ Assert.Equal(context.TestTokenSet.FormTokenString, tokenset.RequestToken);
+ }
+
+ [Fact]
+ public void GetTokens_ExistingValidCookieToken_GeneratesANewFormToken()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature();
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: true,
+ isOldCookieValid: true,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var tokenset = antiforgery.GetTokens(context.HttpContext);
+
+ // Assert
+ Assert.Null(tokenset.CookieToken);
+ Assert.Equal(context.TestTokenSet.FormTokenString, tokenset.RequestToken);
+
+ Assert.NotNull(antiforgeryFeature);
+ Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
+ Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
+ Assert.True(antiforgeryFeature.HaveGeneratedNewCookieToken);
+ Assert.Null(antiforgeryFeature.NewCookieToken);
+ Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.NewRequestToken);
+ Assert.Equal(context.TestTokenSet.FormTokenString, antiforgeryFeature.NewRequestTokenString);
+ }
+
+ [Fact]
+ public void GetTokens_DoesNotSerializeTwice()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature
+ {
+ HaveDeserializedCookieToken = true,
+ HaveGeneratedNewCookieToken = true,
+ NewRequestToken = new AntiforgeryToken(),
+ NewRequestTokenString = "serialized-form-token-from-context",
+ };
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: true,
+ isOldCookieValid: true,
+ antiforgeryFeature: antiforgeryFeature);
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var tokenset = antiforgery.GetTokens(context.HttpContext);
+
+ // Assert
+ Assert.Null(tokenset.CookieToken);
+ Assert.Equal("serialized-form-token-from-context", tokenset.RequestToken);
+
+ Assert.Null(antiforgeryFeature.NewCookieToken);
+
+ // Token serializer not used.
+ context.TokenSerializer.Verify(
+ o => o.Deserialize(It.IsAny()),
+ Times.Never);
+ context.TokenSerializer.Verify(
+ o => o.Serialize(It.IsAny()),
+ Times.Never);
+ }
+
+ [Fact]
+ public void GetAndStoreTokens_ExistingValidCookieToken_NotOverriden()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature();
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: true,
+ isOldCookieValid: true,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
+
+ // Assert
+ // We shouldn't have saved the cookie because it already existed.
+ context.TokenStore.Verify(
+ t => t.SaveCookieToken(It.IsAny(), It.IsAny()),
+ Times.Never);
+
+ Assert.Null(tokenSet.CookieToken);
+ Assert.Equal(context.TestTokenSet.FormTokenString, tokenSet.RequestToken);
+
+ Assert.NotNull(antiforgeryFeature);
+ Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
+ Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
+ Assert.True(antiforgeryFeature.HaveGeneratedNewCookieToken);
+ Assert.Null(antiforgeryFeature.NewCookieToken);
+ Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.NewRequestToken);
+ Assert.Equal(context.TestTokenSet.FormTokenString, antiforgeryFeature.NewRequestTokenString);
+ }
+
+ [Fact]
+ public void GetAndStoreTokens_ExistingValidCookieToken_NotOverriden_AndSetsDoNotCacheHeaders()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature();
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: true,
+ isOldCookieValid: true,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
+
+ // Assert
+ // We shouldn't have saved the cookie because it already existed.
+ context.TokenStore.Verify(
+ t => t.SaveCookieToken(It.IsAny(), It.IsAny()),
+ Times.Never);
+
+ Assert.Null(tokenSet.CookieToken);
+ Assert.Equal(context.TestTokenSet.FormTokenString, tokenSet.RequestToken);
+
+ Assert.NotNull(antiforgeryFeature);
+ Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
+ Assert.Equal("no-cache, no-store", context.HttpContext.Response.Headers[HeaderNames.CacheControl]);
+ Assert.Equal("no-cache", context.HttpContext.Response.Headers[HeaderNames.Pragma]);
+ }
+
+ [Fact]
+ public void GetAndStoreTokens_ExistingCachingHeaders_Overriden()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature();
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: true,
+ isOldCookieValid: true,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+ context.HttpContext.Response.Headers["Cache-Control"] = "public";
+
+ // Act
+ var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
+
+ // Assert
+ // We shouldn't have saved the cookie because it already existed.
+ context.TokenStore.Verify(
+ t => t.SaveCookieToken(It.IsAny(), It.IsAny()),
+ Times.Never);
+
+ Assert.Null(tokenSet.CookieToken);
+ Assert.Equal(context.TestTokenSet.FormTokenString, tokenSet.RequestToken);
+
+ Assert.NotNull(antiforgeryFeature);
+ Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
+ Assert.Equal("no-cache, no-store", context.HttpContext.Response.Headers[HeaderNames.CacheControl]);
+ Assert.Equal("no-cache", context.HttpContext.Response.Headers[HeaderNames.Pragma]);
+ }
+
+ [Fact]
+ public void GetAndStoreTokens_NoExistingCookieToken_Saved()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature();
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: false,
+ isOldCookieValid: false,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
+
+ // Assert
+ context.TokenStore.Verify(
+ t => t.SaveCookieToken(It.IsAny(), context.TestTokenSet.NewCookieTokenString),
+ Times.Once);
+
+ Assert.Equal(context.TestTokenSet.NewCookieTokenString, tokenSet.CookieToken);
+ Assert.Equal(context.TestTokenSet.FormTokenString, tokenSet.RequestToken);
+
+ Assert.NotNull(antiforgeryFeature);
+ Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
+ Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
+ Assert.True(antiforgeryFeature.HaveGeneratedNewCookieToken);
+ Assert.Equal(context.TestTokenSet.NewCookieToken, antiforgeryFeature.NewCookieToken);
+ Assert.Equal(context.TestTokenSet.NewCookieTokenString, antiforgeryFeature.NewCookieTokenString);
+ Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.NewRequestToken);
+ Assert.Equal(context.TestTokenSet.FormTokenString, antiforgeryFeature.NewRequestTokenString);
+ Assert.True(antiforgeryFeature.HaveStoredNewCookieToken);
+ }
+
+ [Fact]
+ public void GetAndStoreTokens_NoExistingCookieToken_Saved_AndSetsDoNotCacheHeaders()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature();
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: false,
+ isOldCookieValid: false,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
+
+ // Assert
+ context.TokenStore.Verify(
+ t => t.SaveCookieToken(It.IsAny(), context.TestTokenSet.NewCookieTokenString),
+ Times.Once);
+
+ Assert.Equal(context.TestTokenSet.NewCookieTokenString, tokenSet.CookieToken);
+ Assert.Equal(context.TestTokenSet.FormTokenString, tokenSet.RequestToken);
+
+ Assert.NotNull(antiforgeryFeature);
+ Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
+ Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
+ Assert.Equal("no-cache, no-store", context.HttpContext.Response.Headers[HeaderNames.CacheControl]);
+ Assert.Equal("no-cache", context.HttpContext.Response.Headers[HeaderNames.Pragma]);
+ }
+
+ [Fact]
+ public void GetAndStoreTokens_DoesNotSerializeTwice()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature
+ {
+ HaveDeserializedCookieToken = true,
+ HaveGeneratedNewCookieToken = true,
+ NewCookieToken = new AntiforgeryToken(),
+ NewCookieTokenString = "serialized-cookie-token-from-context",
+ NewRequestToken = new AntiforgeryToken(),
+ NewRequestTokenString = "serialized-form-token-from-context",
+ };
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: true,
+ isOldCookieValid: true,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+
+ context.TokenStore
+ .Setup(t => t.SaveCookieToken(context.HttpContext, "serialized-cookie-token-from-context"))
+ .Verifiable();
+
+ // Act
+ var tokenset = antiforgery.GetAndStoreTokens(context.HttpContext);
+
+ // Assert
+ // Token store used once, with expected arguments.
+ // Passed context's cookie token though request's cookie token was valid.
+ context.TokenStore.Verify(
+ t => t.SaveCookieToken(context.HttpContext, "serialized-cookie-token-from-context"),
+ Times.Once);
+
+ // Token serializer not used.
+ context.TokenSerializer.Verify(
+ o => o.Deserialize(It.IsAny()),
+ Times.Never);
+ context.TokenSerializer.Verify(
+ o => o.Serialize(It.IsAny()),
+ Times.Never);
+
+ Assert.Equal("serialized-cookie-token-from-context", tokenset.CookieToken);
+ Assert.Equal("serialized-form-token-from-context", tokenset.RequestToken);
+
+ Assert.True(antiforgeryFeature.HaveStoredNewCookieToken);
+ }
+
+ [Fact]
+ public void GetAndStoreTokens_DoesNotStoreTwice()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature
+ {
+ HaveDeserializedCookieToken = true,
+ HaveGeneratedNewCookieToken = true,
+ HaveStoredNewCookieToken = true,
+ NewCookieToken = new AntiforgeryToken(),
+ NewCookieTokenString = "serialized-cookie-token-from-context",
+ NewRequestToken = new AntiforgeryToken(),
+ NewRequestTokenString = "serialized-form-token-from-context",
+ };
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: true,
+ isOldCookieValid: true,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var tokenset = antiforgery.GetAndStoreTokens(context.HttpContext);
+
+ // Assert
+ // Token store not used.
+ context.TokenStore.Verify(
+ t => t.SaveCookieToken(It.IsAny(), It.IsAny()),
+ Times.Never);
+
+ // Token serializer not used.
+ context.TokenSerializer.Verify(
+ o => o.Deserialize(It.IsAny()),
+ Times.Never);
+ context.TokenSerializer.Verify(
+ o => o.Serialize(It.IsAny()),
+ Times.Never);
+
+ Assert.Equal("serialized-cookie-token-from-context", tokenset.CookieToken);
+ Assert.Equal("serialized-form-token-from-context", tokenset.RequestToken);
+ }
+
+ [Fact]
+ public async Task IsRequestValidAsync_FromStore_Failure()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature();
+ var context = CreateMockContext(new AntiforgeryOptions(), antiforgeryFeature: antiforgeryFeature);
+
+ string message;
+ context.TokenGenerator
+ .Setup(o => o.TryValidateTokenSet(
+ context.HttpContext,
+ context.TestTokenSet.OldCookieToken,
+ context.TestTokenSet.RequestToken,
+ out message))
+ .Returns(false);
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var result = await antiforgery.IsRequestValidAsync(context.HttpContext);
+
+ // Assert
+ Assert.False(result);
+ context.TokenGenerator.Verify();
+
+ // Failed _after_ updating the AntiforgeryContext.
+ Assert.NotNull(antiforgeryFeature);
+ Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
+ Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
+ Assert.True(antiforgeryFeature.HaveDeserializedRequestToken);
+ Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.RequestToken);
+ }
+
+ [Fact]
+ public async Task IsRequestValidAsync_FromStore_Success()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature();
+ var context = CreateMockContext(new AntiforgeryOptions(), antiforgeryFeature: antiforgeryFeature);
+ context.HttpContext.Request.Method = "POST";
+
+ string message;
+ context.TokenGenerator
+ .Setup(o => o.TryValidateTokenSet(
+ context.HttpContext,
+ context.TestTokenSet.OldCookieToken,
+ context.TestTokenSet.RequestToken,
+ out message))
+ .Returns(true)
+ .Verifiable();
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var result = await antiforgery.IsRequestValidAsync(context.HttpContext);
+
+ // Assert
+ Assert.True(result);
+ context.TokenGenerator.Verify();
+
+ Assert.NotNull(antiforgeryFeature);
+ Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
+ Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
+ Assert.True(antiforgeryFeature.HaveDeserializedRequestToken);
+ Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.RequestToken);
+ }
+
+ [Fact]
+ public async Task IsRequestValidAsync_DoesNotDeserializeTwice()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature
+ {
+ HaveDeserializedCookieToken = true,
+ CookieToken = new AntiforgeryToken(),
+ HaveDeserializedRequestToken = true,
+ RequestToken = new AntiforgeryToken(),
+ };
+ var context = CreateMockContext(new AntiforgeryOptions(), antiforgeryFeature: antiforgeryFeature);
+ context.HttpContext.Request.Method = "POST";
+
+ string message;
+ context.TokenGenerator
+ .Setup(o => o.TryValidateTokenSet(
+ context.HttpContext,
+ antiforgeryFeature.CookieToken,
+ antiforgeryFeature.RequestToken,
+ out message))
+ .Returns(true)
+ .Verifiable();
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var result = await antiforgery.IsRequestValidAsync(context.HttpContext);
+
+ // Assert
+ Assert.True(result);
+ context.TokenGenerator.Verify();
+
+ // Token serializer not used.
+ context.TokenSerializer.Verify(
+ o => o.Deserialize(It.IsAny()),
+ Times.Never);
+ context.TokenSerializer.Verify(
+ o => o.Serialize(It.IsAny()),
+ Times.Never);
+ }
+
+ [Theory]
+ [InlineData("GeT")]
+ [InlineData("HEAD")]
+ [InlineData("options")]
+ [InlineData("TrAcE")]
+ public async Task IsRequestValidAsync_SkipsAntiforgery_ForSafeHttpMethods(string httpMethod)
+ {
+ // Arrange
+ var context = CreateMockContext(new AntiforgeryOptions());
+ context.HttpContext.Request.Method = httpMethod;
+
+ string message;
+ context.TokenGenerator
+ .Setup(o => o.TryValidateTokenSet(
+ context.HttpContext,
+ It.IsAny(),
+ It.IsAny(),
+ out message))
+ .Returns(false)
+ .Verifiable();
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var result = await antiforgery.IsRequestValidAsync(context.HttpContext);
+
+ // Assert
+ Assert.True(result);
+ context.TokenGenerator
+ .Verify(o => o.TryValidateTokenSet(
+ context.HttpContext,
+ It.IsAny(),
+ It.IsAny(),
+ out message),
+ Times.Never);
+ }
+
+ [Theory]
+ [InlineData("PUT")]
+ [InlineData("post")]
+ [InlineData("Delete")]
+ [InlineData("Custom")]
+ public async Task IsRequestValidAsync_ValidatesAntiforgery_ForNonSafeHttpMethods(string httpMethod)
+ {
+ // Arrange
+ var context = CreateMockContext(new AntiforgeryOptions());
+ context.HttpContext.Request.Method = httpMethod;
+
+ string message;
+ context.TokenGenerator
+ .Setup(o => o.TryValidateTokenSet(
+ context.HttpContext,
+ It.IsAny(),
+ It.IsAny(),
+ out message))
+ .Returns(true)
+ .Verifiable();
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var result = await antiforgery.IsRequestValidAsync(context.HttpContext);
+
+ // Assert
+ Assert.True(result);
+ context.TokenGenerator.Verify();
+ }
+
+ [Fact]
+ public async Task ValidateRequestAsync_FromStore_Failure()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature();
+ var context = CreateMockContext(new AntiforgeryOptions(), antiforgeryFeature: antiforgeryFeature);
+
+ var message = "my-message";
+ context.TokenGenerator
+ .Setup(o => o.TryValidateTokenSet(
+ context.HttpContext,
+ context.TestTokenSet.OldCookieToken,
+ context.TestTokenSet.RequestToken,
+ out message))
+ .Returns(false)
+ .Verifiable();
+ var antiforgery = GetAntiforgery(context);
+
+ // Act & assert
+ var exception = await Assert.ThrowsAsync(
+ () => antiforgery.ValidateRequestAsync(context.HttpContext));
+ Assert.Equal("my-message", exception.Message);
+ context.TokenGenerator.Verify();
+
+ // Failed _after_ updating the AntiforgeryContext.
+ Assert.NotNull(antiforgeryFeature);
+ Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
+ Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
+ Assert.True(antiforgeryFeature.HaveDeserializedRequestToken);
+ Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.RequestToken);
+ }
+
+ [Fact]
+ public async Task ValidateRequestAsync_FromStore_Success()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature();
+ var context = CreateMockContext(new AntiforgeryOptions(), antiforgeryFeature: antiforgeryFeature);
+
+ string message;
+ context.TokenGenerator
+ .Setup(o => o.TryValidateTokenSet(
+ context.HttpContext,
+ context.TestTokenSet.OldCookieToken,
+ context.TestTokenSet.RequestToken,
+ out message))
+ .Returns(true)
+ .Verifiable();
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ await antiforgery.ValidateRequestAsync(context.HttpContext);
+
+ // Assert
+ context.TokenGenerator.Verify();
+
+ Assert.NotNull(antiforgeryFeature);
+ Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
+ Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
+ Assert.True(antiforgeryFeature.HaveDeserializedRequestToken);
+ Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.RequestToken);
+ }
+
+ [Fact]
+ public async Task ValidateRequestAsync_NoCookieToken_Throws()
+ {
+ // Arrange
+ var context = CreateMockContext(new AntiforgeryOptions()
+ {
+ Cookie = { Name = "cookie-name" },
+ FormFieldName = "form-field-name",
+ HeaderName = null,
+ });
+
+ var tokenSet = new AntiforgeryTokenSet(null, null, "form-field-name", null);
+ context.TokenStore
+ .Setup(s => s.GetRequestTokensAsync(context.HttpContext))
+ .Returns(Task.FromResult(tokenSet));
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(
+ () => antiforgery.ValidateRequestAsync(context.HttpContext));
+ Assert.Equal("The required antiforgery cookie \"cookie-name\" is not present.", exception.Message);
+ }
+
+ [Fact]
+ public async Task ValidateRequestAsync_NonFormRequest_HeaderDisabled_Throws()
+ {
+ // Arrange
+ var context = CreateMockContext(new AntiforgeryOptions()
+ {
+ Cookie = { Name = "cookie-name" },
+ FormFieldName = "form-field-name",
+ HeaderName = null,
+ });
+
+ var tokenSet = new AntiforgeryTokenSet(null, "cookie-token", "form-field-name", null);
+ context.TokenStore
+ .Setup(s => s.GetRequestTokensAsync(context.HttpContext))
+ .Returns(Task.FromResult(tokenSet));
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(
+ () => antiforgery.ValidateRequestAsync(context.HttpContext));
+ Assert.Equal("The required antiforgery form field \"form-field-name\" is not present.", exception.Message);
+ }
+
+ [Fact]
+ public async Task ValidateRequestAsync_NonFormRequest_NoHeaderValue_Throws()
+ {
+ // Arrange
+ var context = CreateMockContext(new AntiforgeryOptions()
+ {
+ Cookie = { Name = "cookie-name" },
+ FormFieldName = "form-field-name",
+ HeaderName = "header-name",
+ });
+
+ context.HttpContext.Request.ContentType = "application/json";
+
+ var tokenSet = new AntiforgeryTokenSet(null, "cookie-token", "form-field-name", "header-name");
+ context.TokenStore
+ .Setup(s => s.GetRequestTokensAsync(context.HttpContext))
+ .Returns(Task.FromResult(tokenSet));
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(
+ () => antiforgery.ValidateRequestAsync(context.HttpContext));
+ Assert.Equal("The required antiforgery header value \"header-name\" is not present.", exception.Message);
+ }
+
+ [Fact]
+ public async Task ValidateRequestAsync_FormRequest_NoRequestTokenValue_Throws()
+ {
+ // Arrange
+ var context = CreateMockContext(new AntiforgeryOptions()
+ {
+ Cookie = { Name = "cookie-name" },
+ FormFieldName = "form-field-name",
+ HeaderName = "header-name",
+ });
+
+ context.HttpContext.Request.ContentType = "application/x-www-form-urlencoded";
+
+ var tokenSet = new AntiforgeryTokenSet(null, "cookie-token", "form-field-name", "header-name");
+ context.TokenStore
+ .Setup(s => s.GetRequestTokensAsync(context.HttpContext))
+ .Returns(Task.FromResult(tokenSet));
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(
+ () => antiforgery.ValidateRequestAsync(context.HttpContext));
+ Assert.Equal(
+ "The required antiforgery request token was not provided in either form field \"form-field-name\" " +
+ "or header value \"header-name\".",
+ exception.Message);
+ }
+
+ [Fact]
+ public async Task ValidateRequestAsync_DoesNotDeserializeTwice()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature
+ {
+ HaveDeserializedCookieToken = true,
+ CookieToken = new AntiforgeryToken(),
+ HaveDeserializedRequestToken = true,
+ RequestToken = new AntiforgeryToken(),
+ };
+ var context = CreateMockContext(new AntiforgeryOptions(), antiforgeryFeature: antiforgeryFeature);
+
+ string message;
+ context.TokenGenerator
+ .Setup(o => o.TryValidateTokenSet(
+ context.HttpContext,
+ antiforgeryFeature.CookieToken,
+ antiforgeryFeature.RequestToken,
+ out message))
+ .Returns(true)
+ .Verifiable();
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ await antiforgery.ValidateRequestAsync(context.HttpContext);
+
+ // Assert (does not throw)
+ context.TokenGenerator.Verify();
+
+ // Token serializer not used.
+ context.TokenSerializer.Verify(
+ o => o.Deserialize(It.IsAny()),
+ Times.Never);
+ context.TokenSerializer.Verify(
+ o => o.Serialize(It.IsAny()),
+ Times.Never);
+ }
+
+ [Fact]
+ public void SetCookieTokenAndHeader_PreserveXFrameOptionsHeader()
+ {
+ // Arrange
+ var options = new AntiforgeryOptions();
+ var antiforgeryFeature = new AntiforgeryFeature();
+ var expectedHeaderValue = "DIFFERENTORIGIN";
+
+ // Generate a new cookie.
+ var context = CreateMockContext(
+ options,
+ useOldCookie: false,
+ isOldCookieValid: false,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+ context.HttpContext.Response.Headers["X-Frame-Options"] = expectedHeaderValue;
+
+ // Act
+ antiforgery.SetCookieTokenAndHeader(context.HttpContext);
+
+ // Assert
+ var xFrameOptions = context.HttpContext.Response.Headers["X-Frame-Options"];
+ Assert.Equal(expectedHeaderValue, xFrameOptions);
+ }
+
+ [Fact]
+ public void SetCookieTokenAndHeader_NewCookieToken_SetsDoNotCacheHeaders()
+ {
+ // Arrange
+ var options = new AntiforgeryOptions();
+ var antiforgeryFeature = new AntiforgeryFeature();
+
+ // Generate a new cookie.
+ var context = CreateMockContext(
+ options,
+ useOldCookie: false,
+ isOldCookieValid: false,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ antiforgery.SetCookieTokenAndHeader(context.HttpContext);
+
+ // Assert
+ Assert.Equal("no-cache, no-store", context.HttpContext.Response.Headers["Cache-Control"]);
+ Assert.Equal("no-cache", context.HttpContext.Response.Headers["Pragma"]);
+ }
+
+ [Fact]
+ public void SetCookieTokenAndHeader_ValidOldCookieToken_SetsDoNotCacheHeaders()
+ {
+ // Arrange
+ var options = new AntiforgeryOptions();
+ var antiforgeryFeature = new AntiforgeryFeature();
+
+ // Generate a new cookie.
+ var context = CreateMockContext(
+ options,
+ useOldCookie: true,
+ isOldCookieValid: true,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ antiforgery.SetCookieTokenAndHeader(context.HttpContext);
+
+ // Assert
+ Assert.Equal("no-cache, no-store", context.HttpContext.Response.Headers["Cache-Control"]);
+ Assert.Equal("no-cache", context.HttpContext.Response.Headers["Pragma"]);
+ }
+
+ [Fact]
+ public void SetCookieTokenAndHeader_OverridesExistingCachingHeaders()
+ {
+ // Arrange
+ var options = new AntiforgeryOptions();
+ var antiforgeryFeature = new AntiforgeryFeature();
+
+ // Generate a new cookie.
+ var context = CreateMockContext(
+ options,
+ useOldCookie: true,
+ isOldCookieValid: true,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+ context.HttpContext.Response.Headers["Cache-Control"] = "public";
+
+ // Act
+ antiforgery.SetCookieTokenAndHeader(context.HttpContext);
+
+ // Assert
+ Assert.Equal("no-cache, no-store", context.HttpContext.Response.Headers["Cache-Control"]);
+ Assert.Equal("no-cache", context.HttpContext.Response.Headers["Pragma"]);
+ }
+
+ [Theory]
+ [InlineData(false, "SAMEORIGIN")]
+ [InlineData(true, null)]
+ public void SetCookieTokenAndHeader_AddsXFrameOptionsHeader(
+ bool suppressXFrameOptions,
+ string expectedHeaderValue)
+ {
+ // Arrange
+ var options = new AntiforgeryOptions()
+ {
+ SuppressXFrameOptionsHeader = suppressXFrameOptions
+ };
+ var antiforgeryFeature = new AntiforgeryFeature();
+
+ // Generate a new cookie.
+ var context = CreateMockContext(
+ options,
+ useOldCookie: false,
+ isOldCookieValid: false,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ antiforgery.SetCookieTokenAndHeader(context.HttpContext);
+
+ // Assert
+ var xFrameOptions = context.HttpContext.Response.Headers["X-Frame-Options"];
+ Assert.Equal(expectedHeaderValue, xFrameOptions);
+
+ Assert.NotNull(antiforgeryFeature);
+ Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
+ Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
+ Assert.True(antiforgeryFeature.HaveGeneratedNewCookieToken);
+ Assert.Equal(context.TestTokenSet.NewCookieToken, antiforgeryFeature.NewCookieToken);
+ Assert.Equal(context.TestTokenSet.NewCookieTokenString, antiforgeryFeature.NewCookieTokenString);
+ Assert.True(antiforgeryFeature.HaveStoredNewCookieToken);
+ }
+
+ [Fact]
+ public void SetCookieTokenAndHeader_DoesNotDeserializeTwice()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature
+ {
+ HaveDeserializedCookieToken = true,
+ HaveGeneratedNewCookieToken = true,
+ NewCookieToken = new AntiforgeryToken(),
+ NewCookieTokenString = "serialized-cookie-token-from-context",
+ NewRequestToken = new AntiforgeryToken(),
+ NewRequestTokenString = "serialized-form-token-from-context",
+ };
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: true,
+ isOldCookieValid: true,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+
+ context.TokenStore
+ .Setup(t => t.SaveCookieToken(context.HttpContext, "serialized-cookie-token-from-context"))
+ .Verifiable();
+
+ // Act
+ antiforgery.SetCookieTokenAndHeader(context.HttpContext);
+
+ // Assert
+ // Token store used once, with expected arguments.
+ // Passed context's cookie token though request's cookie token was valid.
+ context.TokenStore.Verify(
+ t => t.SaveCookieToken(context.HttpContext, "serialized-cookie-token-from-context"),
+ Times.Once);
+
+ // Token serializer not used.
+ context.TokenSerializer.Verify(
+ o => o.Deserialize(It.IsAny()),
+ Times.Never);
+ context.TokenSerializer.Verify(
+ o => o.Serialize(It.IsAny()),
+ Times.Never);
+ }
+
+ [Fact]
+ public void SetCookieTokenAndHeader_DoesNotStoreTwice()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature
+ {
+ HaveDeserializedCookieToken = true,
+ HaveGeneratedNewCookieToken = true,
+ HaveStoredNewCookieToken = true,
+ NewCookieToken = new AntiforgeryToken(),
+ NewCookieTokenString = "serialized-cookie-token-from-context",
+ NewRequestToken = new AntiforgeryToken(),
+ NewRequestTokenString = "serialized-form-token-from-context",
+ };
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: true,
+ isOldCookieValid: true,
+ antiforgeryFeature: antiforgeryFeature);
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ antiforgery.SetCookieTokenAndHeader(context.HttpContext);
+
+ // Assert
+ // Token serializer not used.
+ context.TokenSerializer.Verify(
+ o => o.Deserialize(It.IsAny()),
+ Times.Never);
+ context.TokenSerializer.Verify(
+ o => o.Serialize(It.IsAny()),
+ Times.Never);
+
+ // Token store not used.
+ context.TokenStore.Verify(
+ t => t.SaveCookieToken(It.IsAny(), It.IsAny()),
+ Times.Never);
+ }
+
+ [Fact]
+ public void SetCookieTokenAndHeader_NullCookieToken()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature
+ {
+ HaveDeserializedCookieToken = false,
+ HaveGeneratedNewCookieToken = false,
+ HaveStoredNewCookieToken = true,
+ NewCookieToken = new AntiforgeryToken(),
+ NewCookieTokenString = "serialized-cookie-token-from-context",
+ NewRequestToken = new AntiforgeryToken(),
+ NewRequestTokenString = "serialized-form-token-from-context",
+ };
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: false,
+ isOldCookieValid: false,
+ antiforgeryFeature: antiforgeryFeature);
+ var testTokenSet = new TestTokenSet
+ {
+ OldCookieTokenString = null
+ };
+
+ var nullTokenStore = GetTokenStore(context.HttpContext, testTokenSet, false);
+ var antiforgery = GetAntiforgery(
+ context.HttpContext,
+ tokenGenerator: context.TokenGenerator.Object,
+ tokenStore: nullTokenStore.Object);
+
+ // Act
+ antiforgery.SetCookieTokenAndHeader(context.HttpContext);
+
+ // Assert
+ context.TokenSerializer.Verify(s => s.Deserialize(null), Times.Never);
+ }
+
+ [Fact]
+ public void SetCookieTokenAndHeader_DoesNotModifyHeadersAfterResponseHasStarted()
+ {
+ // Arrange
+ var antiforgeryFeature = new AntiforgeryFeature
+ {
+ HaveDeserializedCookieToken = false,
+ HaveGeneratedNewCookieToken = false,
+ HaveStoredNewCookieToken = true,
+ NewCookieToken = new AntiforgeryToken(),
+ NewCookieTokenString = "serialized-cookie-token-from-context",
+ NewRequestToken = new AntiforgeryToken(),
+ NewRequestTokenString = "serialized-form-token-from-context",
+ };
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: false,
+ isOldCookieValid: false,
+ antiforgeryFeature: antiforgeryFeature);
+ var testTokenSet = new TestTokenSet
+ {
+ OldCookieTokenString = null
+ };
+
+ var nullTokenStore = GetTokenStore(context.HttpContext, testTokenSet, false);
+ var antiforgery = GetAntiforgery(
+ context.HttpContext,
+ tokenGenerator: context.TokenGenerator.Object,
+ tokenStore: nullTokenStore.Object);
+
+ TestResponseFeature testResponse = new TestResponseFeature();
+ context.HttpContext.Features.Set(testResponse);
+ context.HttpContext.Response.Headers["Cache-Control"] = "public";
+ testResponse.StartResponse();
+
+ // Act
+ antiforgery.SetCookieTokenAndHeader(context.HttpContext);
+
+ Assert.Equal("public", context.HttpContext.Response.Headers["Cache-Control"]);
+ }
+
+ [Fact]
+ public void GetAndStoreTokens_DoesNotLogWarning_IfNoExistingCacheHeadersPresent()
+ {
+ // Arrange
+ var testSink = new TestSink();
+ var loggerFactory = new Mock();
+ loggerFactory
+ .Setup(lf => lf.CreateLogger(typeof(DefaultAntiforgery).FullName))
+ .Returns(new TestLogger("test logger", testSink, enabled: true));
+ var services = new ServiceCollection();
+ services.AddSingleton(loggerFactory.Object);
+ var antiforgeryFeature = new AntiforgeryFeature();
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: false,
+ isOldCookieValid: false,
+ antiforgeryFeature: antiforgeryFeature);
+ context.HttpContext.RequestServices = services.BuildServiceProvider();
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
+
+ // Assert
+ var hasWarningMessage = testSink.Writes
+ .Where(wc => wc.LogLevel == LogLevel.Warning)
+ .Select(wc => wc.State?.ToString())
+ .Contains(ResponseCacheHeadersOverrideWarningMessage);
+ Assert.False(hasWarningMessage);
+ }
+
+ [Theory]
+ [InlineData("Cache-Control", "Public")]
+ [InlineData("Cache-Control", "PuBlic")]
+ [InlineData("Cache-Control", "Private")]
+ [InlineData("Cache-Control", "PriVate")]
+ [InlineData("Cache-Control", "No-Store")]
+ [InlineData("Cache-Control", "No-store")]
+ [InlineData("Pragma", "Foo")]
+ public void GetAndStoreTokens_LogsWarning_NonNoCacheHeadersAlreadyPresent(string headerName, string headerValue)
+ {
+ // Arrange
+ var testSink = new TestSink();
+ var loggerFactory = new Mock();
+ loggerFactory
+ .Setup(lf => lf.CreateLogger(typeof(DefaultAntiforgery).FullName))
+ .Returns(new TestLogger("test logger", testSink, enabled: true));
+ var services = new ServiceCollection();
+ services.AddSingleton(loggerFactory.Object);
+ var antiforgeryFeature = new AntiforgeryFeature();
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: false,
+ isOldCookieValid: false,
+ antiforgeryFeature: antiforgeryFeature);
+ context.HttpContext.RequestServices = services.BuildServiceProvider();
+ var antiforgery = GetAntiforgery(context);
+ context.HttpContext.Response.Headers[headerName] = headerValue;
+
+ // Act
+ var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
+
+ // Assert
+ var hasWarningMessage = testSink.Writes
+ .Where(wc => wc.LogLevel == LogLevel.Warning)
+ .Select(wc => wc.State?.ToString())
+ .Contains(ResponseCacheHeadersOverrideWarningMessage);
+ Assert.True(hasWarningMessage);
+ }
+
+ [Theory]
+ [InlineData("Cache-Control", "no-cache")]
+ [InlineData("Pragma", "no-cache")]
+ public void GetAndStoreTokens_DoesNotLogsWarning_ForNoCacheHeaders_AlreadyPresent(string headerName, string headerValue)
+ {
+ // Arrange
+ var testSink = new TestSink();
+ var loggerFactory = new Mock();
+ loggerFactory
+ .Setup(lf => lf.CreateLogger(typeof(DefaultAntiforgery).FullName))
+ .Returns(new TestLogger("test logger", testSink, enabled: true));
+ var services = new ServiceCollection();
+ services.AddSingleton(loggerFactory.Object);
+ var antiforgeryFeature = new AntiforgeryFeature();
+ var context = CreateMockContext(
+ new AntiforgeryOptions(),
+ useOldCookie: false,
+ isOldCookieValid: false,
+ antiforgeryFeature: antiforgeryFeature);
+ context.HttpContext.RequestServices = services.BuildServiceProvider();
+ var antiforgery = GetAntiforgery(context);
+ context.HttpContext.Response.Headers[headerName] = headerValue;
+
+ // Act
+ var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
+
+ // Assert
+ var hasWarningMessage = testSink.Writes
+ .Where(wc => wc.LogLevel == LogLevel.Warning)
+ .Select(wc => wc.State?.ToString())
+ .Contains(ResponseCacheHeadersOverrideWarningMessage);
+ Assert.False(hasWarningMessage);
+ }
+
+ private DefaultAntiforgery GetAntiforgery(
+ HttpContext httpContext,
+ AntiforgeryOptions options = null,
+ IAntiforgeryTokenGenerator tokenGenerator = null,
+ IAntiforgeryTokenSerializer tokenSerializer = null,
+ IAntiforgeryTokenStore tokenStore = null)
+ {
+ var optionsManager = new TestOptionsManager();
+ if (options != null)
+ {
+ optionsManager.Value = options;
+ }
+
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ return new DefaultAntiforgery(
+ antiforgeryOptionsAccessor: optionsManager,
+ tokenGenerator: tokenGenerator,
+ tokenSerializer: tokenSerializer,
+ tokenStore: tokenStore,
+ loggerFactory: loggerFactory);
+ }
+
+ private IServiceProvider GetServices()
+ {
+ var builder = new ServiceCollection();
+ builder.AddSingleton(new LoggerFactory());
+
+ return builder.BuildServiceProvider();
+ }
+
+ private HttpContext GetHttpContext(IAntiforgeryFeature antiforgeryFeature = null)
+ {
+ var httpContext = new DefaultHttpContext();
+ antiforgeryFeature = antiforgeryFeature ?? new AntiforgeryFeature();
+ httpContext.Features.Set(antiforgeryFeature);
+ httpContext.RequestServices = GetServices();
+ httpContext.User = new ClaimsPrincipal(new ClaimsIdentity("some-auth"));
+
+ return httpContext;
+ }
+
+ private DefaultAntiforgery GetAntiforgery(AntiforgeryMockContext context)
+ {
+ return GetAntiforgery(
+ context.HttpContext,
+ context.Options,
+ context.TokenGenerator?.Object,
+ context.TokenSerializer?.Object,
+ context.TokenStore?.Object);
+ }
+
+ private Mock GetTokenStore(
+ HttpContext context,
+ TestTokenSet testTokenSet,
+ bool saveNewCookie = true)
+ {
+ var oldCookieToken = testTokenSet.OldCookieTokenString;
+ var formToken = testTokenSet.FormTokenString;
+ var mockTokenStore = new Mock(MockBehavior.Strict);
+ mockTokenStore
+ .Setup(o => o.GetCookieToken(context))
+ .Returns(oldCookieToken);
+
+ mockTokenStore
+ .Setup(o => o.GetRequestTokensAsync(context))
+ .Returns(() => Task.FromResult(new AntiforgeryTokenSet(
+ formToken,
+ oldCookieToken,
+ "form",
+ "header")));
+
+ if (saveNewCookie)
+ {
+ var newCookieToken = testTokenSet.NewCookieTokenString;
+ mockTokenStore
+ .Setup(o => o.SaveCookieToken(context, newCookieToken))
+ .Verifiable();
+ }
+
+ return mockTokenStore;
+ }
+
+ private Mock GetTokenSerializer(TestTokenSet testTokenSet)
+ {
+ var oldCookieToken = testTokenSet.OldCookieToken;
+ var newCookieToken = testTokenSet.NewCookieToken;
+ var formToken = testTokenSet.RequestToken;
+ var mockSerializer = new Mock(MockBehavior.Strict);
+ mockSerializer.Setup(o => o.Serialize(formToken))
+ .Returns(testTokenSet.FormTokenString);
+ mockSerializer.Setup(o => o.Deserialize(testTokenSet.FormTokenString))
+ .Returns(formToken);
+ mockSerializer.Setup(o => o.Deserialize(testTokenSet.OldCookieTokenString))
+ .Returns(oldCookieToken);
+ mockSerializer.Setup(o => o.Serialize(oldCookieToken))
+ .Returns(testTokenSet.OldCookieTokenString);
+ mockSerializer.Setup(o => o.Serialize(newCookieToken))
+ .Returns(testTokenSet.NewCookieTokenString);
+ return mockSerializer;
+ }
+
+ private AntiforgeryMockContext CreateMockContext(
+ AntiforgeryOptions options,
+ bool useOldCookie = false,
+ bool isOldCookieValid = true,
+ IAntiforgeryFeature antiforgeryFeature = null)
+ {
+ // Arrange
+ var httpContext = GetHttpContext(antiforgeryFeature);
+ var testTokenSet = GetTokenSet();
+
+ var mockSerializer = GetTokenSerializer(testTokenSet);
+
+ var mockTokenStore = GetTokenStore(httpContext, testTokenSet, !useOldCookie);
+
+ var mockGenerator = new Mock(MockBehavior.Strict);
+ mockGenerator
+ .Setup(o => o.GenerateRequestToken(
+ httpContext,
+ useOldCookie ? testTokenSet.OldCookieToken : testTokenSet.NewCookieToken))
+ .Returns(testTokenSet.RequestToken);
+
+ mockGenerator
+ .Setup(o => o.GenerateCookieToken())
+ .Returns(useOldCookie ? testTokenSet.OldCookieToken : testTokenSet.NewCookieToken);
+ mockGenerator
+ .Setup(o => o.IsCookieTokenValid(null))
+ .Returns(false);
+ mockGenerator
+ .Setup(o => o.IsCookieTokenValid(testTokenSet.OldCookieToken))
+ .Returns(isOldCookieValid);
+
+ mockGenerator
+ .Setup(o => o.IsCookieTokenValid(testTokenSet.NewCookieToken))
+ .Returns(!isOldCookieValid);
+
+ return new AntiforgeryMockContext()
+ {
+ Options = options,
+ HttpContext = httpContext,
+ TokenGenerator = mockGenerator,
+ TokenSerializer = mockSerializer,
+ TokenStore = mockTokenStore,
+ TestTokenSet = testTokenSet
+ };
+ }
+
+ private TestTokenSet GetTokenSet()
+ {
+ return new TestTokenSet()
+ {
+ RequestToken = new AntiforgeryToken() { IsCookieToken = false },
+ FormTokenString = "serialized-form-token",
+ OldCookieToken = new AntiforgeryToken() { IsCookieToken = true },
+ OldCookieTokenString = "serialized-old-cookie-token",
+ NewCookieToken = new AntiforgeryToken() { IsCookieToken = true },
+ NewCookieTokenString = "serialized-new-cookie-token",
+ };
+ }
+
+ private class TestTokenSet
+ {
+ public AntiforgeryToken RequestToken { get; set; }
+
+ public string FormTokenString { get; set; }
+
+ public AntiforgeryToken OldCookieToken { get; set; }
+
+ public string OldCookieTokenString { get; set; }
+
+ public AntiforgeryToken NewCookieToken { get; set; }
+
+ public string NewCookieTokenString { get; set; }
+ }
+
+ private class AntiforgeryMockContext
+ {
+ public AntiforgeryOptions Options { get; set; }
+
+ public TestTokenSet TestTokenSet { get; set; }
+
+ public HttpContext HttpContext { get; set; }
+
+ public Mock TokenGenerator { get; set; }
+
+ public Mock TokenStore { get; set; }
+
+ public Mock TokenSerializer { get; set; }
+ }
+
+ private class TestOptionsManager : IOptions
+ {
+ public AntiforgeryOptions Value { get; set; } = new AntiforgeryOptions();
+ }
+
+ private class TestResponseFeature : HttpResponseFeature
+ {
+ private bool _hasStarted = false;
+
+ public override bool HasStarted { get => _hasStarted; }
+
+ public TestResponseFeature()
+ {
+ }
+
+ public void StartResponse()
+ {
+ _hasStarted = true;
+ }
+ }
+ }
+}
diff --git a/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenGeneratorTest.cs b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenGeneratorTest.cs
new file mode 100644
index 0000000000..981de8e94c
--- /dev/null
+++ b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenGeneratorTest.cs
@@ -0,0 +1,628 @@
+// 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.Security.Claims;
+using System.Security.Cryptography;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Testing;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class DefaultAntiforgeryTokenGeneratorProviderTest
+ {
+ [Fact]
+ public void GenerateCookieToken()
+ {
+ // Arrange
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: null,
+ additionalDataProvider: null);
+
+ // Act
+ var token = tokenProvider.GenerateCookieToken();
+
+ // Assert
+ Assert.NotNull(token);
+ }
+
+ [Fact]
+ public void GenerateRequestToken_InvalidCookieToken()
+ {
+ // Arrange
+ var cookieToken = new AntiforgeryToken() { IsCookieToken = false };
+ var httpContext = new DefaultHttpContext();
+ httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
+ Assert.False(httpContext.User.Identity.IsAuthenticated);
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: null,
+ additionalDataProvider: null);
+
+ // Act & Assert
+ ExceptionAssert.ThrowsArgument(
+ () => tokenProvider.GenerateRequestToken(httpContext, cookieToken),
+ "cookieToken",
+ "The antiforgery cookie token is invalid.");
+ }
+
+ [Fact]
+ public void GenerateRequestToken_AnonymousUser()
+ {
+ // Arrange
+ var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
+ var httpContext = new DefaultHttpContext();
+ httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
+ Assert.False(httpContext.User.Identity.IsAuthenticated);
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: null,
+ additionalDataProvider: null);
+
+ // Act
+ var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
+
+ // Assert
+ Assert.NotNull(fieldToken);
+ Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
+ Assert.False(fieldToken.IsCookieToken);
+ Assert.Empty(fieldToken.Username);
+ Assert.Null(fieldToken.ClaimUid);
+ Assert.Empty(fieldToken.AdditionalData);
+ }
+
+ [Fact]
+ public void GenerateRequestToken_AuthenticatedWithoutUsernameAndNoAdditionalData_NoAdditionalData()
+ {
+ // Arrange
+ var cookieToken = new AntiforgeryToken()
+ {
+ IsCookieToken = true
+ };
+
+ var httpContext = new DefaultHttpContext();
+ httpContext.User = new ClaimsPrincipal(new MyAuthenticatedIdentityWithoutUsername());
+
+ var options = new AntiforgeryOptions();
+ var claimUidExtractor = new Mock().Object;
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: claimUidExtractor,
+ additionalDataProvider: null);
+
+ // Act & assert
+ var exception = Assert.Throws(
+ () => tokenProvider.GenerateRequestToken(httpContext, cookieToken));
+ Assert.Equal(
+ "The provided identity of type " +
+ $"'{typeof(MyAuthenticatedIdentityWithoutUsername).FullName}' " +
+ "is marked IsAuthenticated = true but does not have a value for Name. " +
+ "By default, the antiforgery system requires that all authenticated identities have a unique Name. " +
+ "If it is not possible to provide a unique Name for this identity, " +
+ "consider extending IAntiforgeryAdditionalDataProvider by overriding the " +
+ "DefaultAntiforgeryAdditionalDataProvider " +
+ "or a custom type that can provide some form of unique identifier for the current user.",
+ exception.Message);
+ }
+
+ [Fact]
+ public void GenerateRequestToken_AuthenticatedWithoutUsername_WithAdditionalData()
+ {
+ // Arrange
+ var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
+
+ var httpContext = new DefaultHttpContext();
+ httpContext.User = new ClaimsPrincipal(new MyAuthenticatedIdentityWithoutUsername());
+
+ var mockAdditionalDataProvider = new Mock();
+ mockAdditionalDataProvider.Setup(o => o.GetAdditionalData(httpContext))
+ .Returns("additional-data");
+
+ var claimUidExtractor = new Mock().Object;
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: claimUidExtractor,
+ additionalDataProvider: mockAdditionalDataProvider.Object);
+
+ // Act
+ var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
+
+ // Assert
+ Assert.NotNull(fieldToken);
+ Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
+ Assert.False(fieldToken.IsCookieToken);
+ Assert.Empty(fieldToken.Username);
+ Assert.Null(fieldToken.ClaimUid);
+ Assert.Equal("additional-data", fieldToken.AdditionalData);
+ }
+
+ [Fact]
+ public void GenerateRequestToken_ClaimsBasedIdentity()
+ {
+ // Arrange
+ var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
+
+ var identity = GetAuthenticatedIdentity("some-identity");
+ var httpContext = new DefaultHttpContext();
+ httpContext.User = new ClaimsPrincipal(identity);
+
+ byte[] data = new byte[256 / 8];
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(data);
+ }
+ var base64ClaimUId = Convert.ToBase64String(data);
+ var expectedClaimUid = new BinaryBlob(256, data);
+
+ var mockClaimUidExtractor = new Mock();
+ mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(It.Is(c => c.Identity == identity)))
+ .Returns(base64ClaimUId);
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: mockClaimUidExtractor.Object,
+ additionalDataProvider: null);
+
+ // Act
+ var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
+
+ // Assert
+ Assert.NotNull(fieldToken);
+ Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
+ Assert.False(fieldToken.IsCookieToken);
+ Assert.Equal("", fieldToken.Username);
+ Assert.Equal(expectedClaimUid, fieldToken.ClaimUid);
+ Assert.Equal("", fieldToken.AdditionalData);
+ }
+
+ [Fact]
+ public void GenerateRequestToken_RegularUserWithUsername()
+ {
+ // Arrange
+ var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
+
+ var httpContext = new DefaultHttpContext();
+ var mockIdentity = new Mock();
+ mockIdentity.Setup(o => o.IsAuthenticated)
+ .Returns(true);
+ mockIdentity.Setup(o => o.Name)
+ .Returns("my-username");
+
+ httpContext.User = new ClaimsPrincipal(mockIdentity.Object);
+
+ var claimUidExtractor = new Mock().Object;
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: claimUidExtractor,
+ additionalDataProvider: null);
+
+ // Act
+ var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
+
+ // Assert
+ Assert.NotNull(fieldToken);
+ Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
+ Assert.False(fieldToken.IsCookieToken);
+ Assert.Equal("my-username", fieldToken.Username);
+ Assert.Null(fieldToken.ClaimUid);
+ Assert.Empty(fieldToken.AdditionalData);
+ }
+
+ [Fact]
+ public void IsCookieTokenValid_FieldToken_ReturnsFalse()
+ {
+ // Arrange
+ var cookieToken = new AntiforgeryToken()
+ {
+ IsCookieToken = false
+ };
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: null,
+ additionalDataProvider: null);
+
+ // Act
+ var isValid = tokenProvider.IsCookieTokenValid(cookieToken);
+
+ // Assert
+ Assert.False(isValid);
+ }
+
+ [Fact]
+ public void IsCookieTokenValid_NullToken_ReturnsFalse()
+ {
+ // Arrange
+ AntiforgeryToken cookieToken = null;
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: null,
+ additionalDataProvider: null);
+
+ // Act
+ var isValid = tokenProvider.IsCookieTokenValid(cookieToken);
+
+ // Assert
+ Assert.False(isValid);
+ }
+
+ [Fact]
+ public void IsCookieTokenValid_ValidToken_ReturnsTrue()
+ {
+ // Arrange
+ var cookieToken = new AntiforgeryToken()
+ {
+ IsCookieToken = true
+ };
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: null,
+ additionalDataProvider: null);
+
+ // Act
+ var isValid = tokenProvider.IsCookieTokenValid(cookieToken);
+
+ // Assert
+ Assert.True(isValid);
+ }
+
+
+ [Fact]
+ public void TryValidateTokenSet_CookieTokenMissing()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
+
+ var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: null,
+ additionalDataProvider: null);
+
+ // Act & Assert
+ string message;
+ var ex = Assert.Throws(
+ () => tokenProvider.TryValidateTokenSet(httpContext, null, fieldtoken, out message));
+
+ var trimmed = ex.Message.Substring(0, ex.Message.IndexOf(Environment.NewLine));
+ Assert.Equal(@"The required antiforgery cookie token must be provided.", trimmed);
+ }
+
+ [Fact]
+ public void TryValidateTokenSet_FieldTokenMissing()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
+
+ var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: null,
+ additionalDataProvider: null);
+
+
+ // Act & Assert
+ string message;
+ var ex = Assert.Throws(
+ () => tokenProvider.TryValidateTokenSet(httpContext, cookieToken, null, out message));
+
+ var trimmed = ex.Message.Substring(0, ex.Message.IndexOf(Environment.NewLine));
+ Assert.Equal("The required antiforgery request token must be provided.", trimmed);
+ }
+
+ [Fact]
+ public void TryValidateTokenSet_FieldAndCookieTokensSwapped_FieldTokenDuplicated()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
+
+ var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
+ var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: null,
+ additionalDataProvider: null);
+
+ string expectedMessage =
+ "Validation of the provided antiforgery token failed. " +
+ "The cookie token and the request token were swapped.";
+
+ // Act
+ string message;
+ var result = tokenProvider.TryValidateTokenSet(httpContext, fieldtoken, fieldtoken, out message);
+
+ // Assert
+ Assert.False(result);
+ Assert.Equal(expectedMessage, message);
+ }
+
+ [Fact]
+ public void TryValidateTokenSet_FieldAndCookieTokensSwapped_CookieDuplicated()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
+
+ var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
+ var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: null,
+ additionalDataProvider: null);
+
+ string expectedMessage =
+ "Validation of the provided antiforgery token failed. " +
+ "The cookie token and the request token were swapped.";
+
+ // Act
+ string message;
+ var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, cookieToken, out message);
+
+ // Assert
+ Assert.False(result);
+ Assert.Equal(expectedMessage, message);
+ }
+
+ [Fact]
+ public void TryValidateTokenSet_FieldAndCookieTokensHaveDifferentSecurityKeys()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
+
+ var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
+ var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: null,
+ additionalDataProvider: null);
+
+ string expectedMessage = "The antiforgery cookie token and request token do not match.";
+
+ // Act
+ string message;
+ var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
+
+ // Assert
+ Assert.False(result);
+ Assert.Equal(expectedMessage, message);
+ }
+
+ [Theory]
+ [InlineData("the-user", "the-other-user")]
+ [InlineData("http://example.com/uri-casing", "http://example.com/URI-casing")]
+ [InlineData("https://example.com/secure-uri-casing", "https://example.com/secure-URI-casing")]
+ public void TryValidateTokenSet_UsernameMismatch(string identityUsername, string embeddedUsername)
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ var identity = GetAuthenticatedIdentity(identityUsername);
+ httpContext.User = new ClaimsPrincipal(identity);
+
+ var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
+ var fieldtoken = new AntiforgeryToken()
+ {
+ SecurityToken = cookieToken.SecurityToken,
+ Username = embeddedUsername,
+ IsCookieToken = false
+ };
+
+ var mockClaimUidExtractor = new Mock();
+ mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(It.Is(c => c.Identity == identity)))
+ .Returns((string)null);
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: mockClaimUidExtractor.Object,
+ additionalDataProvider: null);
+
+ string expectedMessage =
+ $"The provided antiforgery token was meant for user \"{embeddedUsername}\", " +
+ $"but the current user is \"{identityUsername}\".";
+
+ // Act
+ string message;
+ var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
+
+ // Assert
+ Assert.False(result);
+ Assert.Equal(expectedMessage, message);
+ }
+
+ [Fact]
+ public void TryValidateTokenSet_ClaimUidMismatch()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ var identity = GetAuthenticatedIdentity("the-user");
+ httpContext.User = new ClaimsPrincipal(identity);
+
+ var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
+ var fieldtoken = new AntiforgeryToken()
+ {
+ SecurityToken = cookieToken.SecurityToken,
+ IsCookieToken = false,
+ ClaimUid = new BinaryBlob(256)
+ };
+
+ var differentToken = new BinaryBlob(256);
+ var mockClaimUidExtractor = new Mock();
+ mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(It.Is(c => c.Identity == identity)))
+ .Returns(Convert.ToBase64String(differentToken.GetData()));
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: mockClaimUidExtractor.Object,
+ additionalDataProvider: null);
+
+ string expectedMessage =
+ "The provided antiforgery token was meant for a different " +
+ "claims-based user than the current user.";
+
+ // Act
+ string message;
+ var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
+
+ // Assert
+ Assert.False(result);
+ Assert.Equal(expectedMessage, message);
+ }
+
+ [Fact]
+ public void TryValidateTokenSet_AdditionalDataRejected()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ var identity = new ClaimsIdentity();
+ httpContext.User = new ClaimsPrincipal(identity);
+
+ var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
+ var fieldtoken = new AntiforgeryToken()
+ {
+ SecurityToken = cookieToken.SecurityToken,
+ Username = String.Empty,
+ IsCookieToken = false,
+ AdditionalData = "some-additional-data"
+ };
+
+ var mockAdditionalDataProvider = new Mock();
+ mockAdditionalDataProvider
+ .Setup(o => o.ValidateAdditionalData(httpContext, "some-additional-data"))
+ .Returns(false);
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: null,
+ additionalDataProvider: mockAdditionalDataProvider.Object);
+
+ string expectedMessage = "The provided antiforgery token failed a custom data check.";
+
+ // Act
+ string message;
+ var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
+
+ // Assert
+ Assert.False(result);
+ Assert.Equal(expectedMessage, message);
+ }
+
+ [Fact]
+ public void TryValidateTokenSet_Success_AnonymousUser()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ var identity = new ClaimsIdentity();
+ httpContext.User = new ClaimsPrincipal(identity);
+
+ var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
+ var fieldtoken = new AntiforgeryToken()
+ {
+ SecurityToken = cookieToken.SecurityToken,
+ Username = String.Empty,
+ IsCookieToken = false,
+ AdditionalData = "some-additional-data"
+ };
+
+ var mockAdditionalDataProvider = new Mock();
+ mockAdditionalDataProvider.Setup(o => o.ValidateAdditionalData(httpContext, "some-additional-data"))
+ .Returns(true);
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: null,
+ additionalDataProvider: mockAdditionalDataProvider.Object);
+
+ // Act
+ string message;
+ var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
+
+ // Assert
+ Assert.True(result);
+ Assert.Null(message);
+ }
+
+ [Fact]
+ public void TryValidateTokenSet_Success_AuthenticatedUserWithUsername()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ var identity = GetAuthenticatedIdentity("the-user");
+ httpContext.User = new ClaimsPrincipal(identity);
+
+ var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
+ var fieldtoken = new AntiforgeryToken()
+ {
+ SecurityToken = cookieToken.SecurityToken,
+ Username = "THE-USER",
+ IsCookieToken = false,
+ AdditionalData = "some-additional-data"
+ };
+
+ var mockAdditionalDataProvider = new Mock();
+ mockAdditionalDataProvider.Setup(o => o.ValidateAdditionalData(httpContext, "some-additional-data"))
+ .Returns(true);
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: new Mock().Object,
+ additionalDataProvider: mockAdditionalDataProvider.Object);
+
+ // Act
+ string message;
+ var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
+
+ // Assert
+ Assert.True(result);
+ Assert.Null(message);
+ }
+
+ [Fact]
+ public void TryValidateTokenSet_Success_ClaimsBasedUser()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ var identity = GetAuthenticatedIdentity("the-user");
+ httpContext.User = new ClaimsPrincipal(identity);
+
+ var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
+ var fieldtoken = new AntiforgeryToken()
+ {
+ SecurityToken = cookieToken.SecurityToken,
+ IsCookieToken = false,
+ ClaimUid = new BinaryBlob(256)
+ };
+
+ var mockClaimUidExtractor = new Mock();
+ mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(It.Is(c => c.Identity == identity)))
+ .Returns(Convert.ToBase64String(fieldtoken.ClaimUid.GetData()));
+
+ var tokenProvider = new DefaultAntiforgeryTokenGenerator(
+ claimUidExtractor: mockClaimUidExtractor.Object,
+ additionalDataProvider: null);
+
+ // Act
+ string message;
+ var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
+
+ // Assert
+ Assert.True(result);
+ Assert.Null(message);
+ }
+
+ private static ClaimsIdentity GetAuthenticatedIdentity(string identityUsername)
+ {
+ var claim = new Claim(ClaimsIdentity.DefaultNameClaimType, identityUsername);
+ return new ClaimsIdentity(new[] { claim }, "Some-Authentication");
+ }
+
+ private sealed class MyAuthenticatedIdentityWithoutUsername : ClaimsIdentity
+ {
+ public override bool IsAuthenticated
+ {
+ get { return true; }
+ }
+
+ public override string Name
+ {
+ get { return String.Empty; }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenSerializerTest.cs b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenSerializerTest.cs
new file mode 100644
index 0000000000..88ce09b4e2
--- /dev/null
+++ b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenSerializerTest.cs
@@ -0,0 +1,189 @@
+// 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 Microsoft.AspNetCore.DataProtection;
+using Microsoft.Extensions.ObjectPool;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class DefaultAntiforgeryTokenSerializerTest
+ {
+ private static readonly Mock _dataProtector = GetDataProtector();
+ private static readonly BinaryBlob _claimUid = new BinaryBlob(256, new byte[] { 0x6F, 0x16, 0x48, 0xE9, 0x72, 0x49, 0xAA, 0x58, 0x75, 0x40, 0x36, 0xA6, 0x7E, 0x24, 0x8C, 0xF0, 0x44, 0xF0, 0x7E, 0xCF, 0xB0, 0xED, 0x38, 0x75, 0x56, 0xCE, 0x02, 0x9A, 0x4F, 0x9A, 0x40, 0xE0 });
+ private static readonly BinaryBlob _securityToken = new BinaryBlob(128, new byte[] { 0x70, 0x5E, 0xED, 0xCC, 0x7D, 0x42, 0xF1, 0xD6, 0xB3, 0xB9, 0x8A, 0x59, 0x36, 0x25, 0xBB, 0x4C });
+ private static readonly ObjectPool _pool =
+ new DefaultObjectPoolProvider().Create(new AntiforgerySerializationContextPooledObjectPolicy());
+ private const byte _salt = 0x05;
+
+ [Theory]
+ [InlineData(
+ "01" // Version
+ + "705EEDCC7D42F1D6B3B9" // SecurityToken
+ // (WRONG!) Stream ends too early
+ )]
+ [InlineData(
+ "01" // Version
+ + "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
+ + "01" // IsCookieToken
+ + "00" // (WRONG!) Too much data in stream
+ )]
+ [InlineData(
+ "02" // (WRONG! - must be 0x01) Version
+ + "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
+ + "01" // IsCookieToken
+ )]
+ [InlineData(
+ "01" // Version
+ + "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
+ + "00" // IsCookieToken
+ + "00" // IsClaimsBased
+ + "05" // Username length header
+ + "0000" // (WRONG!) Too little data in stream
+ )]
+ public void Deserialize_BadToken_Throws(string serializedToken)
+ {
+ // Arrange
+ var testSerializer = new DefaultAntiforgeryTokenSerializer(_dataProtector.Object, _pool);
+
+ // Act & assert
+ var ex = Assert.Throws(() => testSerializer.Deserialize(serializedToken));
+ Assert.Equal(@"The antiforgery token could not be decrypted.", ex.Message);
+ }
+
+ [Fact]
+ public void Serialize_FieldToken_WithClaimUid_TokenRoundTripSuccessful()
+ {
+ // Arrange
+ var testSerializer = new DefaultAntiforgeryTokenSerializer(_dataProtector.Object, _pool);
+
+ //"01" // Version
+ //+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
+ //+ "00" // IsCookieToken
+ //+ "01" // IsClaimsBased
+ //+ "6F1648E97249AA58754036A67E248CF044F07ECFB0ED387556CE029A4F9A40E0" // ClaimUid
+ //+ "05" // AdditionalData length header
+ //+ "E282AC3437"; // AdditionalData ("€47") as UTF8
+ var token = new AntiforgeryToken()
+ {
+ SecurityToken = _securityToken,
+ IsCookieToken = false,
+ ClaimUid = _claimUid,
+ AdditionalData = "€47"
+ };
+
+ // Act
+ var actualSerializedData = testSerializer.Serialize(token);
+ var deserializedToken = testSerializer.Deserialize(actualSerializedData);
+
+ // Assert
+ AssertTokensEqual(token, deserializedToken);
+ _dataProtector.Verify();
+ }
+
+ [Fact]
+ public void Serialize_FieldToken_WithUsername_TokenRoundTripSuccessful()
+ {
+ // Arrange
+ var testSerializer = new DefaultAntiforgeryTokenSerializer(_dataProtector.Object, _pool);
+
+ //"01" // Version
+ //+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
+ //+ "00" // IsCookieToken
+ //+ "00" // IsClaimsBased
+ //+ "08" // Username length header
+ //+ "4AC3A972C3B46D65" // Username ("Jérôme") as UTF8
+ //+ "05" // AdditionalData length header
+ //+ "E282AC3437"; // AdditionalData ("€47") as UTF8
+ var token = new AntiforgeryToken()
+ {
+ SecurityToken = _securityToken,
+ IsCookieToken = false,
+ Username = "Jérôme",
+ AdditionalData = "€47"
+ };
+
+ // Act
+ var actualSerializedData = testSerializer.Serialize(token);
+ var deserializedToken = testSerializer.Deserialize(actualSerializedData);
+
+ // Assert
+ AssertTokensEqual(token, deserializedToken);
+ _dataProtector.Verify();
+ }
+
+ [Fact]
+ public void Serialize_CookieToken_TokenRoundTripSuccessful()
+ {
+ // Arrange
+ var testSerializer = new DefaultAntiforgeryTokenSerializer(_dataProtector.Object, _pool);
+
+ //"01" // Version
+ //+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
+ //+ "01"; // IsCookieToken
+ var token = new AntiforgeryToken()
+ {
+ SecurityToken = _securityToken,
+ IsCookieToken = true
+ };
+
+ // Act
+ string actualSerializedData = testSerializer.Serialize(token);
+ var deserializedToken = testSerializer.Deserialize(actualSerializedData);
+
+ // Assert
+ AssertTokensEqual(token, deserializedToken);
+ _dataProtector.Verify();
+ }
+
+ private static Mock GetDataProtector()
+ {
+ var mockCryptoSystem = new Mock();
+ mockCryptoSystem.Setup(o => o.Protect(It.IsAny()))
+ .Returns(Protect)
+ .Verifiable();
+ mockCryptoSystem.Setup(o => o.Unprotect(It.IsAny()))
+ .Returns(UnProtect)
+ .Verifiable();
+
+ var provider = new Mock();
+ provider
+ .Setup(p => p.CreateProtector(It.IsAny()))
+ .Returns(mockCryptoSystem.Object);
+ return provider;
+ }
+
+ private static byte[] Protect(byte[] data)
+ {
+ var input = new List(data);
+ input.Add(_salt);
+ return input.ToArray();
+ }
+
+ private static byte[] UnProtect(byte[] data)
+ {
+ var salt = data[data.Length - 1];
+ if (salt != _salt)
+ {
+ throw new ArgumentException("Invalid salt value in data");
+ }
+
+ return data.Take(data.Length - 1).ToArray();
+ }
+
+ private static void AssertTokensEqual(AntiforgeryToken expected, AntiforgeryToken actual)
+ {
+ Assert.NotNull(expected);
+ Assert.NotNull(actual);
+ Assert.Equal(expected.AdditionalData, actual.AdditionalData);
+ Assert.Equal(expected.ClaimUid, actual.ClaimUid);
+ Assert.Equal(expected.IsCookieToken, actual.IsCookieToken);
+ Assert.Equal(expected.SecurityToken, actual.SecurityToken);
+ Assert.Equal(expected.Username, actual.Username);
+ }
+ }
+}
diff --git a/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenStoreTest.cs b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenStoreTest.cs
new file mode 100644
index 0000000000..1ca1f57fc5
--- /dev/null
+++ b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenStoreTest.cs
@@ -0,0 +1,457 @@
+// 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.Http;
+using Microsoft.AspNetCore.Http.Internal;
+using Microsoft.Extensions.Primitives;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class DefaultAntiforgeryTokenStoreTest
+ {
+ private readonly string _cookieName = "cookie-name";
+
+ [Fact]
+ public void GetCookieToken_CookieDoesNotExist_ReturnsNull()
+ {
+ // Arrange
+ var httpContext = GetHttpContext(new RequestCookieCollection());
+ var options = new AntiforgeryOptions
+ {
+ Cookie = { Name = _cookieName }
+ };
+
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act
+ var token = tokenStore.GetCookieToken(httpContext);
+
+ // Assert
+ Assert.Null(token);
+ }
+
+ [Fact]
+ public void GetCookieToken_CookieIsEmpty_ReturnsNull()
+ {
+ // Arrange
+ var httpContext = GetHttpContext(_cookieName, string.Empty);
+ var options = new AntiforgeryOptions
+ {
+ Cookie = { Name = _cookieName }
+ };
+
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act
+ var token = tokenStore.GetCookieToken(httpContext);
+
+ // Assert
+ Assert.Null(token);
+ }
+
+ [Fact]
+ public void GetCookieToken_CookieIsNotEmpty_ReturnsToken()
+ {
+ // Arrange
+ var expectedToken = "valid-value";
+ var httpContext = GetHttpContext(_cookieName, expectedToken);
+
+ var options = new AntiforgeryOptions
+ {
+ Cookie = { Name = _cookieName }
+ };
+
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act
+ var token = tokenStore.GetCookieToken(httpContext);
+
+ // Assert
+ Assert.Equal(expectedToken, token);
+ }
+
+ [Fact]
+ public async Task GetRequestTokens_CookieIsEmpty_ReturnsNullTokens()
+ {
+ // Arrange
+ var httpContext = GetHttpContext(new RequestCookieCollection());
+ httpContext.Request.Form = FormCollection.Empty;
+
+ var options = new AntiforgeryOptions
+ {
+ Cookie = { Name = "cookie-name" },
+ FormFieldName = "form-field-name",
+ };
+
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act
+ var tokenSet = await tokenStore.GetRequestTokensAsync(httpContext);
+
+ // Assert
+ Assert.Null(tokenSet.CookieToken);
+ Assert.Null(tokenSet.RequestToken);
+ }
+
+ [Fact]
+ public async Task GetRequestTokens_HeaderTokenTakensPriority_OverFormToken()
+ {
+ // Arrange
+ var httpContext = GetHttpContext("cookie-name", "cookie-value");
+ httpContext.Request.ContentType = "application/x-www-form-urlencoded";
+ httpContext.Request.Form = new FormCollection(new Dictionary
+ {
+ { "form-field-name", "form-value" },
+ }); // header value has priority.
+ httpContext.Request.Headers.Add("header-name", "header-value");
+
+ var options = new AntiforgeryOptions
+ {
+ Cookie = { Name = "cookie-name" },
+ FormFieldName = "form-field-name",
+ HeaderName = "header-name",
+ };
+
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act
+ var tokens = await tokenStore.GetRequestTokensAsync(httpContext);
+
+ // Assert
+ Assert.Equal("cookie-value", tokens.CookieToken);
+ Assert.Equal("header-value", tokens.RequestToken);
+ }
+
+ [Fact]
+ public async Task GetRequestTokens_NoHeaderToken_FallsBackToFormToken()
+ {
+ // Arrange
+ var httpContext = GetHttpContext("cookie-name", "cookie-value");
+ httpContext.Request.ContentType = "application/x-www-form-urlencoded";
+ httpContext.Request.Form = new FormCollection(new Dictionary
+ {
+ { "form-field-name", "form-value" },
+ });
+
+ var options = new AntiforgeryOptions
+ {
+ Cookie = { Name = "cookie-name" },
+ FormFieldName = "form-field-name",
+ HeaderName = "header-name",
+ };
+
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act
+ var tokens = await tokenStore.GetRequestTokensAsync(httpContext);
+
+ // Assert
+ Assert.Equal("cookie-value", tokens.CookieToken);
+ Assert.Equal("form-value", tokens.RequestToken);
+ }
+
+ [Fact]
+ public async Task GetRequestTokens_NonFormContentType_UsesHeaderToken()
+ {
+ // Arrange
+ var httpContext = GetHttpContext("cookie-name", "cookie-value");
+ httpContext.Request.ContentType = "application/json";
+ httpContext.Request.Headers.Add("header-name", "header-value");
+
+ // Will not be accessed
+ httpContext.Request.Form = null;
+
+ var options = new AntiforgeryOptions
+ {
+ Cookie = { Name = "cookie-name" },
+ FormFieldName = "form-field-name",
+ HeaderName = "header-name",
+ };
+
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act
+ var tokens = await tokenStore.GetRequestTokensAsync(httpContext);
+
+ // Assert
+ Assert.Equal("cookie-value", tokens.CookieToken);
+ Assert.Equal("header-value", tokens.RequestToken);
+ }
+
+ [Fact]
+ public async Task GetRequestTokens_NoHeaderToken_NonFormContentType_ReturnsNullToken()
+ {
+ // Arrange
+ var httpContext = GetHttpContext("cookie-name", "cookie-value");
+ httpContext.Request.ContentType = "application/json";
+
+ // Will not be accessed
+ httpContext.Request.Form = null;
+
+ var options = new AntiforgeryOptions
+ {
+ Cookie = { Name = "cookie-name" },
+ FormFieldName = "form-field-name",
+ HeaderName = "header-name",
+ };
+
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act
+ var tokenSet = await tokenStore.GetRequestTokensAsync(httpContext);
+
+ // Assert
+ Assert.Equal("cookie-value", tokenSet.CookieToken);
+ Assert.Null(tokenSet.RequestToken);
+ }
+
+ [Fact]
+ public async Task GetRequestTokens_BothHeaderValueAndFormFieldsEmpty_ReturnsNullTokens()
+ {
+ // Arrange
+ var httpContext = GetHttpContext("cookie-name", "cookie-value");
+ httpContext.Request.ContentType = "application/x-www-form-urlencoded";
+ httpContext.Request.Form = FormCollection.Empty;
+
+ var options = new AntiforgeryOptions
+ {
+ Cookie = { Name = "cookie-name" },
+ FormFieldName = "form-field-name",
+ HeaderName = "header-name",
+ };
+
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act
+ var tokenSet = await tokenStore.GetRequestTokensAsync(httpContext);
+
+ // Assert
+ Assert.Equal("cookie-value", tokenSet.CookieToken);
+ Assert.Null(tokenSet.RequestToken);
+ }
+
+ [Theory]
+ [InlineData(false, CookieSecurePolicy.SameAsRequest, null)]
+ [InlineData(true, CookieSecurePolicy.SameAsRequest, true)]
+ [InlineData(false, CookieSecurePolicy.Always, true)]
+ [InlineData(true, CookieSecurePolicy.Always, true)]
+ [InlineData(false, CookieSecurePolicy.None, null)]
+ [InlineData(true, CookieSecurePolicy.None, null)]
+ public void SaveCookieToken_HonorsCookieSecurePolicy_OnOptions(
+ bool isRequestSecure,
+ CookieSecurePolicy policy,
+ bool? expectedCookieSecureFlag)
+ {
+ // Arrange
+ var token = "serialized-value";
+ bool defaultCookieSecureValue = expectedCookieSecureFlag ?? false; // pulled from config; set by ctor
+ var cookies = new MockResponseCookieCollection();
+
+ var httpContext = new Mock();
+ httpContext
+ .Setup(hc => hc.Request.IsHttps)
+ .Returns(isRequestSecure);
+ httpContext
+ .Setup(o => o.Response.Cookies)
+ .Returns(cookies);
+ httpContext
+ .SetupGet(hc => hc.Request.PathBase)
+ .Returns("/");
+
+ var options = new AntiforgeryOptions()
+ {
+ Cookie =
+ {
+ Name = _cookieName,
+ SecurePolicy = policy
+ },
+ };
+
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act
+ tokenStore.SaveCookieToken(httpContext.Object, token);
+
+ // Assert
+ Assert.Equal(1, cookies.Count);
+ Assert.NotNull(cookies);
+ Assert.Equal(_cookieName, cookies.Key);
+ Assert.Equal("serialized-value", cookies.Value);
+ Assert.True(cookies.Options.HttpOnly);
+ Assert.Equal(defaultCookieSecureValue, cookies.Options.Secure);
+ }
+
+ [Theory]
+ [InlineData(null, "/")]
+ [InlineData("", "/")]
+ [InlineData("/", "/")]
+ [InlineData("/vdir1", "/vdir1")]
+ [InlineData("/vdir1/vdir2", "/vdir1/vdir2")]
+ public void SaveCookieToken_SetsCookieWithApproriatePathBase(string requestPathBase, string expectedCookiePath)
+ {
+ // Arrange
+ var token = "serialized-value";
+ var cookies = new MockResponseCookieCollection();
+ var httpContext = new Mock();
+ httpContext
+ .Setup(hc => hc.Response.Cookies)
+ .Returns(cookies);
+ httpContext
+ .SetupGet(hc => hc.Request.PathBase)
+ .Returns(requestPathBase);
+ httpContext
+ .SetupGet(hc => hc.Request.Path)
+ .Returns("/index.html");
+ var options = new AntiforgeryOptions
+ {
+ Cookie = { Name = _cookieName }
+ };
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act
+ tokenStore.SaveCookieToken(httpContext.Object, token);
+
+ // Assert
+ Assert.Equal(1, cookies.Count);
+ Assert.NotNull(cookies);
+ Assert.Equal(_cookieName, cookies.Key);
+ Assert.Equal("serialized-value", cookies.Value);
+ Assert.True(cookies.Options.HttpOnly);
+ Assert.Equal(expectedCookiePath, cookies.Options.Path);
+ }
+
+ [Fact]
+ public void SaveCookieToken_NonNullAntiforgeryOptionsConfigureCookieOptionsPath_UsesCookieOptionsPath()
+ {
+ // Arrange
+ var expectedCookiePath = "/";
+ var requestPathBase = "/vdir1";
+ var token = "serialized-value";
+ var cookies = new MockResponseCookieCollection();
+ var httpContext = new Mock();
+ httpContext
+ .Setup(hc => hc.Response.Cookies)
+ .Returns(cookies);
+ httpContext
+ .SetupGet(hc => hc.Request.PathBase)
+ .Returns(requestPathBase);
+ httpContext
+ .SetupGet(hc => hc.Request.Path)
+ .Returns("/index.html");
+ var options = new AntiforgeryOptions
+ {
+ Cookie =
+ {
+ Name = _cookieName,
+ Path = expectedCookiePath
+ }
+ };
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act
+ tokenStore.SaveCookieToken(httpContext.Object, token);
+
+ // Assert
+ Assert.Equal(1, cookies.Count);
+ Assert.NotNull(cookies);
+ Assert.Equal(_cookieName, cookies.Key);
+ Assert.Equal("serialized-value", cookies.Value);
+ Assert.True(cookies.Options.HttpOnly);
+ Assert.Equal(expectedCookiePath, cookies.Options.Path);
+ }
+
+ [Fact]
+ public void SaveCookieToken_NonNullAntiforgeryOptionsConfigureCookieOptionsDomain_UsesCookieOptionsDomain()
+ {
+ // Arrange
+ var expectedCookieDomain = "microsoft.com";
+ var token = "serialized-value";
+ var cookies = new MockResponseCookieCollection();
+ var httpContext = new Mock();
+ httpContext
+ .Setup(hc => hc.Response.Cookies)
+ .Returns(cookies);
+ httpContext
+ .SetupGet(hc => hc.Request.PathBase)
+ .Returns("/vdir1");
+ httpContext
+ .SetupGet(hc => hc.Request.Path)
+ .Returns("/index.html");
+ var options = new AntiforgeryOptions
+ {
+ Cookie =
+ {
+ Name = _cookieName,
+ Domain = expectedCookieDomain
+ }
+ };
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act
+ tokenStore.SaveCookieToken(httpContext.Object, token);
+
+ // Assert
+ Assert.Equal(1, cookies.Count);
+ Assert.NotNull(cookies);
+ Assert.Equal(_cookieName, cookies.Key);
+ Assert.Equal("serialized-value", cookies.Value);
+ Assert.True(cookies.Options.HttpOnly);
+ Assert.Equal("/vdir1", cookies.Options.Path);
+ Assert.Equal(expectedCookieDomain, cookies.Options.Domain);
+ }
+
+ private HttpContext GetHttpContext(string cookieName, string cookieValue)
+ {
+ var cookies = new RequestCookieCollection(new Dictionary
+ {
+ { cookieName, cookieValue },
+ });
+
+ return GetHttpContext(cookies);
+ }
+
+ private HttpContext GetHttpContext(IRequestCookieCollection cookies)
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.Cookies = cookies;
+
+ return httpContext;
+ }
+
+ private class MockResponseCookieCollection : IResponseCookies
+ {
+ public string Key { get; set; }
+ public string Value { get; set; }
+ public CookieOptions Options { get; set; }
+ public int Count { get; set; }
+
+ public void Append(string key, string value, CookieOptions options)
+ {
+ Key = key;
+ Value = value;
+ Options = options;
+ Count++;
+ }
+
+ public void Append(string key, string value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Delete(string key, CookieOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Delete(string key)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultClaimUidExtractorTest.cs b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultClaimUidExtractorTest.cs
new file mode 100644
index 0000000000..1852b910da
--- /dev/null
+++ b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultClaimUidExtractorTest.cs
@@ -0,0 +1,261 @@
+// 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 Microsoft.Extensions.ObjectPool;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class DefaultClaimUidExtractorTest
+ {
+ private static readonly ObjectPool _pool =
+ new DefaultObjectPoolProvider().Create(new AntiforgerySerializationContextPooledObjectPolicy());
+
+ [Fact]
+ public void ExtractClaimUid_Unauthenticated()
+ {
+ // Arrange
+ var extractor = new DefaultClaimUidExtractor(_pool);
+
+ var mockIdentity = new Mock();
+ mockIdentity.Setup(o => o.IsAuthenticated)
+ .Returns(false);
+
+ // Act
+ var claimUid = extractor.ExtractClaimUid(new ClaimsPrincipal(mockIdentity.Object));
+
+ // Assert
+ Assert.Null(claimUid);
+ }
+
+ [Fact]
+ public void ExtractClaimUid_ClaimsIdentity()
+ {
+ // Arrange
+ var mockIdentity = new Mock();
+ mockIdentity.Setup(o => o.IsAuthenticated)
+ .Returns(true);
+ mockIdentity.Setup(o => o.Claims).Returns(new Claim[] { new Claim(ClaimTypes.Name, "someName") });
+
+ var extractor = new DefaultClaimUidExtractor(_pool);
+
+ // Act
+ var claimUid = extractor.ExtractClaimUid(new ClaimsPrincipal(mockIdentity.Object ));
+
+ // Assert
+ Assert.NotNull(claimUid);
+ Assert.Equal("yhXE+2v4zSXHtRHmzm4cmrhZca2J0g7yTUwtUerdeF4=", claimUid);
+ }
+
+ [Fact]
+ public void DefaultUniqueClaimTypes_NotPresent_SerializesAllClaimTypes()
+ {
+ var identity = new ClaimsIdentity("someAuthentication");
+ identity.AddClaim(new Claim(ClaimTypes.Email, "someone@antifrogery.com"));
+ identity.AddClaim(new Claim(ClaimTypes.GivenName, "some"));
+ identity.AddClaim(new Claim(ClaimTypes.Surname, "one"));
+ identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, string.Empty));
+
+ // Arrange
+ var claimsIdentity = (ClaimsIdentity)identity;
+
+ // Act
+ var identiferParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(new ClaimsIdentity[] { claimsIdentity })
+ .ToArray();
+ var claims = claimsIdentity.Claims.ToList();
+ claims.Sort((a, b) => string.Compare(a.Type, b.Type, StringComparison.Ordinal));
+
+ // Assert
+ int index = 0;
+ foreach (var claim in claims)
+ {
+ Assert.Equal(identiferParameters[index++], claim.Type);
+ Assert.Equal(identiferParameters[index++], claim.Value);
+ Assert.Equal(identiferParameters[index++], claim.Issuer);
+ }
+ }
+
+ [Fact]
+ public void DefaultUniqueClaimTypes_Present()
+ {
+ // Arrange
+ var identity = new ClaimsIdentity("someAuthentication");
+ identity.AddClaim(new Claim("fooClaim", "fooClaimValue"));
+ identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "nameIdentifierValue"));
+
+ // Act
+ var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(new ClaimsIdentity[] { identity });
+
+ // Assert
+ Assert.Equal(new string[]
+ {
+ ClaimTypes.NameIdentifier,
+ "nameIdentifierValue",
+ "LOCAL AUTHORITY",
+ }, uniqueIdentifierParameters);
+ }
+
+ [Fact]
+ public void GetUniqueIdentifierParameters_PrefersSubClaimOverNameIdentifierAndUpn()
+ {
+ // Arrange
+ var identity = new ClaimsIdentity("someAuthentication");
+ identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "nameIdentifierValue"));
+ identity.AddClaim(new Claim("sub", "subClaimValue"));
+ identity.AddClaim(new Claim(ClaimTypes.Upn, "upnClaimValue"));
+
+ // Act
+ var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(new ClaimsIdentity[] { identity });
+
+ // Assert
+ Assert.Equal(new string[]
+ {
+ "sub",
+ "subClaimValue",
+ "LOCAL AUTHORITY",
+ }, uniqueIdentifierParameters);
+ }
+
+ [Fact]
+ public void GetUniqueIdentifierParameters_PrefersNameIdentifierOverUpn()
+ {
+ // Arrange
+ var identity = new ClaimsIdentity("someAuthentication");
+ identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "nameIdentifierValue"));
+ identity.AddClaim(new Claim(ClaimTypes.Upn, "upnClaimValue"));
+
+ // Act
+ var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(new ClaimsIdentity[] { identity });
+
+ // Assert
+ Assert.Equal(new string[]
+ {
+ ClaimTypes.NameIdentifier,
+ "nameIdentifierValue",
+ "LOCAL AUTHORITY",
+ }, uniqueIdentifierParameters);
+ }
+
+ [Fact]
+ public void GetUniqueIdentifierParameters_UsesUpnIfPresent()
+ {
+ // Arrange
+ var identity = new ClaimsIdentity("someAuthentication");
+ identity.AddClaim(new Claim("fooClaim", "fooClaimValue"));
+ identity.AddClaim(new Claim(ClaimTypes.Upn, "upnClaimValue"));
+
+ // Act
+ var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(new ClaimsIdentity[] { identity });
+
+ // Assert
+ Assert.Equal(new string[]
+ {
+ ClaimTypes.Upn,
+ "upnClaimValue",
+ "LOCAL AUTHORITY",
+ }, uniqueIdentifierParameters);
+ }
+
+ [Fact]
+ public void GetUniqueIdentifierParameters_MultipleIdentities_UsesOnlyAuthenticatedIdentities()
+ {
+ // Arrange
+ var identity1 = new ClaimsIdentity(); // no authentication
+ identity1.AddClaim(new Claim("sub", "subClaimValue"));
+ var identity2 = new ClaimsIdentity("someAuthentication");
+ identity2.AddClaim(new Claim(ClaimTypes.NameIdentifier, "nameIdentifierValue"));
+
+ // Act
+ var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(new ClaimsIdentity[] { identity1, identity2 });
+
+ // Assert
+ Assert.Equal(new string[]
+ {
+ ClaimTypes.NameIdentifier,
+ "nameIdentifierValue",
+ "LOCAL AUTHORITY",
+ }, uniqueIdentifierParameters);
+ }
+
+ [Fact]
+ public void GetUniqueIdentifierParameters_NoKnownClaimTypesFound_SortsAndReturnsAllClaimsFromAuthenticatedIdentities()
+ {
+ // Arrange
+ var identity1 = new ClaimsIdentity(); // no authentication
+ identity1.AddClaim(new Claim("sub", "subClaimValue"));
+ var identity2 = new ClaimsIdentity("someAuthentication");
+ identity2.AddClaim(new Claim(ClaimTypes.Email, "email@domain.com"));
+ var identity3 = new ClaimsIdentity("someAuthentication");
+ identity3.AddClaim(new Claim(ClaimTypes.Country, "countryValue"));
+ var identity4 = new ClaimsIdentity("someAuthentication");
+ identity4.AddClaim(new Claim(ClaimTypes.Name, "claimName"));
+
+ // Act
+ var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(
+ new ClaimsIdentity[] { identity1, identity2, identity3, identity4 });
+
+ // Assert
+ Assert.Equal(new List
+ {
+ ClaimTypes.Country,
+ "countryValue",
+ "LOCAL AUTHORITY",
+ ClaimTypes.Email,
+ "email@domain.com",
+ "LOCAL AUTHORITY",
+ ClaimTypes.Name,
+ "claimName",
+ "LOCAL AUTHORITY",
+ }, uniqueIdentifierParameters);
+ }
+
+ [Fact]
+ public void GetUniqueIdentifierParameters_PrefersNameFromFirstIdentity_OverSubFromSecondIdentity()
+ {
+ // Arrange
+ var identity1 = new ClaimsIdentity("someAuthentication");
+ identity1.AddClaim(new Claim(ClaimTypes.NameIdentifier, "nameIdentifierValue"));
+ var identity2 = new ClaimsIdentity("someAuthentication");
+ identity2.AddClaim(new Claim("sub", "subClaimValue"));
+
+ // Act
+ var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(
+ new ClaimsIdentity[] { identity1, identity2 });
+
+ // Assert
+ Assert.Equal(new string[]
+ {
+ ClaimTypes.NameIdentifier,
+ "nameIdentifierValue",
+ "LOCAL AUTHORITY",
+ }, uniqueIdentifierParameters);
+ }
+
+ [Fact]
+ public void GetUniqueIdentifierParameters_PrefersUpnFromFirstIdentity_OverNameFromSecondIdentity()
+ {
+ // Arrange
+ var identity1 = new ClaimsIdentity("someAuthentication");
+ identity1.AddClaim(new Claim(ClaimTypes.Upn, "upnValue"));
+ var identity2 = new ClaimsIdentity("someAuthentication");
+ identity2.AddClaim(new Claim(ClaimTypes.NameIdentifier, "nameIdentifierValue"));
+
+ // Act
+ var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(
+ new ClaimsIdentity[] { identity1, identity2 });
+
+ // Assert
+ Assert.Equal(new string[]
+ {
+ ClaimTypes.Upn,
+ "upnValue",
+ "LOCAL AUTHORITY",
+ }, uniqueIdentifierParameters);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Microsoft.AspNetCore.Antiforgery.Test.csproj b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Microsoft.AspNetCore.Antiforgery.Test.csproj
new file mode 100644
index 0000000000..6b82710c8a
--- /dev/null
+++ b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/Microsoft.AspNetCore.Antiforgery.Test.csproj
@@ -0,0 +1,24 @@
+
+
+
+ $(StandardTestTfms)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/TestOptionsManager.cs b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/TestOptionsManager.cs
new file mode 100644
index 0000000000..7a6b3d7739
--- /dev/null
+++ b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test/TestOptionsManager.cs
@@ -0,0 +1,21 @@
+// 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.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Antiforgery
+{
+ public class TestOptionsManager : IOptions
+ {
+ public TestOptionsManager()
+ {
+ }
+
+ public TestOptionsManager(AntiforgeryOptions options)
+ {
+ Value = options;
+ }
+
+ public AntiforgeryOptions Value { get; set; } = new AntiforgeryOptions();
+ }
+}
diff --git a/src/Antiforgery/version.props b/src/Antiforgery/version.props
new file mode 100644
index 0000000000..669c874829
--- /dev/null
+++ b/src/Antiforgery/version.props
@@ -0,0 +1,12 @@
+
+
+ 2.1.1
+ rtm
+ $(VersionPrefix)
+ $(VersionPrefix)-$(VersionSuffix)-final
+ t000
+ a-
+ $(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-'))
+ $(VersionSuffix)-$(BuildNumber)
+
+