26

I know in controllers, you can write [Authorize("policyName")] without an issue, but is there any way to use a policy in a view? I'd rather not use User.IsInRole(...) every single time I want to authorize some HTML.

Edit:

Here's some code

Startup.cs -- Policy Declaration

    services.AddAuthorization(options =>
    {
        options.AddPolicy("testPolicy", policy =>
        {
            policy.RequireAuthenticatedUser()
                  .RequireRole("RoleOne", "RoleTwo", "RoleThree")
                  .RequireClaim(ClaimTypes.Email);
        });
    });

Admin Controller

[Authorize("testPolicy")]
public class AdminController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}

Navbar HTML

<div class="navbar navbar-inverse navbar-fixed-top">
            <div class="container">
                <div class="navbar-collapse collapse">
                    <ul class="nav navbar-nav">
                        <li><a asp-controller="Home" asp-action="Index">Home</a></li> 

                        <!-- I want to implement my policy here. -->
                        @if (User.IsInRole("..."))
                        {
                            <li><a asp-controller="Admin" asp-action="Index">Admin</a></li>
                        }
                    </ul>
                    @await Html.PartialAsync("_LoginPartial")
                </div>
            </div>
Daath
  • 1,899
  • 7
  • 26
  • 42
  • I am confused about by why you would need this and what you mean exactly? Could you post some example code in you post please. You could just do it in the controller instead and redirect to a different view if they weren't in that role. – Martin Dawson Mar 17 '16 at 19:06
  • 1
    Well in my case I was specifically talking about the navbar. I wanted to add an [Admin] link on the navbar that redirects to an admin controller. The controller itself is authorized with a policy, but my purpose is purely cosmetic - to only show the [Admin] tab if the user is meets the Policy requirements, just like in the Controller – Daath Mar 17 '16 at 19:20
  • @MartinMazzaDawson I added code to my OP if that helps you out. – Daath Mar 17 '16 at 19:25
  • 1
    I'm pretty sure the only way is to add a server side check like you have done above, what you are doing is completely fine. I still don't understand why you would need anything other than this check. Although what would make this better is to separate the business logic the from view logic and do `@if (FooModel.IsInRole("..."))` and assign this property value in your controller's action method to the `IsInRole(...)` – Martin Dawson Mar 17 '16 at 19:56
  • 3
    I found this link which may be helpful.. https://docs.asp.net/en/latest/security/authorization/views.html – Martin Dawson Mar 17 '16 at 20:03
  • @MartinMazzaDawson I think the link in your last comment answers the question. IMO you should make it an answer. – Jojo Mar 18 '16 at 09:06
  • @MartinMazzaDawson Thank you so much for taking the time out. And you're right - when I think about it, separating the logic makes complete sense. I was having tunnel vision in my train of thought. And thank you for the link - that was exactly what I was trying to achieve, but I was unable to find it. If you'd like to post the link as an answer I'd be happy to mark it as the correct answer :) – Daath Mar 18 '16 at 13:08

3 Answers3

29

I ended up creating a tag helper to conditionally hide the element it's associated with.

[HtmlTargetElement(Attributes = "policy")]
public class PolicyTagHelper : TagHelper
{
    private readonly IAuthorizationService _authService;
    private readonly ClaimsPrincipal _principal;

    public PolicyTagHelper(IAuthorizationService authService, IHttpContextAccessor httpContextAccessor)
    {
        _authService = authService;
        _principal = httpContextAccessor.HttpContext.User;
    }

    public string Policy { get; set; }

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        // if (!await _authService.AuthorizeAsync(_principal, Policy)) ASP.NET Core 1.x
        if (!(await _authService.AuthorizeAsync(_principal, Policy)).Succeeded)
            output.SuppressOutput();
    }
}

Usage

<li policy="testPolicy"><a asp-controller="Admin" asp-action="Index">Admin</a></li>
Chris
  • 4,393
  • 1
  • 27
  • 33
  • 2
    By far the cleanest approach above all - this should be updated to become the real answer with the advance of technology. – user3141326 Nov 04 '17 at 17:40
  • 2
    +1 Awesome solution! For devs new to TagHelpers, you need to register your tag helper in your view or _ViewImports file like this: `@addTagHelper *, YourAssemblyName` Read more: https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-5.0#managing-tag-helper-scope – Mark Sep 17 '21 at 05:22
22

I found this link which may be helpful: https://docs.asp.net/en/latest/security/authorization/views.html

Examples from that page:

@if (await AuthorizationService.AuthorizeAsync(User, "PolicyName"))
{
    <p>This paragraph is displayed because you fulfilled PolicyName.</p>
}

In some cases the resource will be your view model, and you can call AuthorizeAsync in exactly the same way as you would check during resource based authorization;

@if (await AuthorizationService.AuthorizeAsync(User, Model, Operations.Edit))
{
    <p><a class="btn btn-default" role="button"
        href="@Url.Action("Edit", "Document", new {id= Model.Id})">Edit</a></p>
}
Martin Dawson
  • 7,455
  • 6
  • 49
  • 92
  • 1
    Not sure in 1.0 or 2.0, but in core 3.0, the auth check should be `@if ((await AuthorizationService.AuthorizeAsync(User, "PolicyName")).Succeeded)` - note the final property check of `Succeeded`. – Metro Smurf Oct 05 '20 at 14:20
0

This is one of the big improvements in ASP Core when you can inject the identity to all pages in the startup file:

@if (User.IsInRole("Admin"))
{
    <p>
    <a asp-action="Create" asp-controller="MyController">Create New</a>
</p>
}

In Startup.cs:

 services.AddIdentity<ApplicationUser, IdentityRole>()

EDIT: Ok I misread the post, you already knew this :) - ill leave it anyway if someone can use it.

Morten_564834
  • 1,479
  • 3
  • 15
  • 26