diff --git a/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryBuilderExtensions.cs b/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryBuilderExtensions.cs
new file mode 100644
index 0000000000..7181518dfb
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryBuilderExtensions.cs
@@ -0,0 +1,29 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ /// Extension methods for configuring the antiforgery middleware.
+ ///
+ public static class AntiforgeryBuilderExtensions
+ {
+ ///
+ /// Adds the antiforgery middleware.
+ ///
+ /// The .
+ /// The .
+ public static IApplicationBuilder UseAntiforgery(this IApplicationBuilder app)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ return app.UseMiddleware();
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryMiddleware.cs b/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryMiddleware.cs
new file mode 100644
index 0000000000..eccadfd699
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Antiforgery/AntiforgeryMiddleware.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;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Antiforgery.Internal;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Antiforgery
+{
+ ///
+ /// A middleware implementation of antiforgery validation.
+ ///
+ public class AntiforgeryMiddleware
+ {
+ private readonly IAntiforgery _antiforgery;
+ private readonly RequestDelegate _next;
+
+ ///
+ /// Creates a new .
+ ///
+ /// The for the next middleware.
+ /// The .
+ public AntiforgeryMiddleware(RequestDelegate next, IAntiforgery antiforgery)
+ {
+ if (next == null)
+ {
+ throw new ArgumentNullException(nameof(next));
+ }
+
+ if (antiforgery == null)
+ {
+ throw new ArgumentNullException(nameof(antiforgery));
+ }
+
+ _next = next;
+ _antiforgery = antiforgery;
+ }
+
+ ///
+ /// Invokes the middleware for the given .
+ ///
+ /// The associated with the current request.
+ /// A which will be completed when execution of the middleware completes.
+ public async Task Invoke(HttpContext httpContext)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ var handler = new AntiforgeryAuthenticationHandler(_antiforgery);
+ await handler.InitializeAsync(httpContext);
+
+ try
+ {
+ await _next(httpContext);
+ }
+ finally
+ {
+ handler.Teardown();
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Antiforgery/IAntiforgery.cs b/src/Microsoft.AspNetCore.Antiforgery/IAntiforgery.cs
index 6ac2e44e37..d22828ef31 100644
--- a/src/Microsoft.AspNetCore.Antiforgery/IAntiforgery.cs
+++ b/src/Microsoft.AspNetCore.Antiforgery/IAntiforgery.cs
@@ -1,6 +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.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
@@ -46,6 +47,18 @@ namespace Microsoft.AspNetCore.Antiforgery
///
Task IsRequestValidAsync(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.
+ /// The claims-based principal to validate.
+ ///
+ /// A that, when completed, returns true if the is requst uses a safe HTTP
+ /// method or contains a value antiforgery token, otherwise returns false.
+ ///
+ Task IsRequestValidAsync(HttpContext httpContext, ClaimsPrincipal principal);
+
///
/// Validates an antiforgery token that was supplied as part of the request.
///
@@ -55,6 +68,16 @@ namespace Microsoft.AspNetCore.Antiforgery
///
Task ValidateRequestAsync(HttpContext httpContext);
+ ///
+ /// Validates an antiforgery token that was supplied as part of the request.
+ ///
+ /// The associated with the current request.
+ /// The claims-based principal to validate.
+ ///
+ /// Thrown when the request does not include a valid antiforgery token.
+ ///
+ Task ValidateRequestAsync(HttpContext httpContext, ClaimsPrincipal principal);
+
///
/// Generates and stores an antiforgery cookie token if one is not available or not valid.
///
diff --git a/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgeryAuthenticationHandler.cs b/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgeryAuthenticationHandler.cs
new file mode 100644
index 0000000000..c3abeb0ac6
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Antiforgery/Internal/AntiforgeryAuthenticationHandler.cs
@@ -0,0 +1,161 @@
+// 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.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features.Authentication;
+using Microsoft.AspNetCore.Http.Features.Authentication.Internal;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class AntiforgeryAuthenticationHandler : IAuthenticationHandler
+ {
+ private readonly IAntiforgery _antiforgery;
+ private HttpContext _httpContext;
+ private IAuthenticationHandler _priorHandler;
+
+ public AntiforgeryAuthenticationHandler(IAntiforgery antiforgery)
+ {
+ if (antiforgery == null)
+ {
+ throw new ArgumentNullException(nameof(antiforgery));
+ }
+
+ _antiforgery = antiforgery;
+ }
+
+ public async Task InitializeAsync(HttpContext httpContext)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ _httpContext = httpContext;
+
+ var authentication = GetAuthenticationFeature(_httpContext);
+
+ _priorHandler = authentication.Handler;
+ authentication.Handler = this;
+
+ if (authentication.User != null)
+ {
+ if (!await _antiforgery.IsRequestValidAsync(_httpContext))
+ {
+ // Wipe out any existing principal if we can't validate this request.
+ authentication.User = null;
+ return;
+ }
+ }
+ }
+
+ public void Teardown()
+ {
+ var authentication = GetAuthenticationFeature(_httpContext);
+ authentication.Handler = _priorHandler;
+ }
+
+ ///
+ public async Task AuthenticateAsync(AuthenticateContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (_priorHandler != null)
+ {
+ await _priorHandler.AuthenticateAsync(context);
+
+ var authentication = GetAuthenticationFeature(_httpContext);
+ if (context.Principal != null)
+ {
+ try
+ {
+ await _antiforgery.ValidateRequestAsync(_httpContext, context.Principal);
+ }
+ catch (AntiforgeryValidationException ex)
+ {
+ context.Failed(ex);
+ return;
+ }
+ }
+ }
+ }
+
+ ///
+ public Task ChallengeAsync(ChallengeContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (_priorHandler != null)
+ {
+ return _priorHandler.ChallengeAsync(context);
+ }
+
+ return TaskCache.CompletedTask;
+ }
+
+ ///
+ public void GetDescriptions(DescribeSchemesContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (_priorHandler != null)
+ {
+ _priorHandler.GetDescriptions(context);
+ }
+ }
+
+ ///
+ public Task SignInAsync(SignInContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (_priorHandler != null)
+ {
+ return _priorHandler.SignInAsync(context);
+ }
+
+ return TaskCache.CompletedTask;
+ }
+
+ ///
+ public Task SignOutAsync(SignOutContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (_priorHandler != null)
+ {
+ return _priorHandler.SignOutAsync(context);
+ }
+
+ return TaskCache.CompletedTask;
+ }
+
+ private static IHttpAuthenticationFeature GetAuthenticationFeature(HttpContext httpContext)
+ {
+ var authentication = httpContext.Features.Get();
+ if (authentication == null)
+ {
+ authentication = new HttpAuthenticationFeature();
+ httpContext.Features.Set(authentication);
+ }
+
+ return authentication;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgery.cs b/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgery.cs
index 137b0b4fe0..95f0208054 100644
--- a/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgery.cs
+++ b/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgery.cs
@@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
+using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
@@ -85,7 +86,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
}
///
- public async Task IsRequestValidAsync(HttpContext httpContext)
+ public Task IsRequestValidAsync(HttpContext httpContext)
{
if (httpContext == null)
{
@@ -94,13 +95,26 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
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))
+ return IsRequestValidAsync(httpContext, httpContext.User);
+ }
+
+ ///
+ public async Task IsRequestValidAsync(HttpContext httpContext, ClaimsPrincipal principal)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ if (principal == null)
+ {
+ throw new ArgumentNullException(nameof(principal));
+ }
+
+ CheckSSLConfig(httpContext);
+
+ if (!IsValidationRequired(httpContext))
{
- // Validation not needed for these request types.
return true;
}
@@ -126,6 +140,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
string message;
var result = _tokenGenerator.TryValidateTokenSet(
httpContext,
+ principal,
deserializedCookieToken,
deserializedRequestToken,
out message);
@@ -143,7 +158,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
}
///
- public async Task ValidateRequestAsync(HttpContext httpContext)
+ public Task ValidateRequestAsync(HttpContext httpContext)
{
if (httpContext == null)
{
@@ -152,6 +167,29 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
CheckSSLConfig(httpContext);
+ return ValidateRequestAsync(httpContext, httpContext.User);
+ }
+
+ ///
+ public async Task ValidateRequestAsync(HttpContext httpContext, ClaimsPrincipal principal)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ if (principal == null)
+ {
+ throw new ArgumentNullException(nameof(principal));
+ }
+
+ CheckSSLConfig(httpContext);
+
+ if (!IsValidationRequired(httpContext))
+ {
+ return;
+ }
+
var tokens = await _tokenStore.GetRequestTokensAsync(httpContext);
if (tokens.CookieToken == null)
{
@@ -180,12 +218,15 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
}
}
- ValidateTokens(httpContext, tokens);
+ ValidateTokens(httpContext, principal, tokens);
_logger.ValidatedAntiforgeryToken();
}
- private void ValidateTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet)
+ private void ValidateTokens(
+ HttpContext httpContext,
+ ClaimsPrincipal principal,
+ AntiforgeryTokenSet antiforgeryTokenSet)
{
Debug.Assert(!string.IsNullOrEmpty(antiforgeryTokenSet.CookieToken));
Debug.Assert(!string.IsNullOrEmpty(antiforgeryTokenSet.RequestToken));
@@ -203,6 +244,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
string message;
if (!_tokenGenerator.TryValidateTokenSet(
httpContext,
+ principal,
deserializedCookieToken,
deserializedRequestToken,
out message))
@@ -268,6 +310,21 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
}
}
+ private bool IsValidationRequired(HttpContext 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 HTTP methods that don't mutate any state.
+ return false;
+ }
+
+ return true;
+ }
+
private AntiforgeryContext GetCookieTokens(HttpContext httpContext)
{
var services = httpContext.RequestServices;
@@ -340,7 +397,10 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
if (antiforgeryContext.NewRequestToken == null)
{
var cookieToken = antiforgeryContext.NewCookieToken ?? antiforgeryContext.CookieToken;
- antiforgeryContext.NewRequestToken = _tokenGenerator.GenerateRequestToken(httpContext, cookieToken);
+ antiforgeryContext.NewRequestToken = _tokenGenerator.GenerateRequestToken(
+ httpContext,
+ httpContext.User,
+ cookieToken);
}
return antiforgeryContext;
diff --git a/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenGenerator.cs b/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenGenerator.cs
index 872f7ed18c..14c102bb51 100644
--- a/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenGenerator.cs
+++ b/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenGenerator.cs
@@ -35,6 +35,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
///
public AntiforgeryToken GenerateRequestToken(
HttpContext httpContext,
+ ClaimsPrincipal principal,
AntiforgeryToken cookieToken)
{
if (httpContext == null)
@@ -63,11 +64,11 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
var isIdentityAuthenticated = false;
// populate Username and ClaimUid
- var authenticatedIdentity = GetAuthenticatedIdentity(httpContext.User);
+ var authenticatedIdentity = GetAuthenticatedIdentity(principal);
if (authenticatedIdentity != null)
{
isIdentityAuthenticated = true;
- requestToken.ClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(httpContext.User));
+ requestToken.ClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(principal));
if (requestToken.ClaimUid == null)
{
@@ -109,6 +110,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
///
public bool TryValidateTokenSet(
HttpContext httpContext,
+ ClaimsPrincipal principal,
AntiforgeryToken cookieToken,
AntiforgeryToken requestToken,
out string message)
@@ -150,10 +152,10 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
var currentUsername = string.Empty;
BinaryBlob currentClaimUid = null;
- var authenticatedIdentity = GetAuthenticatedIdentity(httpContext.User);
+ var authenticatedIdentity = GetAuthenticatedIdentity(principal);
if (authenticatedIdentity != null)
{
- currentClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(httpContext.User));
+ currentClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(principal));
if (currentClaimUid == null)
{
currentUsername = authenticatedIdentity.Name ?? string.Empty;
diff --git a/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryTokenGenerator.cs b/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryTokenGenerator.cs
index c0dff86047..8c5d22cf31 100644
--- a/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryTokenGenerator.cs
+++ b/src/Microsoft.AspNetCore.Antiforgery/Internal/IAntiforgeryTokenGenerator.cs
@@ -1,6 +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.Security.Claims;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Antiforgery.Internal
@@ -20,9 +21,13 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
/// Generates a request token corresponding to .
///
/// The associated with the current request.
+ /// The claims-based principal to use for token generation.
/// A valid cookie token.
/// An .
- AntiforgeryToken GenerateRequestToken(HttpContext httpContext, AntiforgeryToken cookieToken);
+ AntiforgeryToken GenerateRequestToken(
+ HttpContext httpContext,
+ ClaimsPrincipal principal,
+ AntiforgeryToken cookieToken);
///
/// Attempts to validate a cookie token.
@@ -35,6 +40,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
/// Attempts to validate a cookie and request token set for the given .
///
/// The associated with the current request.
+ /// The claims-based principal to use for token validation.
/// A cookie token.
/// A request token.
///
@@ -43,6 +49,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
/// true if the tokens are valid, otherwise false.
bool TryValidateTokenSet(
HttpContext httpContext,
+ ClaimsPrincipal principal,
AntiforgeryToken cookieToken,
AntiforgeryToken requestToken,
out string message);
diff --git a/src/Microsoft.AspNetCore.Antiforgery/Internal/TaskCache.cs b/src/Microsoft.AspNetCore.Antiforgery/Internal/TaskCache.cs
new file mode 100644
index 0000000000..d21d2e7a76
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Antiforgery/Internal/TaskCache.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.
+
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public static class TaskCache
+ {
+ public static readonly Task CompletedTask = Task.FromResult(0);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Antiforgery/project.json b/src/Microsoft.AspNetCore.Antiforgery/project.json
index d5bda2ecf5..4fcfc49f40 100644
--- a/src/Microsoft.AspNetCore.Antiforgery/project.json
+++ b/src/Microsoft.AspNetCore.Antiforgery/project.json
@@ -13,8 +13,7 @@
},
"dependencies": {
"Microsoft.AspNetCore.DataProtection": "1.0.0-*",
- "Microsoft.AspNetCore.Http.Abstractions": "1.0.0-*",
- "Microsoft.AspNetCore.WebUtilities": "1.0.0-*",
+ "Microsoft.AspNetCore.Http": "1.0.0-*",
"Microsoft.Extensions.ObjectPool": "1.0.0-*"
},
"frameworks": {
diff --git a/test/Microsoft.AspNetCore.Antiforgery.Test/AntiforgeryMiddlewareTest.cs b/test/Microsoft.AspNetCore.Antiforgery.Test/AntiforgeryMiddlewareTest.cs
new file mode 100644
index 0000000000..56c606c847
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Antiforgery.Test/AntiforgeryMiddlewareTest.cs
@@ -0,0 +1,382 @@
+// 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.Threading.Tasks;
+using Microsoft.AspNetCore.Antiforgery.Internal;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Builder.Internal;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features.Authentication;
+using Microsoft.AspNetCore.Http.Features.Authentication.Internal;
+using Microsoft.AspNetCore.Http.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Antiforgery
+{
+ // These are really more like integration tests and just verify a bunch of different
+ // reasonable combinations of authN middleware.
+ public class AntiforgeryMiddlewareTest
+ {
+ private readonly ClaimsPrincipal LoggedInUser = new ClaimsPrincipal(new ClaimsIdentity[]
+ {
+ new ClaimsIdentity("Test"),
+ });
+
+ private readonly ClaimsPrincipal LoggedInUser2 = new ClaimsPrincipal(new ClaimsIdentity[]
+ {
+ new ClaimsIdentity("Test"),
+ });
+
+ [Fact]
+ public async Task AutomaticAuthentication_Anonymous()
+ {
+ // Arrange
+ var context = Setup((app) =>
+ {
+ app.Use(next => new AutomaticAuthenticationMiddleware(next, null).Invoke);
+ app.UseAntiforgery();
+ });
+
+ var httpContext = new DefaultHttpContext();
+
+ await context.AppFunc(httpContext);
+
+ Assert.Null(context.Principal);
+ }
+
+ [Fact]
+ public async Task AutomaticAuthentication_LoggedIn_WithoutToken()
+ {
+ // Arrange
+ var context = Setup((app) =>
+ {
+ app.UseMiddleware(LoggedInUser);
+ app.UseAntiforgery();
+ });
+
+ context.Antiforgery
+ .Setup(a => a.IsRequestValidAsync(It.IsAny()))
+ .ReturnsAsync(false);
+
+ var httpContext = new DefaultHttpContext();
+
+ await context.AppFunc(httpContext);
+
+ Assert.Null(context.Principal);
+ }
+
+ [Fact]
+ public async Task AutomaticAuthentication_LoggedIn_WithValidToken()
+ {
+ // Arrange
+ var context = Setup((app) =>
+ {
+ app.UseMiddleware(LoggedInUser);
+ app.UseAntiforgery();
+ });
+
+ context.Antiforgery
+ .Setup(a => a.IsRequestValidAsync(It.IsAny()))
+ .ReturnsAsync(true);
+
+ var httpContext = new DefaultHttpContext();
+
+ await context.AppFunc(httpContext);
+
+ Assert.Same(LoggedInUser, context.Principal);
+ }
+
+ // A middleware after antiforgery in the pipeline can authenticate without going through token
+ // validation.
+ [Fact]
+ public async Task AutomaticAuthentication_LoggedIn_WithoutToken_AuthenticatedBySubsequentMiddleware()
+ {
+ // Arrange
+ var context = Setup((app) =>
+ {
+ app.UseMiddleware(LoggedInUser);
+ app.UseAntiforgery();
+ app.UseMiddleware(LoggedInUser2);
+ });
+
+ context.Antiforgery
+ .Setup(a => a.IsRequestValidAsync(It.IsAny()))
+ .ReturnsAsync(false);
+
+ var httpContext = new DefaultHttpContext();
+
+ await context.AppFunc(httpContext);
+
+ Assert.Same(LoggedInUser2, context.Principal);
+ }
+
+ [Fact]
+ public async Task PasiveAuthentication_Anonymous()
+ {
+ // Arrange
+ var context = Setup((app) =>
+ {
+ app.Use(next => new AuthenticationHandlerMiddleware(next, null).Invoke);
+ app.UseAntiforgery();
+ app.UseMiddleware();
+ });
+
+ var httpContext = new DefaultHttpContext();
+
+ await context.AppFunc(httpContext);
+
+ Assert.Null(context.Principal);
+ }
+
+ [Fact]
+ public async Task PassiveAuthentication_LoggedIn_WithoutToken()
+ {
+ // Arrange
+ var context = Setup((app) =>
+ {
+ app.UseMiddleware(LoggedInUser);
+ app.UseAntiforgery();
+ app.UseMiddleware();
+ });
+
+ context.Antiforgery
+ .Setup(a => a.ValidateRequestAsync(It.IsAny(), LoggedInUser))
+ .Throws(new AntiforgeryValidationException("error"));
+
+ var httpContext = new DefaultHttpContext();
+
+ await context.AppFunc(httpContext);
+
+ Assert.Null(context.Principal);
+ }
+
+ [Fact]
+ public async Task PassiveAuthentication_LoggedIn_WithValidToken()
+ {
+ // Arrange
+ var context = Setup((app) =>
+ {
+ app.UseMiddleware(LoggedInUser);
+ app.UseAntiforgery();
+ app.UseMiddleware();
+ });
+
+ context.Antiforgery
+ .Setup(a => a.ValidateRequestAsync(It.IsAny(), LoggedInUser))
+ .Returns(TaskCache.CompletedTask);
+
+ var httpContext = new DefaultHttpContext();
+
+ await context.AppFunc(httpContext);
+
+ Assert.Same(LoggedInUser, context.Principal);
+ }
+
+ // A middleware after antiforgery in the pipeline can authenticate without going through token
+ // validation.
+ [Fact]
+ public async Task PassiveAuthentication_LoggedIn_WithoutToken_AuthenticatedBySubsequentMiddleware()
+ {
+ // Arrange
+ var context = Setup((app) =>
+ {
+ app.UseMiddleware(LoggedInUser);
+ app.UseAntiforgery();
+ app.UseMiddleware(LoggedInUser2);
+ app.UseMiddleware();
+ });
+
+ var httpContext = new DefaultHttpContext();
+
+ await context.AppFunc(httpContext);
+
+ Assert.Same(LoggedInUser2, context.Principal);
+ }
+
+ private static IHttpAuthenticationFeature GetAuthenticationFeature(HttpContext httpContext)
+ {
+ var authentication = httpContext.Features.Get();
+ if (authentication == null)
+ {
+ authentication = new HttpAuthenticationFeature();
+ httpContext.Features.Set(authentication);
+ }
+
+ return authentication;
+ }
+
+ private static TestContext Setup(Action action)
+ {
+ var services = new ServiceCollection();
+ services.AddLogging();
+ services.AddOptions();
+
+ var antiforgery = new Mock(MockBehavior.Strict);
+ services.AddSingleton(antiforgery.Object);
+
+ var result = new TestContext();
+ result.Antiforgery = antiforgery;
+
+ var app = new ApplicationBuilder(services.BuildServiceProvider());
+ action(app);
+
+ // Capture the logged in user 'after' the middleware so we can validate it.
+ app.Run(c =>
+ {
+ result.Principal = GetAuthenticationFeature(c).User;
+ return TaskCache.CompletedTask;
+ });
+
+ result.AppFunc = app.Build();
+ return result;
+ }
+
+ private class TestContext
+ {
+ public Mock Antiforgery { get; set; }
+
+ public RequestDelegate AppFunc { get; set; }
+
+ public ClaimsPrincipal Principal { get; set; }
+ }
+
+ private class AutomaticAuthenticationMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly ClaimsPrincipal _principal;
+
+ public AutomaticAuthenticationMiddleware(RequestDelegate next, ClaimsPrincipal principal)
+ {
+ _next = next;
+ _principal = principal;
+ }
+
+ public Task Invoke(HttpContext httpContext)
+ {
+ GetAuthenticationFeature(httpContext).User = _principal;
+ return _next(httpContext);
+ }
+ }
+
+ private class AuthenticationHandlerMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly ClaimsPrincipal _principal;
+
+ public AuthenticationHandlerMiddleware(RequestDelegate next, ClaimsPrincipal principal)
+ {
+ _next = next;
+ _principal = principal;
+ }
+
+ public async Task Invoke(HttpContext httpContext)
+ {
+ var handler = new AuthenticationHandler(_principal);
+ await handler.InitializeAsync(httpContext);
+
+ try
+ {
+ await _next(httpContext);
+ }
+ finally
+ {
+ await handler.TeardownAsync();
+ }
+ }
+ }
+
+ private class AuthenticationHandler : IAuthenticationHandler
+ {
+ private readonly ClaimsPrincipal _principal;
+ private IAuthenticationHandler _priorHandler;
+ private HttpContext _httpContext;
+
+ public AuthenticationHandler(ClaimsPrincipal principal)
+ {
+ _principal = principal;
+ }
+
+ public Task InitializeAsync(HttpContext httpContext)
+ {
+ _httpContext = httpContext;
+
+ var authenticationFeature = GetAuthenticationFeature(_httpContext);
+ _priorHandler = authenticationFeature.Handler;
+ authenticationFeature.Handler = this;
+
+ return TaskCache.CompletedTask;
+ }
+
+ public Task TeardownAsync()
+ {
+ var authenticationFeature = GetAuthenticationFeature(_httpContext);
+ authenticationFeature.Handler = _priorHandler;
+
+ return TaskCache.CompletedTask;
+ }
+
+ public Task AuthenticateAsync(AuthenticateContext context)
+ {
+ if (_principal == null)
+ {
+ context.NotAuthenticated();
+ }
+ else
+ {
+ context.Authenticated(_principal, null, null);
+ }
+
+ return TaskCache.CompletedTask;
+ }
+
+ public Task ChallengeAsync(ChallengeContext context)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void GetDescriptions(DescribeSchemesContext context)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SignInAsync(SignInContext context)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SignOutAsync(SignOutContext context)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class CallAuthenticateMiddleware
+ {
+ private readonly RequestDelegate _next;
+
+ public CallAuthenticateMiddleware(RequestDelegate next)
+ {
+ _next = next;
+ }
+
+ public async Task Invoke(HttpContext httpContext)
+ {
+ var authenticationFeature = GetAuthenticationFeature(httpContext);
+
+ var authenticateContext = new AuthenticateContext("Test");
+ await httpContext.Authentication.AuthenticateAsync(authenticateContext);
+
+ if (authenticateContext.Accepted)
+ {
+ authenticationFeature.User = authenticateContext.Principal;
+ }
+
+ await _next(httpContext);
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/AntiforgeryAuthenticationHandlerTest.cs b/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/AntiforgeryAuthenticationHandlerTest.cs
new file mode 100644
index 0000000000..670f818bf7
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/AntiforgeryAuthenticationHandlerTest.cs
@@ -0,0 +1,293 @@
+// 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.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features.Authentication;
+using Microsoft.AspNetCore.Http.Features.Authentication.Internal;
+using Microsoft.AspNetCore.Http.Internal;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Antiforgery.Internal
+{
+ public class AntiforgeryAuthenticationHandlerTest
+ {
+ [Fact]
+ public async Task IntializeAsync_NoOp_WhenAnonymous()
+ {
+ // Arrange
+ var antiforgery = new Mock(MockBehavior.Strict);
+ var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
+
+ antiforgery
+ .Setup(a => a.IsRequestValidAsync(It.IsAny()))
+ .ReturnsAsync(false)
+ .Verifiable();
+
+ var httpContext = new DefaultHttpContext();
+
+ // Act
+ await handler.InitializeAsync(httpContext);
+
+ // Assert
+ antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny()), Times.Never());
+ }
+
+ [Fact]
+ public async Task IntializeAsync_ValidatesRequest_WhenLoggedIn()
+ {
+ // Arrange
+ var antiforgery = new Mock(MockBehavior.Strict);
+ var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
+
+ antiforgery
+ .Setup(a => a.IsRequestValidAsync(It.IsAny()))
+ .ReturnsAsync(true)
+ .Verifiable();
+
+ var httpContext = new DefaultHttpContext();
+
+ var authenticationFeature = new HttpAuthenticationFeature();
+ httpContext.Features.Set(authenticationFeature);
+ authenticationFeature.User = new ClaimsPrincipal();
+
+ // Act
+ await handler.InitializeAsync(httpContext);
+
+ // Assert
+ antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task IntializeAsync_ClearsUser_WhenInvalid()
+ {
+ // Arrange
+ var antiforgery = new Mock(MockBehavior.Strict);
+ var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
+
+ antiforgery
+ .Setup(a => a.IsRequestValidAsync(It.IsAny()))
+ .ReturnsAsync(false)
+ .Verifiable();
+
+ var httpContext = new DefaultHttpContext();
+
+ var authenticationFeature = new HttpAuthenticationFeature();
+ httpContext.Features.Set(authenticationFeature);
+ authenticationFeature.User = new ClaimsPrincipal();
+
+ // Act
+ await handler.InitializeAsync(httpContext);
+
+ // Assert
+ Assert.Null(authenticationFeature.User);
+ antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task IntializeAsync_AttachesAuthorizationHandler()
+ {
+ // Arrange
+ var antiforgery = new Mock(MockBehavior.Strict);
+ var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
+
+ antiforgery
+ .Setup(a => a.IsRequestValidAsync(It.IsAny()))
+ .ReturnsAsync(false)
+ .Verifiable();
+
+ var httpContext = new DefaultHttpContext();
+
+ var authenticationFeature = new HttpAuthenticationFeature();
+ httpContext.Features.Set(authenticationFeature);
+
+ // Act
+ await handler.InitializeAsync(httpContext);
+
+ // Assert
+ Assert.Same(handler, authenticationFeature.Handler);
+ antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny()), Times.Never());
+ }
+
+ [Fact]
+ public async Task AuthenticateAsync_NoPriorHandler_NoOp()
+ {
+ // Arrange
+ var antiforgery = new Mock(MockBehavior.Strict);
+ var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
+
+ antiforgery
+ .Setup(a => a.IsRequestValidAsync(It.IsAny()))
+ .ReturnsAsync(false)
+ .Verifiable();
+
+ antiforgery
+ .Setup(a => a.ValidateRequestAsync(It.IsAny(), It.IsAny()))
+ .Verifiable();
+
+ var httpContext = new DefaultHttpContext();
+
+ var authenticationFeature = new HttpAuthenticationFeature();
+ httpContext.Features.Set(authenticationFeature);
+
+ await handler.InitializeAsync(httpContext);
+
+ var authenticateContext = new AuthenticateContext("Test");
+
+ // Act
+ await handler.AuthenticateAsync(authenticateContext);
+
+ // Assert
+ Assert.False(authenticateContext.Accepted);
+
+ antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny()), Times.Never());
+ antiforgery.Verify(
+ a => a.ValidateRequestAsync(It.IsAny(), It.IsAny()),
+ Times.Never());
+ }
+
+ [Fact]
+ public async Task AuthenticateAsync_PriorHandlerDoesNotAuthenticate_NoOp()
+ {
+ // Arrange
+ var antiforgery = new Mock(MockBehavior.Strict);
+ var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
+
+ antiforgery
+ .Setup(a => a.IsRequestValidAsync(It.IsAny()))
+ .ReturnsAsync(false)
+ .Verifiable();
+
+ antiforgery
+ .Setup(a => a.ValidateRequestAsync(It.IsAny(), It.IsAny()))
+ .Verifiable();
+
+ var httpContext = new DefaultHttpContext();
+
+ var authenticationFeature = new HttpAuthenticationFeature();
+ httpContext.Features.Set(authenticationFeature);
+ var priorHandler = new Mock(MockBehavior.Strict);
+ authenticationFeature.Handler = priorHandler.Object;
+
+ priorHandler
+ .Setup(h => h.AuthenticateAsync(It.IsAny()))
+ .Returns(TaskCache.CompletedTask)
+ .Callback(c => c.NotAuthenticated());
+
+ await handler.InitializeAsync(httpContext);
+
+ var authenticateContext = new AuthenticateContext("Test");
+
+ // Act
+ await handler.AuthenticateAsync(authenticateContext);
+
+ // Assert
+ Assert.True(authenticateContext.Accepted);
+ Assert.Null(authenticateContext.Principal);
+
+ antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny()), Times.Never());
+ antiforgery.Verify(
+ a => a.ValidateRequestAsync(It.IsAny(), It.IsAny()),
+ Times.Never());
+ }
+
+ [Fact]
+ public async Task AuthenticateAsync_PriorHandlerSetsPrincipal_Valid()
+ {
+ // Arrange
+ var antiforgery = new Mock(MockBehavior.Strict);
+ var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
+
+ var principal = new ClaimsPrincipal();
+
+ antiforgery
+ .Setup(a => a.IsRequestValidAsync(It.IsAny()))
+ .ReturnsAsync(false)
+ .Verifiable();
+
+ antiforgery
+ .Setup(a => a.ValidateRequestAsync(It.IsAny(), principal))
+ .Returns(TaskCache.CompletedTask)
+ .Verifiable();
+
+ var httpContext = new DefaultHttpContext();
+
+ var authenticationFeature = new HttpAuthenticationFeature();
+ httpContext.Features.Set(authenticationFeature);
+ var priorHandler = new Mock(MockBehavior.Strict);
+ authenticationFeature.Handler = priorHandler.Object;
+
+ priorHandler
+ .Setup(h => h.AuthenticateAsync(It.IsAny()))
+ .Returns(TaskCache.CompletedTask)
+ .Callback(c => c.Authenticated(principal, null, null));
+
+ await handler.InitializeAsync(httpContext);
+
+ var authenticateContext = new AuthenticateContext("Test");
+
+ // Act
+ await handler.AuthenticateAsync(authenticateContext);
+
+ // Assert
+ Assert.True(authenticateContext.Accepted);
+ Assert.Same(principal, authenticateContext.Principal);
+
+ antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny()), Times.Never());
+ antiforgery.Verify(
+ a => a.ValidateRequestAsync(It.IsAny(), principal),
+ Times.Once());
+ }
+
+ [Fact]
+ public async Task AuthenticateAsync_PriorHandlerSetsPrincipal_Invalid()
+ {
+ // Arrange
+ var antiforgery = new Mock(MockBehavior.Strict);
+ var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
+
+ var principal = new ClaimsPrincipal();
+
+ antiforgery
+ .Setup(a => a.IsRequestValidAsync(It.IsAny()))
+ .ReturnsAsync(false)
+ .Verifiable();
+
+ antiforgery
+ .Setup(a => a.ValidateRequestAsync(It.IsAny(), principal))
+ .Throws(new AntiforgeryValidationException("invalid"))
+ .Verifiable();
+
+ var httpContext = new DefaultHttpContext();
+
+ var authenticationFeature = new HttpAuthenticationFeature();
+ httpContext.Features.Set(authenticationFeature);
+ var priorHandler = new Mock(MockBehavior.Strict);
+ authenticationFeature.Handler = priorHandler.Object;
+
+ priorHandler
+ .Setup(h => h.AuthenticateAsync(It.IsAny()))
+ .Returns(TaskCache.CompletedTask)
+ .Callback(c => c.Authenticated(principal, null, null));
+
+ await handler.InitializeAsync(httpContext);
+
+ var authenticateContext = new AuthenticateContext("Test");
+
+ // Act
+ await handler.AuthenticateAsync(authenticateContext);
+
+ // Assert
+ Assert.True(authenticateContext.Accepted);
+ Assert.Null(authenticateContext.Principal);
+ Assert.NotNull(authenticateContext.Error);
+
+ antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny()), Times.Never());
+ antiforgery.Verify(
+ a => a.ValidateRequestAsync(It.IsAny(), principal),
+ Times.Once());
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTest.cs b/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTest.cs
index 670b7f607b..2d10221a41 100644
--- a/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTest.cs
+++ b/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTest.cs
@@ -16,6 +16,22 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
{
public class DefaultAntiforgeryTest
{
+ public static TheoryData SafeHttpMethods => new TheoryData()
+ {
+ "GeT",
+ "HEAD",
+ "options",
+ "TrAcE",
+ };
+
+ public static TheoryData UnsafeHttpMethods => new TheoryData()
+ {
+ "PUT",
+ "post",
+ "Delete",
+ "Custom",
+ };
+
[Fact]
public async Task ChecksSSL_ValidateRequestAsync_Throws()
{
@@ -36,6 +52,26 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
exception.Message);
}
+ [Fact]
+ public async Task ChecksSSL_ValidateRequestAsync_WithPrincipal_Throws()
+ {
+ // Arrange
+ var httpContext = GetHttpContext();
+ var options = new AntiforgeryOptions()
+ {
+ RequireSsl = true
+ };
+ var antiforgery = GetAntiforgery(httpContext, options);
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(
+ () => antiforgery.ValidateRequestAsync(httpContext, new ClaimsPrincipal()));
+ Assert.Equal(
+ @"The antiforgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " +
+ "but the current request is not an SSL request.",
+ exception.Message);
+ }
+
[Fact]
public async Task ChecksSSL_IsRequestValidAsync_Throws()
{
@@ -57,6 +93,27 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
exception.Message);
}
+ [Fact]
+ public async Task ChecksSSL_IsRequestValidAsync_WithPrincipal_Throws()
+ {
+ // Arrange
+ var httpContext = GetHttpContext();
+ var options = new AntiforgeryOptions()
+ {
+ RequireSsl = true
+ };
+
+ var antiforgery = GetAntiforgery(httpContext, options);
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(
+ () => antiforgery.IsRequestValidAsync(httpContext, new ClaimsPrincipal()));
+ Assert.Equal(
+ @"The antiforgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " +
+ "but the current request is not an SSL request.",
+ exception.Message);
+ }
+
[Fact]
public void ChecksSSL_GetAndStoreTokens_Throws()
{
@@ -420,6 +477,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
+ It.IsAny(),
context.TestTokenSet.OldCookieToken,
context.TestTokenSet.RequestToken,
out message))
@@ -454,6 +512,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
+ It.IsAny(),
context.TestTokenSet.OldCookieToken,
context.TestTokenSet.RequestToken,
out message))
@@ -497,6 +556,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
+ It.IsAny(),
contextAccessor.Value.CookieToken,
contextAccessor.Value.RequestToken,
out message))
@@ -522,10 +582,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
}
[Theory]
- [InlineData("GeT")]
- [InlineData("HEAD")]
- [InlineData("options")]
- [InlineData("TrAcE")]
+ [MemberData(nameof(SafeHttpMethods))]
public async Task IsRequestValidAsync_SkipsAntiforgery_ForSafeHttpMethods(string httpMethod)
{
// Arrange
@@ -536,6 +593,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
+ It.IsAny(),
It.IsAny(),
It.IsAny(),
out message))
@@ -552,6 +610,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Verify(o => o.TryValidateTokenSet(
context.HttpContext,
+ It.IsAny(),
It.IsAny(),
It.IsAny(),
out message),
@@ -559,10 +618,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
}
[Theory]
- [InlineData("PUT")]
- [InlineData("post")]
- [InlineData("Delete")]
- [InlineData("Custom")]
+ [MemberData(nameof(UnsafeHttpMethods))]
public async Task IsRequestValidAsync_ValidatesAntiforgery_ForNonSafeHttpMethods(string httpMethod)
{
// Arrange
@@ -573,6 +629,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
+ It.IsAny(),
It.IsAny(),
It.IsAny(),
out message))
@@ -589,6 +646,68 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator.Verify();
}
+ [Fact]
+ public async Task IsRequestValidAsync_UsesPrincipalFromHttpContext()
+ {
+ // Arrange
+ var context = CreateMockContext(new AntiforgeryOptions());
+ context.HttpContext.Request.Method = "POST";
+
+ var principal = new ClaimsPrincipal();
+ context.HttpContext.User = principal;
+
+ string message;
+ context.TokenGenerator
+ .Setup(o => o.TryValidateTokenSet(
+ context.HttpContext,
+ principal,
+ 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 IsRequestValidAsync_UsesPassedInPrincipal()
+ {
+ // Arrange
+ var context = CreateMockContext(new AntiforgeryOptions());
+ context.HttpContext.Request.Method = "POST";
+
+ var principal = new ClaimsPrincipal();
+ context.HttpContext.User = new ClaimsPrincipal(); // This should be ignored.
+
+ string message;
+ context.TokenGenerator
+ .Setup(o => o.TryValidateTokenSet(
+ context.HttpContext,
+ principal,
+ It.IsAny(),
+ It.IsAny(),
+ out message))
+ .Returns(true)
+ .Verifiable();
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ var result = await antiforgery.IsRequestValidAsync(context.HttpContext, principal);
+
+ // Assert
+ Assert.True(result);
+ context.TokenGenerator.Verify();
+ }
+
[Fact]
public async Task ValidateRequestAsync_FromStore_Failure()
{
@@ -600,6 +719,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
+ It.IsAny(),
context.TestTokenSet.OldCookieToken,
context.TestTokenSet.RequestToken,
out message))
@@ -632,6 +752,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
+ It.IsAny(),
context.TestTokenSet.OldCookieToken,
context.TestTokenSet.RequestToken,
out message))
@@ -776,6 +897,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
+ It.IsAny(),
contextAccessor.Value.CookieToken,
contextAccessor.Value.RequestToken,
out message))
@@ -799,6 +921,129 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
Times.Never);
}
+ [Theory]
+ [MemberData(nameof(SafeHttpMethods))]
+ public async Task ValidateRequestAsync_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(),
+ It.IsAny(),
+ out message))
+ .Returns(false)
+ .Verifiable();
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ await antiforgery.ValidateRequestAsync(context.HttpContext);
+
+ // Assert
+ context.TokenGenerator
+ .Verify(o => o.TryValidateTokenSet(
+ context.HttpContext,
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ out message),
+ Times.Never);
+ }
+
+ [Theory]
+ [MemberData(nameof(UnsafeHttpMethods))]
+ public async Task ValidateRequestAsync_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(),
+ It.IsAny(),
+ out message))
+ .Returns(true)
+ .Verifiable();
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ await antiforgery.ValidateRequestAsync(context.HttpContext);
+
+ // Assert
+ context.TokenGenerator.Verify();
+ }
+
+ [Fact]
+ public async Task ValidateRequestAsync_UsesPrincipalFromHttpContext()
+ {
+ // Arrange
+ var context = CreateMockContext(new AntiforgeryOptions());
+ context.HttpContext.Request.Method = "POST";
+
+ var principal = new ClaimsPrincipal();
+ context.HttpContext.User = principal;
+
+ string message;
+ context.TokenGenerator
+ .Setup(o => o.TryValidateTokenSet(
+ context.HttpContext,
+ principal,
+ It.IsAny(),
+ It.IsAny(),
+ out message))
+ .Returns(true)
+ .Verifiable();
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ await antiforgery.ValidateRequestAsync(context.HttpContext);
+
+ // Assert
+ context.TokenGenerator.Verify();
+ }
+
+ [Fact]
+ public async Task ValidateRequestAsync_UsesPassedInPrincipal()
+ {
+ // Arrange
+ var context = CreateMockContext(new AntiforgeryOptions());
+ context.HttpContext.Request.Method = "POST";
+
+ var principal = new ClaimsPrincipal();
+ context.HttpContext.User = new ClaimsPrincipal(); // This should be ignored.
+
+ string message;
+ context.TokenGenerator
+ .Setup(o => o.TryValidateTokenSet(
+ context.HttpContext,
+ principal,
+ It.IsAny(),
+ It.IsAny(),
+ out message))
+ .Returns(true)
+ .Verifiable();
+
+ var antiforgery = GetAntiforgery(context);
+
+ // Act
+ await antiforgery.ValidateRequestAsync(context.HttpContext, principal);
+
+ // Assert
+ context.TokenGenerator.Verify();
+ }
+
[Theory]
[InlineData(false, "SAMEORIGIN")]
[InlineData(true, null)]
@@ -1045,6 +1290,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
mockGenerator
.Setup(o => o.GenerateRequestToken(
httpContext,
+ It.IsAny(),
useOldCookie ? testTokenSet.OldCookieToken : testTokenSet.NewCookieToken))
.Returns(testTokenSet.RequestToken);
diff --git a/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenGeneratorTest.cs b/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenGeneratorTest.cs
index 5509726a38..1720d93b95 100644
--- a/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenGeneratorTest.cs
+++ b/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenGeneratorTest.cs
@@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var cookieToken = new AntiforgeryToken() { IsCookieToken = false };
var httpContext = new DefaultHttpContext();
- httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
+ var principal = new ClaimsPrincipal(new ClaimsIdentity());
Assert.False(httpContext.User.Identity.IsAuthenticated);
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
@@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act & Assert
ExceptionAssert.ThrowsArgument(
- () => tokenProvider.GenerateRequestToken(httpContext, cookieToken),
+ () => tokenProvider.GenerateRequestToken(httpContext, principal, cookieToken),
"cookieToken",
"The antiforgery cookie token is invalid.");
}
@@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var httpContext = new DefaultHttpContext();
- httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
+ var principal = new ClaimsPrincipal(new ClaimsIdentity());
Assert.False(httpContext.User.Identity.IsAuthenticated);
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
@@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
additionalDataProvider: null);
// Act
- var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
+ var fieldToken = tokenProvider.GenerateRequestToken(httpContext, principal, cookieToken);
// Assert
Assert.NotNull(fieldToken);
@@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
};
var httpContext = new DefaultHttpContext();
- httpContext.User = new ClaimsPrincipal(new MyAuthenticatedIdentityWithoutUsername());
+ var principal = new ClaimsPrincipal(new MyAuthenticatedIdentityWithoutUsername());
var options = new AntiforgeryOptions();
var claimUidExtractor = new Mock().Object;
@@ -95,7 +95,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act & assert
var exception = Assert.Throws(
- () => tokenProvider.GenerateRequestToken(httpContext, cookieToken));
+ () => tokenProvider.GenerateRequestToken(httpContext, principal, cookieToken));
Assert.Equal(
"The provided identity of type " +
$"'{typeof(MyAuthenticatedIdentityWithoutUsername).FullName}' " +
@@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var httpContext = new DefaultHttpContext();
- httpContext.User = new ClaimsPrincipal(new MyAuthenticatedIdentityWithoutUsername());
+ var principal = new ClaimsPrincipal(new MyAuthenticatedIdentityWithoutUsername());
var mockAdditionalDataProvider = new Mock();
mockAdditionalDataProvider.Setup(o => o.GetAdditionalData(httpContext))
@@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
additionalDataProvider: mockAdditionalDataProvider.Object);
// Act
- var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
+ var fieldToken = tokenProvider.GenerateRequestToken(httpContext, principal, cookieToken);
// Assert
Assert.NotNull(fieldToken);
@@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
var identity = GetAuthenticatedIdentity("some-identity");
var httpContext = new DefaultHttpContext();
- httpContext.User = new ClaimsPrincipal(identity);
+ var principal = new ClaimsPrincipal(identity);
byte[] data = new byte[256 / 8];
using (var rng = RandomNumberGenerator.Create())
@@ -166,7 +166,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
additionalDataProvider: null);
// Act
- var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
+ var fieldToken = tokenProvider.GenerateRequestToken(httpContext, principal, cookieToken);
// Assert
Assert.NotNull(fieldToken);
@@ -190,7 +190,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
mockIdentity.Setup(o => o.Name)
.Returns("my-username");
- httpContext.User = new ClaimsPrincipal(mockIdentity.Object);
+ var principal = new ClaimsPrincipal(mockIdentity.Object);
var claimUidExtractor = new Mock().Object;
@@ -199,7 +199,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
additionalDataProvider: null);
// Act
- var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
+ var fieldToken = tokenProvider.GenerateRequestToken(httpContext, principal, cookieToken);
// Assert
Assert.NotNull(fieldToken);
@@ -272,7 +272,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
{
// Arrange
var httpContext = new DefaultHttpContext();
- httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
+ var principal = new ClaimsPrincipal(new ClaimsIdentity());
var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
@@ -283,7 +283,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act & Assert
string message;
var ex = Assert.Throws(
- () => tokenProvider.TryValidateTokenSet(httpContext, null, fieldtoken, out message));
+ () => tokenProvider.TryValidateTokenSet(httpContext, principal, 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);
@@ -294,7 +294,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
{
// Arrange
var httpContext = new DefaultHttpContext();
- httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
+ var principal = new ClaimsPrincipal(new ClaimsIdentity());
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
@@ -306,7 +306,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act & Assert
string message;
var ex = Assert.Throws(
- () => tokenProvider.TryValidateTokenSet(httpContext, cookieToken, null, out message));
+ () => tokenProvider.TryValidateTokenSet(httpContext, principal, 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);
@@ -317,7 +317,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
{
// Arrange
var httpContext = new DefaultHttpContext();
- httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
+ var principal = new ClaimsPrincipal(new ClaimsIdentity());
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
@@ -332,7 +332,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
- var result = tokenProvider.TryValidateTokenSet(httpContext, fieldtoken, fieldtoken, out message);
+ var result = tokenProvider.TryValidateTokenSet(httpContext, principal, fieldtoken, fieldtoken, out message);
// Assert
Assert.False(result);
@@ -344,7 +344,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
{
// Arrange
var httpContext = new DefaultHttpContext();
- httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
+ var principal = new ClaimsPrincipal(new ClaimsIdentity());
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
@@ -359,7 +359,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
- var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, cookieToken, out message);
+ var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, cookieToken, out message);
// Assert
Assert.False(result);
@@ -371,7 +371,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
{
// Arrange
var httpContext = new DefaultHttpContext();
- httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
+ var principal = new ClaimsPrincipal(new ClaimsIdentity());
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
@@ -384,7 +384,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
- var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
+ var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, fieldtoken, out message);
// Assert
Assert.False(result);
@@ -400,7 +400,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var httpContext = new DefaultHttpContext();
var identity = GetAuthenticatedIdentity(identityUsername);
- httpContext.User = new ClaimsPrincipal(identity);
+ var principal = new ClaimsPrincipal(identity);
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken()
@@ -424,7 +424,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
- var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
+ var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, fieldtoken, out message);
// Assert
Assert.False(result);
@@ -437,7 +437,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var httpContext = new DefaultHttpContext();
var identity = GetAuthenticatedIdentity("the-user");
- httpContext.User = new ClaimsPrincipal(identity);
+ var principal = new ClaimsPrincipal(identity);
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken()
@@ -462,7 +462,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
- var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
+ var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, fieldtoken, out message);
// Assert
Assert.False(result);
@@ -475,7 +475,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var httpContext = new DefaultHttpContext();
var identity = new ClaimsIdentity();
- httpContext.User = new ClaimsPrincipal(identity);
+ var principal = new ClaimsPrincipal(identity);
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken()
@@ -499,7 +499,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
- var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
+ var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, fieldtoken, out message);
// Assert
Assert.False(result);
@@ -512,7 +512,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var httpContext = new DefaultHttpContext();
var identity = new ClaimsIdentity();
- httpContext.User = new ClaimsPrincipal(identity);
+ var principal = new ClaimsPrincipal(identity);
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken()
@@ -533,7 +533,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
- var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
+ var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, fieldtoken, out message);
// Assert
Assert.True(result);
@@ -546,7 +546,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var httpContext = new DefaultHttpContext();
var identity = GetAuthenticatedIdentity("the-user");
- httpContext.User = new ClaimsPrincipal(identity);
+ var principal = new ClaimsPrincipal(identity);
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken()
@@ -567,7 +567,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
- var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
+ var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, fieldtoken, out message);
// Assert
Assert.True(result);
@@ -580,7 +580,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var httpContext = new DefaultHttpContext();
var identity = GetAuthenticatedIdentity("the-user");
- httpContext.User = new ClaimsPrincipal(identity);
+ var principal = new ClaimsPrincipal(identity);
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken()
@@ -600,7 +600,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
- var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
+ var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, fieldtoken, out message);
// Assert
Assert.True(result);