Restricting Access To MVC Views
Tags: dotnet, webdev
Published: 2020-07-14 21:01:33

<!-- wp:paragraph -->
<p>There are so many different mechanisms for authentication and for claims verification in .NET for MVC applications with ASP.NET Identity, JSON Web Tokens, and Azure Active Directory to name a few.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>But what if you are rolling your own authentication and need to restrict specific pages to specific users?</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>Time for a Session</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>.NET's <a href="https://docs.microsoft.com/en-us/dotnet/api/system.web.httpcontext?view=netframework-4.8">HTTPContext</a> object contains a property called <a href="https://docs.microsoft.com/en-us/dotnet/api/system.web.httpcontext.current?view=netframework-4.8">Current</a> which we will leverage in order to check the permissions of the person trying to load our restricted pages. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>'Current' allows us to get to the current <a href="https://docs.microsoft.com/en-us/dotnet/api/system.web.httpcontext.session?view=netframework-4.8">Session</a> object, which itself stores various pieces of information relating to the individual HTTP requests sent to your MVC app. Thanks to this property, we can store information unique to the site visitor attempting to load the page. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>For example, if you have already authenticated the user by verifying their identity, you can store the date and time their session (as in time on your site / in your application) started, and their permissions. Using these saved properties, you can decide whenever a new page is requested, whether the current session (as in the visitor loading the page) is allowed to view it, and redirect them somewhere else if they are not. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>So how do we store session info? Well, let's assume your authentication process hashes an inputted password and verifies it against the password stored in a 'Users' database table. This table will also contain other values such as their email or contact info, but importantly, values that dictate what they can or cannot view, or which pages they are allowed to load. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Your process returns the user object and this signifies a successful login. Now we can store this info in a session object to be checked on future page loads. </p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>Make a User Session Class</h2>
<!-- /wp:heading -->

<!-- wp:code -->
<pre class="wp-block-code"><code>    public class UserSession
    {
        public DateTime StartedAt { get; }
        public DBUserRecord UserDetail { get; }

        public UserSession(DBUserRecord user)
        {
            StartedAt = DateTime.UtcNow;
            UserDetail = user;
        }
    }</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Here I have made a simple class which will contain the returned user data retrieved from the DB when the user logged in <em>(DBUserRecord user</em>) as well as the date and time at which their session on our site began. (This could be useful later on if you want to implement time-restricted viewing of pages.)</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>I have created a constructor, which passes our <em>DBUserRecord</em> as a parameter and initializes it within the class. The constructor also sets the <em>StartedAt</em> value to now. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Great! We have an object that stores info about our user. It's easily extendable, either by adding further properties to this class or to the <em>DBUserRecord</em> we pass in. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>So how to we store this in the current HTTPContext's session?</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>Storing a Session State</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We will want to head to our login page's controller. Remember, this assumes you have a login page which has successfully logged somebody in and returned some user data as a result. The next logical step is to redirect the user either to the expected page (thanks to successful login) or to a message telling them to kindly go away. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>To that end, I will store the session state from within the Controller of the login page. (For more info on Controllers in MVC take a look <a href="https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/controllers-and-routing/aspnet-mvc-controllers-overview-cs">here</a>)</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>It's pretty simple from this point. Controllers return a view to the user, which is what loads the requested page. We wrap a condition around this which authenticates them, and if it is successful, we create the session object we made earlier on, save it to the HTTPContext and then return the view. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Here's an example</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code> //add your authentication method
                    if (Authenticated(username, password)) 
                    {
                        //they're legit

                        //keep user data in an object and   pass to your session's constructor
                        var UserSession session = new UserSession(UserDataFromDB);

                        //create a session object named whatever you want and pass the usersession as a value
                        System.Web.HttpContext.Current.Session.Add("OpenSession", session);

                        //return the view. (Pass the name of the view as a string if different to the one associated with this controller
                        return View(); 
                    }</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>This takes care of getting them logged in, but what about when they change pages? How do we make it so that any other page requested only loads if the session is valid?</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>It's nice and easy. You just have to add a check for the session on the <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.actionresult?view=aspnetcore-3.1">ActionResult </a>function that is called when the page is requested. For example, say our user now tries to load the 'Dashboard.' Requesting the page calls the ActionResult Function, <em>'Index.</em>' Within this function, we just check to make sure the session we created exists. </p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>        public ActionResult Index()
        {
            if (System.Web.HttpContext.Current.Session&#91;"OpenSession"] != null)
            {
                var model = new Dashboard();
                return View(model);
            }
            else
            {
                return RedirectToAction("Index", "Login");
            }
        }</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>So now, if the session exists, we know the user logged in successfully earlier on, and that they are ok to view the page. We return the requested view, passing in any models we need to. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If the user is somewhere they shouldn't be, we call <a href="https://docs.microsoft.com/en-us/dotnet/api/system.web.mvc.controller.redirecttoaction?view=aspnet-mvc-5.2">RedirectToAction()</a>, passing in the ActionResult method to be called and the controller in which it resides. In the example above, this will redirect them to the login screen. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>This can of course be extended further. If you want to restrict certain pages to authenticated users with specific permissions. you would cast the session object back to <em>UserSession</em> and check the permissions before letting them in. </p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>        public ActionResult Index()
        {
            var session = (UserSession)System.Web.HttpContext.Current.Session&#91;"OpenSession"];
            if (session != null)
            {
                if (session.UserDetail.IsAdmin)
                {
                    var model = new Dashboard();
                    return View(model);
                }
            }
            return RedirectToAction("Index", "Login");
        }
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>In most cases, security requirements dictate that you roll a proven framework like JSON Web Tokens. However, if you're working with constraints that force you to roll your own authentication / fencing of content, the local session object is your friend. </p>
<!-- /wp:paragraph -->