Tag Archives: ASP.NET CORE

ASP.NET Core : Overriding controller AuthorizeAttribute for just one action

Standard

In Asp.Net Core, creating custom authorize attributes is over. You’d better deal with policies now. If you want to override the controller AuthorizeAttribute for just one action with a stronger policy, nothing simpler :

[Authorize]
public class AuthorizedController : Controller
{
    [HttpGet("do-authorized-stuff/{id}")]
    public IActionResult Whatever(int id)
    { }

    [Authorize(Consts.AdminOnly)]
    [HttpGet("do-admin-stuff/{id}")]
    public IActionResult WhateverAdminStuff(int id)
    {
    }

}

But what if you want to override it with a weaker policy ? For example you want to override the admin policy just for one action which would be available to any authenticated user like so :

[Authorize(Consts.AdminOnly)]
public class AuthorizedController : Controller
{

    [Authorize] //admin policy is still applied :o(
    [HttpGet("do-authorized-stuff/{id}")]
    public IActionResult Whatever(int id)
    { }

    [HttpGet("do-admin-stuff/{id}")] //admin policy is applied
    public IActionResult WhateverAdminStuff(int id)
    { }

This won’t work as policies are accumulated, in other words it’s an AND not an OR.
Still, Asp.Net Core has a lot of possibilities and you could simply do in Startup.cs file :

services.AddAuthorization(options =>
{
    var simplyAuthenticated = new AuthorizationPolicy(new DenyAnonymousAuthorizationRequirementOverrideOthers().Yield(), new List<string>());
    options.AddPolicy(name: PolicyConsts.AuthenticatedOnlyOverridePolicyName, policy: simplyAuthenticated);
    options.AddPolicy(name: PolicyConsts.Admin, configurePolicy: policy => policy.RequireAssertion(e =>
    {
        if (e.Resource is AuthorizationFilterContext afc)
        {
            var noPolicy = afc.Filters.OfType<AuthorizeFilter>().Any(p => p.Policy.Requirements.Count == 1 && p.Policy.Requirements.Single() is DenyAnonymousAuthorizationRequirementOverrideOthers);
            if (noPolicy)
                return true;
        }
        return e.User.IsInRole(Consts.Admin);
    }));

}

and here’s the custom DenyAnonymousAuthorizationRequirementOverrideOthers :


namespace Whatever
{
    using Microsoft.AspNetCore.Authorization.Infrastructure;

    public class DenyAnonymousAuthorizationRequirementOverrideOthers : DenyAnonymousAuthorizationRequirement
    { }
}

 

and here’s the controller :


[Authorize(PolicyConsts.AdminOnly)]
public class AuthorizedController : Controller
{

    [Authorize(PolicyConsts.AuthenticatedOnlyOverride)]
    [HttpGet("do-authorized-stuff/{id}")]//admin policy is bypassed thanks to our startup.cs code
    public IActionResult Whatever(int id)
    { }

    [HttpGet("do-admin-stuff/{id}")] //admin policy is applied coming from controller
    public IActionResult WhateverAdminStuff(int id)
    { }
}

And you can add any custom policy you want and make it override others in the RequireAssertion Func of other policies.

Hope it helps, see ya !

Advertisements

TagHelpers : using the post method with html anchor tags

Standard

Want to make an anchor which would issue a post call to the server rather than a get ?

post anchor tag

instead of writing the ugly :

<form asp-controller="Account" asp-action="Logout" method="post" id="logout-form"></form>

 <a href="javascript:document.getElementById('logout-form').submit()"></a>

Why not use a tag helper to generate this behind the scenes and keep your html clean ? Here’s mine, I made it simple, your anchor must have an id, a href and a asp-post attribute to work with it.

    [HtmlTargetElement("a", Attributes = ForAttributeName + ",id,href")]
    public class PostAnchorTagHelper : TagHelper
    {
        private const string ForAttributeName = "asp-post";
        private IHtmlGenerator _generator;

        public PostAnchorTagHelper(IHtmlGenerator generator)
        {
            _generator = generator;
        }

        [HtmlAttributeName(ForAttributeName)]
        public string For { get; set; }

        [HtmlAttributeNotBound]
        [ViewContext]
        public ViewContext ViewContext { get; set; }

        public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            var href = context.AllAttributes["href"].Value.ToString();
            var id = context.AllAttributes["id"].Value.ToString();
            output.Attributes.RemoveAll("href");
            var form = new TagBuilder("form");
            form.Attributes.Add("action", href);
            form.Attributes.Add("method", "post");
            form.Attributes.Add("asp-antiforgery", "true");
            var idForm = id + "-form";
            form.Attributes.Add("id", idForm);
            var antiforgeryTag = _generator.GenerateAntiforgery(ViewContext);
            if (antiforgeryTag != null)
            {
                form.InnerHtml.AppendHtml(antiforgeryTag);
            }
            output.Attributes.Add("href", $"javascript:document.getElementById('{idForm}').submit()");
            output.PostElement.AppendHtml(form);
            return base.ProcessAsync(context, output);
        }
    }

Now, just write :

<a id="logout" href="@Url.Action("Logout", "Account")" asp-post="true"></a>

And you’re good to go !

Localizing validation messages in asp.net core : fallback on shared resources if not found

Standard

Right now in asp.net core, when you want to localize your validation messages, you have to create a resource file with the exact same name as your viewmodel, as explained here : https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization#dataannotations-localization

What if you want to keep this smart mechanism but still get a fallback to a shared resource file ? For example, let’s say you want to have the message “This field is absolutely required please !” instead of the standard message, for all required fields in all your viewmodels. You obviously don’t want to specify this message in every localized resource file named like your viewmodel.

Here’s a basic solution. In the startup.cs file, add something like :

services.AddMvc().AddDataAnnotationsLocalization(o =>
o.DataAnnotationLocalizerProvider = (modelType, stringLocalizerFactory) =>
{
   var sl = stringLocalizerFactory.Create(modelType);
   var shared = stringLocalizerFactory.Create(typeof(SharedResources));
   var ezSl = new MyDataAnnotations.ResourceManagerStringLocalizer(sl, shared);
   return ezSl;
})

And this is how your class MyDataAnnotations.ResourceManagerStringLocalizer could look like :

Read the rest of this entry