5

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?

Simon B.Robert
  • 31,754
  • 4
  • 16
  • 24

2 Answers2

3

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.

Dave Alperovich
  • 32,320
  • 8
  • 79
  • 101
  • In your point 4 do you mean that this html is in a partial view that is rendered only if the user a role admin and then you are doing a double check in javascript code ? – Simon B.Robert Jun 14 '15 at 22:25
  • @SimonB.Robert, This div would appear in your cshtml page. To hide / show a partial based on Role. It doesn't have to a an actual partial. It can just be markup. The idea is, anything inside the div will only show if the user has correct role. – Dave Alperovich Jun 14 '15 at 22:42
  • Ok, I understand. Maybe I should clarify my question. I need a solution that would let the server generate the markup for the component based on Role, because I don't want my users to see admin code. I know that I could use a standard view that will represent my component and let the server hide admin markup, but is that the cleanest way to do what I want ? – Simon B.Robert Jun 14 '15 at 22:52
  • @SimonB.Robert, Ok, I see, In Angular, I would render a directive with it's own View inside that div. The Directive would not turn into markup unless condition was true, so the user never see any content of partial or be able to check it's source. In this case, I would request a Partial view from a child action method inside the div. The child action could check your user's role and only return what is appropriate for user's Role. Is that clear? – Dave Alperovich Jun 14 '15 at 22:58
  • @SimonB.Robert, I think this is knockout equivalent to **AngularJS** `ng-if`... the markup doesn't even render if condition is false, so user will not see partial in page or in page source. If it functions the way I think, could save you from a server request. http://knockoutjs.com/documentation/if-binding.html – Dave Alperovich Jun 14 '15 at 23:08
  • From a security point of view. I think the partial view solution is better. But I want to know if you think that this design is appropriate or is there a cleaner design for this same case ? – Simon B.Robert Jun 15 '15 at 01:25
  • @SimonB.Robert, these are standard practices. Security is messy. Even cleaner would sections in your layout. Think about kind of trade offs you are willing to make. Sections and Child-Partials have a cleaner feel. But do you want a true SPA experience? For example, will non-authenticated users be able to see this page? And if the user authenticates as an Admin, would you like to make the Admin-section render without refreshing the page? In that case, you will probably prefer the pure Knockout approach. If all that is clear, I can update my answer. If not, we should move this to chat. – Dave Alperovich Jun 15 '15 at 02:20
  • Yes update your answer but keep both aproachs. Thanks for your time. – Simon B.Robert Jun 15 '15 at 02:53
2

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.

kspearrin
  • 10,238
  • 9
  • 53
  • 82
  • You would have to do this in your view or partial view. I have updated the Component Partial View code above to illustrate. Alternatively, you could even go as far as loading a completely different KO view model JS depending on role but I find this to be pretty ugly. I'm not sure of all the details of your application so this may not be necessary. – kspearrin Jun 15 '15 at 13:13