Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto Validate AntiForgery Token for HTTP API Requests #5728

Merged
merged 8 commits into from
Oct 7, 2020
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetStyles
@using Volo.Abp.MultiTenancy
@using Volo.Abp.Localization
@inject IAbpAntiForgeryManager AbpAntiForgeryManager
@inject IBrandingProvider BrandingProvider
@inject IOptions<AbpMultiTenancyOptions> MultiTenancyOptions
@inject ICurrentTenant CurrentTenant
Expand All @@ -22,7 +21,6 @@

@{
Layout = null;
AbpAntiForgeryManager.SetCookie();
var containerClass = ViewBag.FluidLayout == true ? "container-fluid" : "container"; //TODO: Better and type-safe options
var rtl = CultureHelper.IsRtl ? "rtl" : string.Empty;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetScripts
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetStyles
@using Volo.Abp.Localization
@inject IAbpAntiForgeryManager AbpAntiForgeryManager
@inject IBrandingProvider BrandingProvider
@inject IPageLayout PageLayout
@{
Layout = null;
AbpAntiForgeryManager.SetCookie();
var containerClass = ViewBag.FluidLayout == true ? "container-fluid" : "container"; //TODO: Better and type-safe options

var pageTitle = ViewBag.Title == null ? BrandingProvider.AppName : ViewBag.Title; //TODO: Discard to get from Title
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetScripts
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetStyles
@using Volo.Abp.Localization
@inject IAbpAntiForgeryManager AbpAntiForgeryManager
@inject IBrandingProvider BrandingProvider
@inject IPageLayout PageLayout
@{
Layout = null;
AbpAntiForgeryManager.SetCookie();
var containerClass = ViewBag.FluidLayout == true ? "container-fluid" : "container"; //TODO: Better and type-safe options

var pageTitle = ViewBag.Title == null ? BrandingProvider.AppName : ViewBag.Title; //TODO: Discard to get from Title
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using Volo.Abp.ApiVersioning;
using Volo.Abp.AspNetCore.Mvc.AntiForgery;
using Volo.Abp.AspNetCore.Mvc.ApiExploring;
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.AspNetCore.Mvc.DataAnnotations;
Expand Down Expand Up @@ -94,7 +95,10 @@ public override void ConfigureServices(ServiceConfigurationContext context)
}
});

var mvcCoreBuilder = context.Services.AddMvcCore();
var mvcCoreBuilder = context.Services.AddMvcCore(options =>
{
options.Filters.Add(new AbpAutoValidateAntiforgeryTokenAttribute());
});
context.Services.ExecutePreConfiguredActions(mvcCoreBuilder);

var abpMvcDataAnnotationsLocalizationOptions = context.Services
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;

namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
{
public class AbpAntiForgeryCookieNameProvider : ITransientDependency
{
private readonly IOptionsSnapshot<CookieAuthenticationOptions> _namedOptionsAccessor;
private readonly AbpAntiForgeryOptions _abpAntiForgeryOptions;

public AbpAntiForgeryCookieNameProvider(
IOptionsSnapshot<CookieAuthenticationOptions> namedOptionsAccessor,
IOptions<AbpAntiForgeryOptions> abpAntiForgeryOptions)
{
_namedOptionsAccessor = namedOptionsAccessor;
_abpAntiForgeryOptions = abpAntiForgeryOptions.Value;
}

public virtual string GetAuthCookieNameOrNull()
{
if (_abpAntiForgeryOptions.AuthCookieSchemaName == null)
{
return null;
}

return _namedOptionsAccessor.Get(_abpAntiForgeryOptions.AuthCookieSchemaName)?.Cookie?.Name;
}

public virtual string GetAntiForgeryCookieNameOrNull()
{
return _abpAntiForgeryOptions.TokenCookie.Name;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ public static class AbpAntiForgeryManagerAspNetCoreExtensions
{
public static void SetCookie(this IAbpAntiForgeryManager manager)
{
manager.HttpContext.Response.Cookies.Append(manager.Options.TokenCookieName, manager.GenerateToken());
manager.HttpContext.Response.Cookies.Append(
manager.Options.TokenCookie.Name,
manager.GenerateToken(),
manager.Options.TokenCookie.Build(manager.HttpContext)
);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,68 @@
namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;

namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
{
public class AbpAntiForgeryOptions
{
/// <summary>
/// Get/sets cookie name to transfer Anti Forgery token between server and client.
/// Default value: "XSRF-TOKEN".
/// Use to set the cookie options to transfer Anti Forgery token between server and client.
/// Default name of the cookie: "XSRF-TOKEN".
/// </summary>
public string TokenCookieName { get; set; }
public CookieBuilder TokenCookie { get; }

/// <summary>
/// Get/sets header name to transfer Anti Forgery token from client to the server.
/// Default value: "X-XSRF-TOKEN".
/// Used to find auth cookie when validating Anti Forgery token.
/// Default value: "Identity.Application".
/// </summary>
public string TokenHeaderName { get; set; }
public string AuthCookieSchemaName { get; set; }

/// <summary>
/// Default value: true.
/// </summary>
public bool AutoValidate { get; set; } = true;

/// <summary>
/// A predicate to filter types to auto-validate.
/// Return true to select the type to validate.
/// Default: returns true for all given types.
/// </summary>
[NotNull]
public Predicate<Type> AutoValidateFilter
{
get => _autoValidateFilter;
set => _autoValidateFilter = Check.NotNull(value, nameof(value));
}
private Predicate<Type> _autoValidateFilter;

/// <summary>
/// Default methods: "GET", "HEAD", "TRACE", "OPTIONS".
/// </summary>
[NotNull]
public HashSet<string> AutoValidateIgnoredHttpMethods
{
get => _autoValidateIgnoredHttpMethods;
set => _autoValidateIgnoredHttpMethods = Check.NotNull(value, nameof(value));
}
private HashSet<string> _autoValidateIgnoredHttpMethods;

public AbpAntiForgeryOptions()
{
TokenCookieName = "XSRF-TOKEN";
TokenHeaderName = "X-XSRF-TOKEN";
AutoValidateFilter = type => true;

TokenCookie = new CookieBuilder
{
Name = "XSRF-TOKEN",
HttpOnly = false,
IsEssential = true,
Expiration = TimeSpan.FromDays(3650) //10 years!
};

AuthCookieSchemaName = "Identity.Application";

AutoValidateIgnoredHttpMethods = new HashSet<string> {"GET", "HEAD", "TRACE", "OPTIONS"};
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;

namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AbpAutoValidateAntiforgeryTokenAttribute : Attribute, IFilterFactory, IOrderedFilter
{
/// <summary>
/// Gets the order value for determining the order of execution of filters. Filters execute in
/// ascending numeric value of the <see cref="Order"/> property.
/// </summary>
/// <remarks>
/// <para>
/// Filters are executed in a sequence determined by an ascending sort of the <see cref="Order"/> property.
/// </para>
/// <para>
/// The default Order for this attribute is 1000 because it must run after any filter which does authentication
/// or login in order to allow them to behave as expected (ie Unauthenticated or Redirect instead of 400).
/// </para>
/// <para>
/// Look at <see cref="IOrderedFilter.Order"/> for more detailed info.
/// </para>
/// </remarks>
public int Order { get; set; } = 1000;

/// <inheritdoc />
public bool IsReusable => true;

/// <inheritdoc />
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredService<AbpAutoValidateAntiforgeryTokenAuthorizationFilter>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;

namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
{
public class AbpAutoValidateAntiforgeryTokenAuthorizationFilter : AbpValidateAntiforgeryTokenAuthorizationFilter, ITransientDependency
{
private readonly AbpAntiForgeryOptions _options;

public AbpAutoValidateAntiforgeryTokenAuthorizationFilter(
IAntiforgery antiforgery,
AbpAntiForgeryCookieNameProvider antiForgeryCookieNameProvider,
IOptions<AbpAntiForgeryOptions> options,
ILogger<AbpValidateAntiforgeryTokenAuthorizationFilter> logger)
: base(
antiforgery,
antiForgeryCookieNameProvider,
logger)
{
_options = options.Value;
}

protected override bool ShouldValidate(AuthorizationFilterContext context)
{
if (!_options.AutoValidate)
{
return false;
}

if(context.ActionDescriptor.IsControllerAction())
{
var controllerType = context.ActionDescriptor
.AsControllerActionDescriptor()
.ControllerTypeInfo
.AsType();

if (!_options.AutoValidateFilter(controllerType))
{
return false;
}
}

if (IsIgnoredHttpMethod(context))
{
return false;
}

return base.ShouldValidate(context);
}

protected virtual bool IsIgnoredHttpMethod(AuthorizationFilterContext context)
{
return context.HttpContext
.Request
.Method
.ToUpperInvariant()
.IsIn(_options.AutoValidateIgnoredHttpMethods);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;

namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AbpValidateAntiForgeryTokenAttribute : Attribute, IFilterFactory, IOrderedFilter
{
/// <summary>
/// Gets the order value for determining the order of execution of filters. Filters execute in
/// ascending numeric value of the <see cref="Order"/> property.
/// </summary>
/// <remarks>
/// <para>
/// Filters are executed in an ordering determined by an ascending sort of the <see cref="Order"/> property.
/// </para>
/// <para>
/// The default Order for this attribute is 1000 because it must run after any filter which does authentication
/// or login in order to allow them to behave as expected (ie Unauthenticated or Redirect instead of 400).
/// </para>
/// <para>
/// Look at <see cref="IOrderedFilter.Order"/> for more detailed info.
/// </para>
/// </remarks>
public int Order { get; set; } = 1000;

/// <inheritdoc />
public bool IsReusable => true;

/// <inheritdoc />
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredService<AbpValidateAntiforgeryTokenAuthorizationFilter>();
}
}
}
Loading