Skip to content
This repository has been archived by the owner on Nov 22, 2018. It is now read-only.

Commit

Permalink
[Fixes #105] Disable caching when response uses antiforgery
Browse files Browse the repository at this point in the history
  • Loading branch information
kichalla committed Oct 28, 2016
1 parent 72bc9c0 commit d0f363a
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/Microsoft.AspNetCore.Antiforgery/IAntiforgery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public interface IAntiforgery
{
/// <summary>
/// Generates an <see cref="AntiforgeryTokenSet"/> for this request and stores the cookie token
/// in the response.
/// in the response. It also sets the "Cache-control" and "Pragma" headers to "no-cache".
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
/// <returns>An <see cref="AntiforgeryTokenSet" /> with tokens for the response.</returns>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal static class AntiforgeryLoggerExtensions
private static readonly Action<ILogger, Exception> _newCookieToken;
private static readonly Action<ILogger, Exception> _reusedCookieToken;
private static readonly Action<ILogger, Exception> _tokenDeserializeException;
private static readonly Action<ILogger, Exception> _responseNotCached;

static AntiforgeryLoggerExtensions()
{
Expand Down Expand Up @@ -47,6 +48,10 @@ static AntiforgeryLoggerExtensions()
LogLevel.Error,
7,
"An exception was thrown while deserializing the token.");
_responseNotCached = LoggerMessage.Define(
LogLevel.Information,
7,
"The 'Cache-Control' and 'Pragma' headers of the response have been set as 'no-cache'.");
}

public static void ValidationFailed(this ILogger logger, string message)
Expand Down Expand Up @@ -83,5 +88,10 @@ public static void TokenDeserializeException(this ILogger logger, Exception exce
{
_tokenDeserializeException(logger, exception);
}

public static void ResponseNotCached(this ILogger logger)
{
_responseNotCached(logger, null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;

namespace Microsoft.AspNetCore.Antiforgery.Internal
{
Expand Down Expand Up @@ -66,6 +67,10 @@ public AntiforgeryTokenSet GetAndStoreTokens(HttpContext httpContext)
}
}

// 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;
}

Expand Down Expand Up @@ -237,6 +242,10 @@ public void SetCookieTokenAndHeader(HttpContext httpContext)
{
_logger.ReusedCookieToken();
}

// 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);
}

private void SaveCookieTokenAndHeader(HttpContext httpContext, string cookieToken)
Expand Down Expand Up @@ -358,6 +367,13 @@ private IAntiforgeryFeature GetTokensInternal(HttpContext httpContext)
return antiforgeryFeature;
}

private void SetDoNotCacheHeaders(HttpContext httpContext)
{
httpContext.Response.Headers[HeaderNames.CacheControl] = "no-cache";
httpContext.Response.Headers[HeaderNames.Pragma] = "no-cache";
_logger.ResponseNotCached();
}

private AntiforgeryTokenSet Serialize(IAntiforgeryFeature antiforgeryFeature)
{
// Should only be called after new tokens have been generated.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;

Expand Down Expand Up @@ -275,6 +276,67 @@ public void GetAndStoreTokens_ExistingValidCookieToken_NotOverriden()
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<HttpContext>(), It.IsAny<string>()),
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", 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<HttpContext>(), It.IsAny<string>()),
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", context.HttpContext.Response.Headers[HeaderNames.CacheControl]);
Assert.Equal("no-cache", context.HttpContext.Response.Headers[HeaderNames.Pragma]);
}

[Fact]
public void GetAndStoreTokens_NoExistingCookieToken_Saved()
{
Expand Down Expand Up @@ -309,6 +371,36 @@ public void GetAndStoreTokens_NoExistingCookieToken_Saved()
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<HttpContext>(), 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", context.HttpContext.Response.Headers[HeaderNames.CacheControl]);
Assert.Equal("no-cache", context.HttpContext.Response.Headers[HeaderNames.Pragma]);
}

[Fact]
public void GetAndStoreTokens_DoesNotSerializeTwice()
{
Expand Down Expand Up @@ -808,6 +900,76 @@ public void SetCookieTokenAndHeader_PreserveXFrameOptionsHeader()
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", 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", 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", context.HttpContext.Response.Headers["Cache-Control"]);
Assert.Equal("no-cache", context.HttpContext.Response.Headers["Pragma"]);
}

[Theory]
[InlineData(false, "SAMEORIGIN")]
[InlineData(true, null)]
Expand Down

0 comments on commit d0f363a

Please sign in to comment.