简体   繁体   中英

How to hide role based section in knockout component using asp.net MVC

What is the best way to prevent user from seeing things like admin links in a knockout component?

I don't want to make a client request if the user has right to see those links because it would expose this section on the client.

The only way I can figure it out is to use a view to represent the component template and then check if the user as right on the server-side before rendering the HTML .

But is there another way that can be cleaner than this or is it the right way to proceed?

I've faced similar challenges with AngularJS Apps.

Rather than perform an extra service request, I like to pass logged-in status and role in Model or ViewBag .

For simplicity, let's assume we're using ViewBag .

1) In the Action Method , set

ViewBag.loggedIn = User.Identity.IsAuthenticated;
ViewBag.userId = User.Identity.GetUserId();

var identity = (System.Security.Claims.ClaimsIdentity)User.Identity;
ViewBag.roles = identity.Claims
                .Where(c => c.Type == ClaimTypes.Role)
                .Select(c => c.Value);

2) In a JS File , globally define UserModel with bindings and function to check if user has correct role-name:

var UserModel = function () {
    var self = this;
    self.isLoggedIn = ko.observable("");
    self.userId = ko.observable("");
    self.roles = ko.observableArray([]);


    // function to check if role passed is in array
    self.hasRole = function (roleName) {

      for (i = 0; 1 < self.roles.length ; i++ ) {
          if (self.roles[i] == roleName)
             return true
      }
      return false;
    }

};

var UserData = new UserModel();

In .cshtml View:

3) bind Role and Logged in data

<script type="text/javascript">
   UserData.isLoggedIn("@ViewBag.isLoggedIn");
   UserData.userId("@ViewBag.userId");
   UserData.roles(@Html.Raw(Json.Encode(ViewBag.roles));
</script>

4) Show Partial if Role is correct:

<div data-bind="visible: UserData.hasRole("Admin")>
          ....
</div>

Ways to hide Admin Components:

The Razor Ways:

Rather than hide components with Knockout , there are C#/Razor ways

-> Nested if clause

@((List<String>) ViewBag.roles.contains("Admin")) 
{

...

}

The advantage of Razor Conditions over Knockout is the component will not render when server creates page, leaving no trace for client to see

-> Conditional sections in _Layout

@if ((List<String>) ViewBag.roles.contains("Admin"))
{
    @RenderSection("Admin Section")
}

Cleaner than simple Razor Condition and automatically applied throughout application

-> Partial with Child Action

Called (Action, Controller, Parameter) :

@Html.Action("AdminConsole", "Admin", new { Role = "Admin"})

public ActionResult AdminComponent(string Role)
        {

            If (List<String>) ViewBag.roles.contains("Admin")
                  return PartialView(
            return View(products);
        }

The cleanest of all approaches. Can be combined in a section for greater convenience.


Conclusion on how to hide Admin Components:

How you choose to hide Admin Components depends on your needs. The Razor/C# approach is more convenient and feels more secure.

On the other hand, if you are interested in giving the user the SPA experience, C# and server methods fall short. Do you allow users to Authenticate without refreshing pages? If so, an Admin may Authenticate and change from anonymous user to Admin.

If you are comfortable with refreshing the screen to show the changed content, C#/Razor is your approach. If your priority is to give the user the "responsive experience", You will want to implement a Knockout solution.

If you cannot expose the markup to the client, then the only way to do this would be on the server before the markup is generated. As you have pointed out, this would be done in the view rendering.

In your case, using a partial view to hold your KO component would be appropriate and provide the functionality that you need while allowing you to reuse the component markup.

Here is the method I have used in the past, which has worked very cleanly:

View

<script type="text/javascript">
    ko.components.register("foo", {
        viewModel: FooComponentViewModel,
        template: { element: "component-foo" }
    });
</script>

...

<foo params="IsAdmin: @(User.IsInRole("Admin") ? 'true' : 'false')"></foo>

...

@Html.Partial("Components/_Foo")

Additional context could even be passed to the partial view via the MVC view model if necessary.

Component Partial View

<template id="component-foo">
    @if(@User.IsInRole("Admin"))
    {
        // something you DO care about the client seeing
    }

    ...
</template>

@if(@User.IsInRole("Admin"))
{
    <script type="text/javascript">
        // Some JS code to hide from non admins for this component
    </script>
}

Component View Model

function FooComponentViewModel(params) {
    var self = this;

    self.AdminDoSomething = function () {
        if (!params.IsAdmin) {
            return;
        }

        // something you DO NOT care about the client seeing...
    };
}

I know of no cleaner way to do this while providing the requirements that you seek.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM