From 3280ff6ac55aee20953fbcb7284dcdcb93aa07dd Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 15 Dec 2015 10:45:05 -0800 Subject: [PATCH] Add Header support This change adds support for retrieving an antiforgery CSRF token via a configurable header in addition to the form field. This helps with doing ajax requests in a 1st-party SPA when using cookie auth, and is similar to functionality provided by a bunch of different frameworks. In this change there's also a bunch of churn due to avoiding the term 'form' in favor of 'request' and 'session' in favor of 'cookie'. Where code and error message now mention 'form' they specifically mean form-encoded content. --- .../FormPostSampleMiddleware.cs | 2 +- .../AntiforgeryOptions.cs | 19 ++- .../AntiforgeryToken.cs | 2 +- .../AntiforgeryTokenSet.cs | 18 +-- .../DefaultAntiforgery.cs | 22 ++-- .../DefaultAntiforgeryTokenGenerator.cs | 52 ++++---- .../DefaultAntiforgeryTokenSerializer.cs | 12 +- .../DefaultAntiforgeryTokenStore.cs | 40 ++++-- .../IAntiforgery.cs | 2 +- .../IAntiforgeryTokenGenerator.cs | 8 +- .../IAntiforgeryTokenStore.cs | 2 +- .../Properties/Resources.Designer.cs | 56 +++++++-- .../Resources.resx | 16 ++- .../AntiforgeryTokenTest.cs | 12 +- .../DefaultAntiforgeryTest.cs | 44 +++---- .../DefaultAntiforgeryTokenGeneratorTest.cs | 114 ++++++++--------- .../DefaultAntiforgeryTokenSerializerTest.cs | 22 ++-- .../DefaultAntiforgeryTokenStoreTest.cs | 118 +++++++++++++++++- 18 files changed, 370 insertions(+), 191 deletions(-) diff --git a/samples/AntiforgerySample/FormPostSampleMiddleware.cs b/samples/AntiforgerySample/FormPostSampleMiddleware.cs index aa9b41a872..ef0a8a5421 100644 --- a/samples/AntiforgerySample/FormPostSampleMiddleware.cs +++ b/samples/AntiforgerySample/FormPostSampleMiddleware.cs @@ -40,7 +40,7 @@ namespace AntiforgerySample "; var tokenSet = _antiforgery.GetAndStoreTokens(context); - await context.Response.WriteAsync(string.Format(page, _options.FormFieldName, tokenSet.FormToken)); + await context.Response.WriteAsync(string.Format(page, _options.FormFieldName, tokenSet.RequestToken)); } else if (context.Request.Method == "POST") { diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryOptions.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryOptions.cs index db66421a15..2308e36699 100644 --- a/src/Microsoft.AspNet.Antiforgery/AntiforgeryOptions.cs +++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryOptions.cs @@ -11,16 +11,17 @@ namespace Microsoft.AspNet.Antiforgery public class AntiforgeryOptions { private const string AntiforgeryTokenFieldName = "__RequestVerificationToken"; + private const string AntiforgertyTokenHeaderName = "RequestVerificationToken"; + private string _cookieName; + private string _headerName = AntiforgertyTokenHeaderName; private string _formFieldName = AntiforgeryTokenFieldName; /// - /// Specifies the name of the cookie that is used by the antiforgery - /// system. + /// 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 name. + /// If an explicit name is not provided, the system will automatically generate a name. /// public string CookieName { @@ -59,6 +60,16 @@ namespace Microsoft.AspNet.Antiforgery } } + /// + /// 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 { return _headerName; } + set { _headerName = value; } + } + /// /// Specifies whether SSL is required for the antiforgery system /// to operate. If this setting is 'true' and a non-SSL request diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryToken.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryToken.cs index 61f9665c26..233c5c51cd 100644 --- a/src/Microsoft.AspNet.Antiforgery/AntiforgeryToken.cs +++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryToken.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Antiforgery public BinaryBlob ClaimUid { get; set; } - public bool IsSessionToken { get; set; } + public bool IsCookieToken { get; set; } public BinaryBlob SecurityToken { diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenSet.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenSet.cs index 973e6883e9..8fabd782ee 100644 --- a/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenSet.cs +++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenSet.cs @@ -6,23 +6,23 @@ using System; namespace Microsoft.AspNet.Antiforgery { /// - /// The antiforgery token pair (cookie and form token) for a request. + /// The antiforgery token pair (cookie and request token) for a request. /// public class AntiforgeryTokenSet { /// - /// Creates the antiforgery token pair (cookie and form token) for a request. + /// Creates the antiforgery token pair (cookie and request token) for a request. /// - /// The token that is supplied in the request form body. + /// The token that is supplied in the request. /// The token that is supplied in the request cookie. - public AntiforgeryTokenSet(string formToken, string cookieToken) + public AntiforgeryTokenSet(string requestToken, string cookieToken) { - if (string.IsNullOrEmpty(formToken)) + if (string.IsNullOrEmpty(requestToken)) { - throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(formToken)); + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(requestToken)); } - FormToken = formToken; + RequestToken = requestToken; // Cookie Token is allowed to be null in the case when the old cookie is valid // and there is no new cookieToken generated. @@ -30,9 +30,9 @@ namespace Microsoft.AspNet.Antiforgery } /// - /// The token that is supplied in the request form body. + /// The token that is supplied in the request. /// - public string FormToken { get; private set; } + public string RequestToken { get; private set; } /// The cookie token is allowed to be null. /// This would be the case when the old cookie token is still valid. diff --git a/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgery.cs b/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgery.cs index cd67239b0d..7aa43f581d 100644 --- a/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgery.cs +++ b/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgery.cs @@ -45,14 +45,14 @@ namespace Microsoft.AspNet.Antiforgery var tokenSet = GetAndStoreTokens(context); - // Though FormToken normally contains only US-ASCII letters, numbers, '-', and '_', must assume the + // Though RequestToken normally contains only US-ASCII letters, numbers, '-', and '_', must assume the // IAntiforgeryTokenSerializer implementation has been overridden. Similarly, users may choose a // FormFieldName containing almost any character. var content = new HtmlContentBuilder() .AppendHtml(""); return content; @@ -122,22 +122,22 @@ namespace Microsoft.AspNet.Antiforgery nameof(antiforgeryTokenSet)); } - if (string.IsNullOrEmpty(antiforgeryTokenSet.FormToken)) + if (string.IsNullOrEmpty(antiforgeryTokenSet.RequestToken)) { throw new ArgumentException( - Resources.Antiforgery_FormToken_MustBeProvided_Generic, + Resources.Antiforgery_RequestToken_MustBeProvided_Generic, nameof(antiforgeryTokenSet)); } - // Extract cookie & form tokens + // Extract cookie & request tokens var deserializedCookieToken = _tokenSerializer.Deserialize(antiforgeryTokenSet.CookieToken); - var deserializedFormToken = _tokenSerializer.Deserialize(antiforgeryTokenSet.FormToken); + var deserializedRequestToken = _tokenSerializer.Deserialize(antiforgeryTokenSet.RequestToken); // Validate _tokenGenerator.ValidateTokens( context, deserializedCookieToken, - deserializedFormToken); + deserializedRequestToken); } /// @@ -225,7 +225,7 @@ namespace Microsoft.AspNet.Antiforgery { cookieToken = newCookieToken; } - var formToken = _tokenGenerator.GenerateFormToken( + var requestToken = _tokenGenerator.GenerateRequestToken( context, cookieToken); @@ -233,7 +233,7 @@ namespace Microsoft.AspNet.Antiforgery { // Note : The new cookie would be null if the old cookie is valid. CookieToken = cookieToken, - FormToken = formToken, + RequestToken = requestToken, IsNewCookieToken = newCookieToken != null }; } @@ -241,13 +241,13 @@ namespace Microsoft.AspNet.Antiforgery private AntiforgeryTokenSet Serialize(AntiforgeryTokenSetInternal tokenSet) { return new AntiforgeryTokenSet( - tokenSet.FormToken != null ? _tokenSerializer.Serialize(tokenSet.FormToken) : null, + tokenSet.RequestToken != null ? _tokenSerializer.Serialize(tokenSet.RequestToken) : null, tokenSet.CookieToken != null ? _tokenSerializer.Serialize(tokenSet.CookieToken) : null); } private class AntiforgeryTokenSetInternal { - public AntiforgeryToken FormToken { get; set; } + public AntiforgeryToken RequestToken { get; set; } public AntiforgeryToken CookieToken { get; set; } diff --git a/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgeryTokenGenerator.cs b/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgeryTokenGenerator.cs index c23661ccc4..32a2581f71 100644 --- a/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgeryTokenGenerator.cs +++ b/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgeryTokenGenerator.cs @@ -27,11 +27,11 @@ namespace Microsoft.AspNet.Antiforgery return new AntiforgeryToken() { // SecurityToken will be populated automatically. - IsSessionToken = true + IsCookieToken = true }; } - public AntiforgeryToken GenerateFormToken( + public AntiforgeryToken GenerateRequestToken( HttpContext httpContext, AntiforgeryToken cookieToken) { @@ -42,10 +42,10 @@ namespace Microsoft.AspNet.Antiforgery Debug.Assert(IsCookieTokenValid(cookieToken)); - var formToken = new AntiforgeryToken() + var requestToken = new AntiforgeryToken() { SecurityToken = cookieToken.SecurityToken, - IsSessionToken = false + IsCookieToken = false }; var isIdentityAuthenticated = false; @@ -55,23 +55,23 @@ namespace Microsoft.AspNet.Antiforgery if (identity != null && identity.IsAuthenticated) { isIdentityAuthenticated = true; - formToken.ClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(identity)); - if (formToken.ClaimUid == null) + requestToken.ClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(identity)); + if (requestToken.ClaimUid == null) { - formToken.Username = identity.Name; + requestToken.Username = identity.Name; } } // populate AdditionalData if (_additionalDataProvider != null) { - formToken.AdditionalData = _additionalDataProvider.GetAdditionalData(httpContext); + requestToken.AdditionalData = _additionalDataProvider.GetAdditionalData(httpContext); } if (isIdentityAuthenticated - && string.IsNullOrEmpty(formToken.Username) - && formToken.ClaimUid == null - && string.IsNullOrEmpty(formToken.AdditionalData)) + && 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( @@ -84,46 +84,46 @@ namespace Microsoft.AspNet.Antiforgery nameof(DefaultAntiforgeryAdditionalDataProvider))); } - return formToken; + return requestToken; } public bool IsCookieTokenValid(AntiforgeryToken cookieToken) { - return (cookieToken != null && cookieToken.IsSessionToken); + return (cookieToken != null && cookieToken.IsCookieToken); } public void ValidateTokens( HttpContext httpContext, - AntiforgeryToken sessionToken, - AntiforgeryToken fieldToken) + AntiforgeryToken cookieToken, + AntiforgeryToken requestToken) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } - if (sessionToken == null) + if (cookieToken == null) { throw new ArgumentNullException( - nameof(sessionToken), + nameof(cookieToken), Resources.Antiforgery_CookieToken_MustBeProvided_Generic); } - if (fieldToken == null) + if (requestToken == null) { throw new ArgumentNullException( - nameof(fieldToken), - Resources.Antiforgery_FormToken_MustBeProvided_Generic); + nameof(requestToken), + Resources.Antiforgery_RequestToken_MustBeProvided_Generic); } // Do the tokens have the correct format? - if (!sessionToken.IsSessionToken || fieldToken.IsSessionToken) + if (!cookieToken.IsCookieToken || requestToken.IsCookieToken) { throw new InvalidOperationException(Resources.AntiforgeryToken_TokensSwapped); } // Are the security tokens embedded in each incoming token identical? - if (!Equals(sessionToken.SecurityToken, fieldToken.SecurityToken)) + if (!Equals(cookieToken.SecurityToken, requestToken.SecurityToken)) { throw new InvalidOperationException(Resources.AntiforgeryToken_SecurityTokenMismatch); } @@ -148,24 +148,24 @@ namespace Microsoft.AspNet.Antiforgery currentUsername.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || currentUsername.StartsWith("https://", StringComparison.OrdinalIgnoreCase); - if (!string.Equals(fieldToken.Username, + if (!string.Equals(requestToken.Username, currentUsername, (useCaseSensitiveUsernameComparison) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException( - Resources.FormatAntiforgeryToken_UsernameMismatch(fieldToken.Username, currentUsername)); + Resources.FormatAntiforgeryToken_UsernameMismatch(requestToken.Username, currentUsername)); } - if (!Equals(fieldToken.ClaimUid, currentClaimUid)) + if (!Equals(requestToken.ClaimUid, currentClaimUid)) { throw new InvalidOperationException(Resources.AntiforgeryToken_ClaimUidMismatch); } // Is the AdditionalData valid? if (_additionalDataProvider != null && - !_additionalDataProvider.ValidateAdditionalData(httpContext, fieldToken.AdditionalData)) + !_additionalDataProvider.ValidateAdditionalData(httpContext, requestToken.AdditionalData)) { throw new InvalidOperationException(Resources.AntiforgeryToken_AdditionalDataCheckFailed); } diff --git a/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgeryTokenSerializer.cs b/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgeryTokenSerializer.cs index c5eb46a413..e1ecd406e8 100644 --- a/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgeryTokenSerializer.cs +++ b/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgeryTokenSerializer.cs @@ -56,8 +56,8 @@ namespace Microsoft.AspNet.Antiforgery /* The serialized format of the anti-XSRF token is as follows: * Version: 1 byte integer * SecurityToken: 16 byte binary blob - * IsSessionToken: 1 byte Boolean - * [if IsSessionToken != true] + * IsCookieToken: 1 byte Boolean + * [if IsCookieToken != true] * +- IsClaimsBased: 1 byte Boolean * | [if IsClaimsBased = true] * | `- ClaimUid: 32 byte binary blob @@ -78,9 +78,9 @@ namespace Microsoft.AspNet.Antiforgery var securityTokenBytes = reader.ReadBytes(AntiforgeryToken.SecurityTokenBitLength / 8); deserializedToken.SecurityToken = new BinaryBlob(AntiforgeryToken.SecurityTokenBitLength, securityTokenBytes); - deserializedToken.IsSessionToken = reader.ReadBoolean(); + deserializedToken.IsCookieToken = reader.ReadBoolean(); - if (!deserializedToken.IsSessionToken) + if (!deserializedToken.IsCookieToken) { var isClaimsBased = reader.ReadBoolean(); if (isClaimsBased) @@ -119,9 +119,9 @@ namespace Microsoft.AspNet.Antiforgery { writer.Write(TokenVersion); writer.Write(token.SecurityToken.GetData()); - writer.Write(token.IsSessionToken); + writer.Write(token.IsCookieToken); - if (!token.IsSessionToken) + if (!token.IsCookieToken) { if (token.ClaimUid != null) { diff --git a/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgeryTokenStore.cs b/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgeryTokenStore.cs index 29a50483ce..8adbd024ea 100644 --- a/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgeryTokenStore.cs +++ b/src/Microsoft.AspNet.Antiforgery/DefaultAntiforgeryTokenStore.cs @@ -7,10 +7,10 @@ using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.OptionsModel; +using Microsoft.Extensions.Primitives; namespace Microsoft.AspNet.Antiforgery { - // Saves anti-XSRF tokens split between HttpRequest.Cookies and HttpRequest.Form public class DefaultAntiforgeryTokenStore : IAntiforgeryTokenStore { private readonly AntiforgeryOptions _options; @@ -72,23 +72,43 @@ namespace Microsoft.AspNet.Antiforgery Resources.FormatAntiforgery_CookieToken_MustBeProvided(_options.CookieName)); } - if (!httpContext.Request.HasFormContentType) + StringValues requestToken; + if (httpContext.Request.HasFormContentType) { // Check the content-type before accessing the form collection to make sure // we throw gracefully. - throw new InvalidOperationException( - Resources.FormatAntiforgery_FormToken_MustBeProvided(_options.FormFieldName)); + var form = await httpContext.Request.ReadFormAsync(); + requestToken = form[_options.FormFieldName]; } - var form = await httpContext.Request.ReadFormAsync(); - var formField = form[_options.FormFieldName]; - if (string.IsNullOrEmpty(formField)) + // Fall back to header if the form value was not provided. + if (requestToken.Count == 0 && _options.HeaderName != null) { - throw new InvalidOperationException( - Resources.FormatAntiforgery_FormToken_MustBeProvided(_options.FormFieldName)); + requestToken = httpContext.Request.Headers[_options.HeaderName]; } - return new AntiforgeryTokenSet(formField, requestCookie); + if (requestToken.Count == 0) + { + if (_options.HeaderName == null) + { + var message = Resources.FormatAntiforgery_FormToken_MustBeProvided(_options.FormFieldName); + throw new InvalidOperationException(message); + } + else if (!httpContext.Request.HasFormContentType) + { + var message = Resources.FormatAntiforgery_HeaderToken_MustBeProvided(_options.HeaderName); + throw new InvalidOperationException(message); + } + else + { + var message = Resources.FormatAntiforgery_RequestToken_MustBeProvided( + _options.FormFieldName, + _options.HeaderName); + throw new InvalidOperationException(message); + } + } + + return new AntiforgeryTokenSet(requestToken, requestCookie); } public void SaveCookieToken(HttpContext httpContext, AntiforgeryToken token) diff --git a/src/Microsoft.AspNet.Antiforgery/IAntiforgery.cs b/src/Microsoft.AspNet.Antiforgery/IAntiforgery.cs index 9d11d4dad6..1944b02622 100644 --- a/src/Microsoft.AspNet.Antiforgery/IAntiforgery.cs +++ b/src/Microsoft.AspNet.Antiforgery/IAntiforgery.cs @@ -61,7 +61,7 @@ namespace Microsoft.AspNet.Antiforgery /// /// The associated with the current request. /// - /// The (cookie and form token) for this request. + /// The (cookie and request token) for this request. /// void ValidateTokens(HttpContext context, AntiforgeryTokenSet antiforgeryTokenSet); diff --git a/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenGenerator.cs b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenGenerator.cs index 0918d26917..47b701817c 100644 --- a/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenGenerator.cs +++ b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenGenerator.cs @@ -13,9 +13,9 @@ namespace Microsoft.AspNet.Antiforgery // Generates a new random cookie token. AntiforgeryToken GenerateCookieToken(); - // Given a cookie token, generates a corresponding form token. + // Given a cookie token, generates a corresponding request token. // The incoming cookie token must be valid. - AntiforgeryToken GenerateFormToken( + AntiforgeryToken GenerateRequestToken( HttpContext httpContext, AntiforgeryToken cookieToken); @@ -23,10 +23,10 @@ namespace Microsoft.AspNet.Antiforgery // If it is not, the caller must call GenerateCookieToken() before calling GenerateFormToken(). bool IsCookieTokenValid(AntiforgeryToken cookieToken); - // Validates a (cookie, form) token pair. + // Validates a (cookie, request) token pair. void ValidateTokens( HttpContext httpContext, AntiforgeryToken cookieToken, - AntiforgeryToken formToken); + AntiforgeryToken requestToken); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenStore.cs b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenStore.cs index c600d68e53..15ea992cb8 100644 --- a/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenStore.cs +++ b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenStore.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNet.Antiforgery AntiforgeryToken GetCookieToken(HttpContext httpContext); /// - /// Gets the cookie and form tokens from the request. Will throw an exception if either token is + /// Gets the cookie and request tokens from the request. Will throw an exception if either token is /// not present. /// /// The for the current request. diff --git a/src/Microsoft.AspNet.Antiforgery/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Antiforgery/Properties/Resources.Designer.cs index e4542a56f5..df958a3454 100644 --- a/src/Microsoft.AspNet.Antiforgery/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Antiforgery/Properties/Resources.Designer.cs @@ -75,7 +75,7 @@ namespace Microsoft.AspNet.Antiforgery } /// - /// The antiforgery cookie token and form field token do not match. + /// The antiforgery cookie token and request token do not match. /// internal static string AntiforgeryToken_SecurityTokenMismatch { @@ -83,7 +83,7 @@ namespace Microsoft.AspNet.Antiforgery } /// - /// The antiforgery cookie token and form field token do not match. + /// The antiforgery cookie token and request token do not match. /// internal static string FormatAntiforgeryToken_SecurityTokenMismatch() { @@ -91,7 +91,7 @@ namespace Microsoft.AspNet.Antiforgery } /// - /// Validation of the provided antiforgery token failed. The cookie token and the form token were swapped. + /// Validation of the provided antiforgery token failed. The cookie token and the request token were swapped. /// internal static string AntiforgeryToken_TokensSwapped { @@ -99,7 +99,7 @@ namespace Microsoft.AspNet.Antiforgery } /// - /// Validation of the provided antiforgery token failed. The cookie token and the form token were swapped. + /// Validation of the provided antiforgery token failed. The cookie token and the request token were swapped. /// internal static string FormatAntiforgeryToken_TokensSwapped() { @@ -155,7 +155,7 @@ namespace Microsoft.AspNet.Antiforgery } /// - /// The cookie token must be provided. + /// The required antiforgery cookie token must be provided. /// internal static string Antiforgery_CookieToken_MustBeProvided_Generic { @@ -163,7 +163,7 @@ namespace Microsoft.AspNet.Antiforgery } /// - /// The cookie token must be provided. + /// The required antiforgery cookie token must be provided. /// internal static string FormatAntiforgery_CookieToken_MustBeProvided_Generic() { @@ -187,19 +187,51 @@ namespace Microsoft.AspNet.Antiforgery } /// - /// The form token must be provided. + /// The required antiforgery header value "{0}" is not present. /// - internal static string Antiforgery_FormToken_MustBeProvided_Generic + internal static string Antiforgery_HeaderToken_MustBeProvided { - get { return GetString("Antiforgery_FormToken_MustBeProvided_Generic"); } + get { return GetString("Antiforgery_HeaderToken_MustBeProvided"); } } /// - /// The form token must be provided. + /// The required antiforgery header value "{0}" is not present. /// - internal static string FormatAntiforgery_FormToken_MustBeProvided_Generic() + internal static string FormatAntiforgery_HeaderToken_MustBeProvided(object p0) { - return GetString("Antiforgery_FormToken_MustBeProvided_Generic"); + return 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 { return 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) + { + return 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 { return GetString("Antiforgery_RequestToken_MustBeProvided_Generic"); } + } + + /// + /// The required antiforgery request token must be provided. + /// + internal static string FormatAntiforgery_RequestToken_MustBeProvided_Generic() + { + return GetString("Antiforgery_RequestToken_MustBeProvided_Generic"); } /// diff --git a/src/Microsoft.AspNet.Antiforgery/Resources.resx b/src/Microsoft.AspNet.Antiforgery/Resources.resx index cb8f9c3db8..8e84c4cac5 100644 --- a/src/Microsoft.AspNet.Antiforgery/Resources.resx +++ b/src/Microsoft.AspNet.Antiforgery/Resources.resx @@ -131,10 +131,10 @@ The antiforgery token could not be decrypted. - The antiforgery cookie token and form field token do not match. + The antiforgery cookie token and request token do not match. - Validation of the provided antiforgery token failed. The cookie token and the form token were swapped. + 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}". @@ -147,13 +147,19 @@ The required antiforgery cookie "{0}" is not present. - The cookie token must be provided. + The required antiforgery cookie token must be provided. The required antiforgery form field "{0}" is not present. - - The form token must be provided. + + 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. Value cannot be null or empty. diff --git a/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTokenTest.cs b/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTokenTest.cs index cddd47e43f..c56055bee7 100644 --- a/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTokenTest.cs +++ b/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTokenTest.cs @@ -45,21 +45,21 @@ namespace Microsoft.AspNet.Antiforgery } [Fact] - public void IsSessionTokenProperty() + public void IsCookieTokenProperty() { // Arrange var token = new AntiforgeryToken(); // Act & assert - 1 - Assert.False(token.IsSessionToken); + Assert.False(token.IsCookieToken); // Act & assert - 2 - token.IsSessionToken = true; - Assert.True(token.IsSessionToken); + token.IsCookieToken = true; + Assert.True(token.IsCookieToken); // Act & assert - 3 - token.IsSessionToken = false; - Assert.False(token.IsSessionToken); + token.IsCookieToken = false; + Assert.False(token.IsCookieToken); } [Fact] diff --git a/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTest.cs b/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTest.cs index 5f6b2ea59f..1a85a6826a 100644 --- a/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTest.cs +++ b/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTest.cs @@ -289,7 +289,7 @@ namespace Microsoft.AspNet.Antiforgery // Assert Assert.Equal("serialized-new-cookie-token", tokenset.CookieToken); - Assert.Equal("serialized-form-token", tokenset.FormToken); + Assert.Equal("serialized-form-token", tokenset.RequestToken); } [Fact] @@ -316,7 +316,7 @@ namespace Microsoft.AspNet.Antiforgery // Assert Assert.Equal("serialized-new-cookie-token", tokenset.CookieToken); - Assert.Equal("serialized-form-token", tokenset.FormToken); + Assert.Equal("serialized-form-token", tokenset.RequestToken); } [Fact] @@ -334,7 +334,7 @@ namespace Microsoft.AspNet.Antiforgery // Assert Assert.Equal("serialized-old-cookie-token", tokenset.CookieToken); - Assert.Equal("serialized-form-token", tokenset.FormToken); + Assert.Equal("serialized-form-token", tokenset.RequestToken); } [Fact] @@ -356,7 +356,7 @@ namespace Microsoft.AspNet.Antiforgery t => t.SaveCookieToken(It.IsAny(), It.IsAny()), Times.Never); Assert.Equal("serialized-old-cookie-token", tokenSet.CookieToken); - Assert.Equal("serialized-form-token", tokenSet.FormToken); + Assert.Equal("serialized-form-token", tokenSet.RequestToken); } [Fact] @@ -377,7 +377,7 @@ namespace Microsoft.AspNet.Antiforgery t => t.SaveCookieToken(It.IsAny(), It.IsAny()), Times.Once); Assert.Equal("serialized-new-cookie-token", tokenSet.CookieToken); - Assert.Equal("serialized-form-token", tokenSet.FormToken); + Assert.Equal("serialized-form-token", tokenSet.RequestToken); } [Fact] @@ -391,13 +391,13 @@ namespace Microsoft.AspNet.Antiforgery .Returns(context.TestTokenSet.OldCookieToken); context.TokenSerializer .Setup(o => o.Deserialize("form-token")) - .Returns(context.TestTokenSet.FormToken); + .Returns(context.TestTokenSet.RequestToken); context.TokenGenerator .Setup(o => o.ValidateTokens( context.HttpContext, context.TestTokenSet.OldCookieToken, - context.TestTokenSet.FormToken)) + context.TestTokenSet.RequestToken)) .Throws(new InvalidOperationException("my-message")); context.TokenStore = null; var antiforgery = GetAntiforgery(context); @@ -421,13 +421,13 @@ namespace Microsoft.AspNet.Antiforgery .Returns(context.TestTokenSet.OldCookieToken); context.TokenSerializer .Setup(o => o.Deserialize("form-token")) - .Returns(context.TestTokenSet.FormToken); + .Returns(context.TestTokenSet.RequestToken); context.TokenGenerator .Setup(o => o.ValidateTokens( context.HttpContext, context.TestTokenSet.OldCookieToken, - context.TestTokenSet.FormToken)) + context.TestTokenSet.RequestToken)) .Verifiable(); context.TokenStore = null; var antiforgery = GetAntiforgery(context); @@ -455,7 +455,7 @@ namespace Microsoft.AspNet.Antiforgery // Assert var trimmed = exception.Message.Substring(0, exception.Message.IndexOf(Environment.NewLine)); - Assert.Equal("The cookie token must be provided.", trimmed); + Assert.Equal("The required antiforgery cookie token must be provided.", trimmed); } [Fact] @@ -468,7 +468,7 @@ namespace Microsoft.AspNet.Antiforgery .Setup(o => o.ValidateTokens( context.HttpContext, context.TestTokenSet.OldCookieToken, - context.TestTokenSet.FormToken)) + context.TestTokenSet.RequestToken)) .Throws(new InvalidOperationException("my-message")); var antiforgery = GetAntiforgery(context); @@ -489,7 +489,7 @@ namespace Microsoft.AspNet.Antiforgery .Setup(o => o.ValidateTokens( context.HttpContext, context.TestTokenSet.OldCookieToken, - context.TestTokenSet.FormToken)) + context.TestTokenSet.RequestToken)) .Verifiable(); var antiforgery = GetAntiforgery(context); @@ -567,7 +567,7 @@ namespace Microsoft.AspNet.Antiforgery bool saveNewCookie = true) { var oldCookieToken = testTokenSet.OldCookieToken; - var formToken = testTokenSet.FormToken; + var formToken = testTokenSet.RequestToken; var mockTokenStore = new Mock(MockBehavior.Strict); mockTokenStore.Setup(o => o.GetCookieToken(context)) .Returns(oldCookieToken); @@ -591,7 +591,7 @@ namespace Microsoft.AspNet.Antiforgery { var oldCookieToken = testTokenSet.OldCookieToken; var newCookieToken = testTokenSet.NewCookieToken; - var formToken = testTokenSet.FormToken; + var formToken = testTokenSet.RequestToken; var mockSerializer = new Mock(MockBehavior.Strict); mockSerializer.Setup(o => o.Serialize(formToken)) .Returns(testTokenSet.FormTokenString); @@ -613,7 +613,7 @@ namespace Microsoft.AspNet.Antiforgery { // Arrange var httpContext = GetHttpContext(); - var testTokenSet = GetTokenSet(isOldCookieTokenSessionToken: true, isNewCookieSessionToken: true); + var testTokenSet = GetTokenSet(); var mockSerializer = GetTokenSerializer(testTokenSet); @@ -621,10 +621,10 @@ namespace Microsoft.AspNet.Antiforgery var mockGenerator = new Mock(MockBehavior.Strict); mockGenerator - .Setup(o => o.GenerateFormToken( + .Setup(o => o.GenerateRequestToken( httpContext, useOldCookie ? testTokenSet.OldCookieToken : testTokenSet.NewCookieToken)) - .Returns(testTokenSet.FormToken); + .Returns(testTokenSet.RequestToken); mockGenerator .Setup(o => o.GenerateCookieToken()) @@ -649,22 +649,22 @@ namespace Microsoft.AspNet.Antiforgery }; } - private TestTokenSet GetTokenSet(bool isOldCookieTokenSessionToken = true, bool isNewCookieSessionToken = true) + private TestTokenSet GetTokenSet() { return new TestTokenSet() { - FormToken = new AntiforgeryToken() { IsSessionToken = false }, + RequestToken = new AntiforgeryToken() { IsCookieToken = false }, FormTokenString = "serialized-form-token", - OldCookieToken = new AntiforgeryToken() { IsSessionToken = isOldCookieTokenSessionToken }, + OldCookieToken = new AntiforgeryToken() { IsCookieToken = true }, OldCookieTokenString = "serialized-old-cookie-token", - NewCookieToken = new AntiforgeryToken() { IsSessionToken = isNewCookieSessionToken }, + NewCookieToken = new AntiforgeryToken() { IsCookieToken = true }, NewCookieTokenString = "serialized-new-cookie-token", }; } private class TestTokenSet { - public AntiforgeryToken FormToken { get; set; } + public AntiforgeryToken RequestToken { get; set; } public string FormTokenString { get; set; } diff --git a/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTokenGeneratorTest.cs b/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTokenGeneratorTest.cs index 10fd048d19..4813d68411 100644 --- a/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTokenGeneratorTest.cs +++ b/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTokenGeneratorTest.cs @@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Antiforgery public void GenerateFormToken_AnonymousUser() { // Arrange - var cookieToken = new AntiforgeryToken() { IsSessionToken = true }; + var cookieToken = new AntiforgeryToken() { IsCookieToken = true }; var httpContext = new DefaultHttpContext(); httpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); Assert.False(httpContext.User.Identity.IsAuthenticated); @@ -41,12 +41,12 @@ namespace Microsoft.AspNet.Antiforgery additionalDataProvider: null); // Act - var fieldToken = tokenProvider.GenerateFormToken(httpContext, cookieToken); + var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken); // Assert Assert.NotNull(fieldToken); Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken); - Assert.False(fieldToken.IsSessionToken); + Assert.False(fieldToken.IsCookieToken); Assert.Empty(fieldToken.Username); Assert.Null(fieldToken.ClaimUid); Assert.Empty(fieldToken.AdditionalData); @@ -58,7 +58,7 @@ namespace Microsoft.AspNet.Antiforgery // Arrange var cookieToken = new AntiforgeryToken() { - IsSessionToken = true + IsCookieToken = true }; var httpContext = new DefaultHttpContext(); @@ -73,7 +73,7 @@ namespace Microsoft.AspNet.Antiforgery // Act & assert var exception = Assert.Throws( - () => tokenProvider.GenerateFormToken(httpContext, cookieToken)); + () => tokenProvider.GenerateRequestToken(httpContext, cookieToken)); Assert.Equal( "The provided identity of type " + $"'{typeof(MyAuthenticatedIdentityWithoutUsername).FullName}' " + @@ -90,7 +90,7 @@ namespace Microsoft.AspNet.Antiforgery public void GenerateFormToken_AuthenticatedWithoutUsername_WithAdditionalData() { // Arrange - var cookieToken = new AntiforgeryToken() { IsSessionToken = true }; + var cookieToken = new AntiforgeryToken() { IsCookieToken = true }; var httpContext = new DefaultHttpContext(); httpContext.User = new ClaimsPrincipal(new MyAuthenticatedIdentityWithoutUsername()); @@ -106,12 +106,12 @@ namespace Microsoft.AspNet.Antiforgery additionalDataProvider: mockAdditionalDataProvider.Object); // Act - var fieldToken = tokenProvider.GenerateFormToken(httpContext, cookieToken); + var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken); // Assert Assert.NotNull(fieldToken); Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken); - Assert.False(fieldToken.IsSessionToken); + Assert.False(fieldToken.IsCookieToken); Assert.Empty(fieldToken.Username); Assert.Null(fieldToken.ClaimUid); Assert.Equal("additional-data", fieldToken.AdditionalData); @@ -121,7 +121,7 @@ namespace Microsoft.AspNet.Antiforgery public void GenerateFormToken_ClaimsBasedIdentity() { // Arrange - var cookieToken = new AntiforgeryToken() { IsSessionToken = true }; + var cookieToken = new AntiforgeryToken() { IsCookieToken = true }; var identity = GetAuthenticatedIdentity("some-identity"); var httpContext = new DefaultHttpContext(); @@ -144,12 +144,12 @@ namespace Microsoft.AspNet.Antiforgery additionalDataProvider: null); // Act - var fieldToken = tokenProvider.GenerateFormToken(httpContext, cookieToken); + var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken); // Assert Assert.NotNull(fieldToken); Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken); - Assert.False(fieldToken.IsSessionToken); + Assert.False(fieldToken.IsCookieToken); Assert.Equal("", fieldToken.Username); Assert.Equal(expectedClaimUid, fieldToken.ClaimUid); Assert.Equal("", fieldToken.AdditionalData); @@ -159,7 +159,7 @@ namespace Microsoft.AspNet.Antiforgery public void GenerateFormToken_RegularUserWithUsername() { // Arrange - var cookieToken = new AntiforgeryToken() { IsSessionToken = true }; + var cookieToken = new AntiforgeryToken() { IsCookieToken = true }; var httpContext = new DefaultHttpContext(); var mockIdentity = new Mock(); @@ -177,12 +177,12 @@ namespace Microsoft.AspNet.Antiforgery additionalDataProvider: null); // Act - var fieldToken = tokenProvider.GenerateFormToken(httpContext, cookieToken); + var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken); // Assert Assert.NotNull(fieldToken); Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken); - Assert.False(fieldToken.IsSessionToken); + Assert.False(fieldToken.IsCookieToken); Assert.Equal("my-username", fieldToken.Username); Assert.Null(fieldToken.ClaimUid); Assert.Empty(fieldToken.AdditionalData); @@ -194,7 +194,7 @@ namespace Microsoft.AspNet.Antiforgery // Arrange var cookieToken = new AntiforgeryToken() { - IsSessionToken = false + IsCookieToken = false }; var tokenProvider = new DefaultAntiforgeryTokenGenerator( @@ -230,7 +230,7 @@ namespace Microsoft.AspNet.Antiforgery // Arrange var cookieToken = new AntiforgeryToken() { - IsSessionToken = true + IsCookieToken = true }; var tokenProvider = new DefaultAntiforgeryTokenGenerator( @@ -246,13 +246,13 @@ namespace Microsoft.AspNet.Antiforgery [Fact] - public void ValidateTokens_SessionTokenMissing() + public void ValidateTokens_CookieTokenMissing() { // Arrange var httpContext = new DefaultHttpContext(); httpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); - var fieldtoken = new AntiforgeryToken() { IsSessionToken = false }; + var fieldtoken = new AntiforgeryToken() { IsCookieToken = false }; var tokenProvider = new DefaultAntiforgeryTokenGenerator( claimUidExtractor: null, @@ -263,7 +263,7 @@ namespace Microsoft.AspNet.Antiforgery () => tokenProvider.ValidateTokens(httpContext, null, fieldtoken)); var trimmed = ex.Message.Substring(0, ex.Message.IndexOf(Environment.NewLine)); - Assert.Equal(@"The cookie token must be provided.", trimmed); + Assert.Equal(@"The required antiforgery cookie token must be provided.", trimmed); } [Fact] @@ -273,7 +273,7 @@ namespace Microsoft.AspNet.Antiforgery var httpContext = new DefaultHttpContext(); httpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); - var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; + var cookieToken = new AntiforgeryToken() { IsCookieToken = true }; var tokenProvider = new DefaultAntiforgeryTokenGenerator( claimUidExtractor: null, @@ -281,21 +281,21 @@ namespace Microsoft.AspNet.Antiforgery // Act & assert var ex = Assert.Throws( - () => tokenProvider.ValidateTokens(httpContext, sessionToken, null)); + () => tokenProvider.ValidateTokens(httpContext, cookieToken, null)); var trimmed = ex.Message.Substring(0, ex.Message.IndexOf(Environment.NewLine)); - Assert.Equal("The form token must be provided.", trimmed); + Assert.Equal("The required antiforgery request token must be provided.", trimmed); } [Fact] - public void ValidateTokens_FieldAndSessionTokensSwapped() + public void ValidateTokens_FieldAndCookieTokensSwapped() { // Arrange var httpContext = new DefaultHttpContext(); httpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); - var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; - var fieldtoken = new AntiforgeryToken() { IsSessionToken = false }; + var cookieToken = new AntiforgeryToken() { IsCookieToken = true }; + var fieldtoken = new AntiforgeryToken() { IsCookieToken = false }; var tokenProvider = new DefaultAntiforgeryTokenGenerator( claimUidExtractor: null, @@ -307,27 +307,27 @@ namespace Microsoft.AspNet.Antiforgery () => tokenProvider.ValidateTokens(httpContext, fieldtoken, fieldtoken)); Assert.Equal( "Validation of the provided antiforgery token failed. " + - @"The cookie token and the form token were swapped.", + @"The cookie token and the request token were swapped.", ex1.Message); var ex2 = Assert.Throws( - () => tokenProvider.ValidateTokens(httpContext, sessionToken, sessionToken)); + () => tokenProvider.ValidateTokens(httpContext, cookieToken, cookieToken)); Assert.Equal( "Validation of the provided antiforgery token failed. " + - @"The cookie token and the form token were swapped.", + @"The cookie token and the request token were swapped.", ex2.Message); } [Fact] - public void ValidateTokens_FieldAndSessionTokensHaveDifferentSecurityKeys() + public void ValidateTokens_FieldAndCookieTokensHaveDifferentSecurityKeys() { // Arrange var httpContext = new DefaultHttpContext(); httpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); - var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; - var fieldtoken = new AntiforgeryToken() { IsSessionToken = false }; + var cookieToken = new AntiforgeryToken() { IsCookieToken = true }; + var fieldtoken = new AntiforgeryToken() { IsCookieToken = false }; var tokenProvider = new DefaultAntiforgeryTokenGenerator( claimUidExtractor: null, @@ -335,9 +335,9 @@ namespace Microsoft.AspNet.Antiforgery // Act & Assert var exception = Assert.Throws( - () => tokenProvider.ValidateTokens(httpContext, sessionToken, fieldtoken)); + () => tokenProvider.ValidateTokens(httpContext, cookieToken, fieldtoken)); Assert.Equal( - @"The antiforgery cookie token and form field token do not match.", + @"The antiforgery cookie token and request token do not match.", exception.Message); } @@ -352,12 +352,12 @@ namespace Microsoft.AspNet.Antiforgery var identity = GetAuthenticatedIdentity(identityUsername); httpContext.User = new ClaimsPrincipal(identity); - var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; + var cookieToken = new AntiforgeryToken() { IsCookieToken = true }; var fieldtoken = new AntiforgeryToken() { - SecurityToken = sessionToken.SecurityToken, + SecurityToken = cookieToken.SecurityToken, Username = embeddedUsername, - IsSessionToken = false + IsCookieToken = false }; var mockClaimUidExtractor = new Mock(); @@ -370,7 +370,7 @@ namespace Microsoft.AspNet.Antiforgery // Act & Assert var exception = Assert.Throws( - () => tokenProvider.ValidateTokens(httpContext, sessionToken, fieldtoken)); + () => tokenProvider.ValidateTokens(httpContext, cookieToken, fieldtoken)); Assert.Equal( @"The provided antiforgery token was meant for user """ + embeddedUsername + @""", but the current user is """ + identityUsername + @""".", @@ -385,11 +385,11 @@ namespace Microsoft.AspNet.Antiforgery var identity = GetAuthenticatedIdentity("the-user"); httpContext.User = new ClaimsPrincipal(identity); - var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; + var cookieToken = new AntiforgeryToken() { IsCookieToken = true }; var fieldtoken = new AntiforgeryToken() { - SecurityToken = sessionToken.SecurityToken, - IsSessionToken = false, + SecurityToken = cookieToken.SecurityToken, + IsCookieToken = false, ClaimUid = new BinaryBlob(256) }; @@ -404,7 +404,7 @@ namespace Microsoft.AspNet.Antiforgery // Act & assert var exception = Assert.Throws( - () => tokenProvider.ValidateTokens(httpContext, sessionToken, fieldtoken)); + () => tokenProvider.ValidateTokens(httpContext, cookieToken, fieldtoken)); Assert.Equal( @"The provided antiforgery token was meant for a different claims-based user than the current user.", exception.Message); @@ -418,12 +418,12 @@ namespace Microsoft.AspNet.Antiforgery var identity = new ClaimsIdentity(); httpContext.User = new ClaimsPrincipal(identity); - var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; + var cookieToken = new AntiforgeryToken() { IsCookieToken = true }; var fieldtoken = new AntiforgeryToken() { - SecurityToken = sessionToken.SecurityToken, + SecurityToken = cookieToken.SecurityToken, Username = String.Empty, - IsSessionToken = false, + IsCookieToken = false, AdditionalData = "some-additional-data" }; @@ -437,7 +437,7 @@ namespace Microsoft.AspNet.Antiforgery // Act & assert var exception = Assert.Throws( - () => tokenProvider.ValidateTokens(httpContext, sessionToken, fieldtoken)); + () => tokenProvider.ValidateTokens(httpContext, cookieToken, fieldtoken)); Assert.Equal(@"The provided antiforgery token failed a custom data check.", exception.Message); } @@ -449,12 +449,12 @@ namespace Microsoft.AspNet.Antiforgery var identity = new ClaimsIdentity(); httpContext.User = new ClaimsPrincipal(identity); - var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; + var cookieToken = new AntiforgeryToken() { IsCookieToken = true }; var fieldtoken = new AntiforgeryToken() { - SecurityToken = sessionToken.SecurityToken, + SecurityToken = cookieToken.SecurityToken, Username = String.Empty, - IsSessionToken = false, + IsCookieToken = false, AdditionalData = "some-additional-data" }; @@ -467,7 +467,7 @@ namespace Microsoft.AspNet.Antiforgery additionalDataProvider: mockAdditionalDataProvider.Object); // Act - tokenProvider.ValidateTokens(httpContext, sessionToken, fieldtoken); + tokenProvider.ValidateTokens(httpContext, cookieToken, fieldtoken); // Assert // Nothing to assert - if we got this far, success! @@ -481,12 +481,12 @@ namespace Microsoft.AspNet.Antiforgery var identity = GetAuthenticatedIdentity("the-user"); httpContext.User = new ClaimsPrincipal(identity); - var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; + var cookieToken = new AntiforgeryToken() { IsCookieToken = true }; var fieldtoken = new AntiforgeryToken() { - SecurityToken = sessionToken.SecurityToken, + SecurityToken = cookieToken.SecurityToken, Username = "THE-USER", - IsSessionToken = false, + IsCookieToken = false, AdditionalData = "some-additional-data" }; @@ -499,7 +499,7 @@ namespace Microsoft.AspNet.Antiforgery additionalDataProvider: mockAdditionalDataProvider.Object); // Act - tokenProvider.ValidateTokens(httpContext, sessionToken, fieldtoken); + tokenProvider.ValidateTokens(httpContext, cookieToken, fieldtoken); // Assert // Nothing to assert - if we got this far, success! @@ -513,11 +513,11 @@ namespace Microsoft.AspNet.Antiforgery var identity = GetAuthenticatedIdentity("the-user"); httpContext.User = new ClaimsPrincipal(identity); - var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; + var cookieToken = new AntiforgeryToken() { IsCookieToken = true }; var fieldtoken = new AntiforgeryToken() { - SecurityToken = sessionToken.SecurityToken, - IsSessionToken = false, + SecurityToken = cookieToken.SecurityToken, + IsCookieToken = false, ClaimUid = new BinaryBlob(256) }; @@ -530,7 +530,7 @@ namespace Microsoft.AspNet.Antiforgery additionalDataProvider: null); // Act - tokenProvider.ValidateTokens(httpContext, sessionToken, fieldtoken); + tokenProvider.ValidateTokens(httpContext, cookieToken, fieldtoken); // Assert // Nothing to assert - if we got this far, success! diff --git a/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTokenSerializerTest.cs b/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTokenSerializerTest.cs index 0c074a2b13..a6012bcbe7 100644 --- a/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTokenSerializerTest.cs +++ b/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTokenSerializerTest.cs @@ -26,18 +26,18 @@ namespace Microsoft.AspNet.Antiforgery [InlineData( "01" // Version + "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken - + "01" // IsSessionToken + + "01" // IsCookieToken + "00" // (WRONG!) Too much data in stream )] [InlineData( "02" // (WRONG! - must be 0x01) Version + "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken - + "01" // IsSessionToken + + "01" // IsCookieToken )] [InlineData( "01" // Version + "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken - + "00" // IsSessionToken + + "00" // IsCookieToken + "00" // IsClaimsBased + "05" // Username length header + "0000" // (WRONG!) Too little data in stream @@ -60,7 +60,7 @@ namespace Microsoft.AspNet.Antiforgery //"01" // Version //+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken - //+ "00" // IsSessionToken + //+ "00" // IsCookieToken //+ "01" // IsClaimsBased //+ "6F1648E97249AA58754036A67E248CF044F07ECFB0ED387556CE029A4F9A40E0" // ClaimUid //+ "05" // AdditionalData length header @@ -68,7 +68,7 @@ namespace Microsoft.AspNet.Antiforgery var token = new AntiforgeryToken() { SecurityToken = _securityToken, - IsSessionToken = false, + IsCookieToken = false, ClaimUid = _claimUid, AdditionalData = "€47" }; @@ -90,7 +90,7 @@ namespace Microsoft.AspNet.Antiforgery //"01" // Version //+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken - //+ "00" // IsSessionToken + //+ "00" // IsCookieToken //+ "00" // IsClaimsBased //+ "08" // Username length header //+ "4AC3A972C3B46D65" // Username ("Jérôme") as UTF8 @@ -99,7 +99,7 @@ namespace Microsoft.AspNet.Antiforgery var token = new AntiforgeryToken() { SecurityToken = _securityToken, - IsSessionToken = false, + IsCookieToken = false, Username = "Jérôme", AdditionalData = "€47" }; @@ -114,18 +114,18 @@ namespace Microsoft.AspNet.Antiforgery } [Fact] - public void Serialize_SessionToken_TokenRoundTripSuccessful() + public void Serialize_CookieToken_TokenRoundTripSuccessful() { // Arrange var testSerializer = new DefaultAntiforgeryTokenSerializer(_dataProtector.Object); //"01" // Version //+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken - //+ "01"; // IsSessionToken + //+ "01"; // IsCookieToken var token = new AntiforgeryToken() { SecurityToken = _securityToken, - IsSessionToken = true + IsCookieToken = true }; // Act @@ -178,7 +178,7 @@ namespace Microsoft.AspNet.Antiforgery Assert.NotNull(actual); Assert.Equal(expected.AdditionalData, actual.AdditionalData); Assert.Equal(expected.ClaimUid, actual.ClaimUid); - Assert.Equal(expected.IsSessionToken, actual.IsSessionToken); + Assert.Equal(expected.IsCookieToken, actual.IsCookieToken); Assert.Equal(expected.SecurityToken, actual.SecurityToken); Assert.Equal(expected.Username, actual.Username); } diff --git a/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTokenStoreTest.cs b/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTokenStoreTest.cs index 275632ab4f..585c42e171 100644 --- a/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTokenStoreTest.cs +++ b/test/Microsoft.AspNet.Antiforgery.Test/DefaultAntiforgeryTokenStoreTest.cs @@ -187,7 +187,7 @@ namespace Microsoft.AspNet.Antiforgery } [Fact] - public async Task GetRequestTokens_NonFormContentType_Throws() + public async Task GetRequestTokens_NonFormContentType_HeaderDisabled_Throws() { // Arrange var httpContext = new DefaultHttpContext(); @@ -204,6 +204,7 @@ namespace Microsoft.AspNet.Antiforgery { CookieName = "cookie-name", FormFieldName = "form-field-name", + HeaderName = null, }; var tokenStore = new DefaultAntiforgeryTokenStore( @@ -219,7 +220,110 @@ namespace Microsoft.AspNet.Antiforgery } [Fact] - public async Task GetRequestTokens_FormFieldIsEmpty_Throws() + public async Task GetRequestTokens_FormContentType_FallbackHeaderToken() + { + // Arrange + var httpContext = new DefaultHttpContext(); + httpContext.Request.ContentType = "application/json"; + + // Will not be accessed + httpContext.Request.ContentType = "application/x-www-form-urlencoded"; + httpContext.Request.Form = new FormCollection(new Dictionary()); + httpContext.Request.Cookies = new RequestCookieCollection(new Dictionary() + { + { "cookie-name", "cookie-value" }, + }); + httpContext.Request.Headers.Add("header-name", "header-value"); + + var options = new AntiforgeryOptions() + { + CookieName = "cookie-name", + FormFieldName = "form-field-name", + HeaderName = "header-name", + }; + + var tokenStore = new DefaultAntiforgeryTokenStore( + optionsAccessor: new TestOptionsManager(options), + tokenSerializer: new DefaultAntiforgeryTokenSerializer(new EphemeralDataProtectionProvider())); + + // 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_NonFormContentType_UsesHeaderToken() + { + // Arrange + var httpContext = new DefaultHttpContext(); + httpContext.Request.ContentType = "application/json"; + + // Will not be accessed + httpContext.Request.Form = null; + httpContext.Request.Cookies = new RequestCookieCollection(new Dictionary() + { + { "cookie-name", "cookie-value" }, + }); + + httpContext.Request.Headers.Add("header-name", "header-value"); + + var options = new AntiforgeryOptions() + { + CookieName = "cookie-name", + FormFieldName = "form-field-name", + HeaderName = "header-name", + }; + + var tokenStore = new DefaultAntiforgeryTokenStore( + optionsAccessor: new TestOptionsManager(options), + tokenSerializer: new DefaultAntiforgeryTokenSerializer(new EphemeralDataProtectionProvider())); + + // 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_NonFormContentType_UsesHeaderToken_ThrowsOnMissingValue() + { + // Arrange + var httpContext = new DefaultHttpContext(); + httpContext.Request.ContentType = "application/json"; + + // Will not be accessed + httpContext.Request.Form = null; + httpContext.Request.Cookies = new RequestCookieCollection(new Dictionary() + { + { "cookie-name", "cookie-value" }, + }); + + var options = new AntiforgeryOptions() + { + CookieName = "cookie-name", + FormFieldName = "form-field-name", + HeaderName = "header-name", + }; + + var tokenStore = new DefaultAntiforgeryTokenStore( + optionsAccessor: new TestOptionsManager(options), + tokenSerializer: new DefaultAntiforgeryTokenSerializer(new EphemeralDataProtectionProvider())); + + // Act + var exception = await Assert.ThrowsAsync( + async () => await tokenStore.GetRequestTokensAsync(httpContext)); + + // Assert + Assert.Equal("The required antiforgery header value \"header-name\" is not present.", exception.Message); + } + + [Fact] + public async Task GetRequestTokens_BothFieldsEmpty_Throws() { // Arrange var httpContext = new DefaultHttpContext(); @@ -234,6 +338,7 @@ namespace Microsoft.AspNet.Antiforgery { CookieName = "cookie-name", FormFieldName = "form-field-name", + HeaderName = "header-name", }; var tokenStore = new DefaultAntiforgeryTokenStore( @@ -245,7 +350,10 @@ namespace Microsoft.AspNet.Antiforgery async () => await tokenStore.GetRequestTokensAsync(httpContext)); // Assert - Assert.Equal("The required antiforgery form field \"form-field-name\" is not present.", exception.Message); + 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] @@ -262,11 +370,13 @@ namespace Microsoft.AspNet.Antiforgery { { "cookie-name", "cookie-value" }, }); + httpContext.Request.Headers.Add("header-name", "header-value"); // form value has priority. var options = new AntiforgeryOptions() { CookieName = "cookie-name", FormFieldName = "form-field-name", + HeaderName = "header-name", }; var tokenStore = new DefaultAntiforgeryTokenStore( @@ -278,7 +388,7 @@ namespace Microsoft.AspNet.Antiforgery // Assert Assert.Equal("cookie-value", tokens.CookieToken); - Assert.Equal("form-value", tokens.FormToken); + Assert.Equal("form-value", tokens.RequestToken); } [Theory]