0

Basically I've spent the last few days trying to figure out how to add simple Admin and Member roles onto a website I'm developing for a friend. (I am using ASP.NET Framework 5.2.7.0). I know that Microsoft has a nice role based access feature built in which allows you to put something like [Authorize Role=("Admin") at the top of the controller; however I have not been able to get it to work at all and most of the resources I've found are for ASP.NET Core.

I've tried modifying my web.config file to enable the role based access (and hopefully migrate the roles and such to my database). But since I've been unable to figure any of this out, I've tried going a more hacky route. (**I am not an advanced programmer, I've been doing this for about a year now and am in no way a pro). This is what I've basically come up with in my attempt to verify if a user is an admin (which also didn't work).

 [Authorize]
    public class AdminController : Controller
    {
        private LDSXpressContext db = new LDSXpressContext();

        public ActionResult AdminPortal()
        {
            IsAdmin();
            return View();
        }

        private ActionResult IsAdmin()
        {
            string name = User.Identity.Name;
            //The User.Identity.Name stores the user email when logged in
            var currentUserObject = db.accounts.Where(x => x.clientEmail == name);

            Account currentUser = new Account();
            foreach (var user in currentUserObject)
            {
                //I loop through the results, even though only one user should 
                //be stored in the var CurrentUserObject because it's the only 
                //way I know how to assign it to an object and get its values.
                currentUser = user;
            }

            if (currentUser.role == 2) //the number 2 indicates admin in my db
            {
                return null;
            }
            else
            {
           //Even when this is hit, it just goes back and returns the 
           //AdminPortal view
                return RedirectToAction("Index", "Home");
            }
        }
    }

Now I'm nearly positive that is is NOT a very secure way to check if a signed in user is an admin, but I was hoping that it would at least work. My idea was when someone attempted to access the AdminPortal, the IsAdmin method would run and check if the user is an admin in the database. If they are, then it returns null and the AdminPortal view is displayed, if they are not an Admin, then they are redirected to the Index view on the home page. However, the AdminPortal page is always displayed to any user and this doesn't seem to work either. I've even stepped into the code and watched it run over the return RedirectToAction("Index", "Home"); action, but then it jumps back to the AdminPortal method and just returns the AdminPortal view. So my question is:

1) If anyone happens to have experience with Role Based access in ASP.NET Framework, I would love some tips on how to get it set up

or,

2) If all else fails and I need to use my hacky method, why does it continue to return the AdminView even when the user is not an admin.

**Note: I know I could create a function that returns true or false if the user is an Admin or not, and then have an if/else statement in the AdminPortal controller that will return on view for true and another for false, however I don't want to have to implement that onto every ActionMethod, it'd be nice to keep it down to one line, or just the [Authorize Role="Admin] above the controller if possible.

Thank you guys so much for any help provided, I've been trying to research and fix this for days now and decided to reach out and ask the community!

Tyler Edwards
  • 184
  • 2
  • 9
  • 1
    By "ASP.NET Framework", I assume you mean this is an ASP.NET MVC 5 or lower project? – Tieson T. Jun 26 '19 at 01:53
  • If I'm being honest, I don't really know. I was just taught that this was ASP.NET framework, and that's as I know it by. I guess I gotta go do some research to figure that one out! – Tyler Edwards Jun 26 '19 at 02:47
  • 1
    Generally, if someone just says "ASP.NET", they probably mean "ASP.NET WebForms", which has been around awhile. What you have _looks_ like ASP.NET MVC, which is built on top of ASP.NET but swaps monolithic "forms" for the model-view-controller pattern. – Tieson T. Jun 26 '19 at 02:51

1 Answers1

1

At a minimum, you'll want to make some adjustments to what you're doing:

[Authorize]
public class AdminController : Controller
{
    public ActionResult AdminPortal()
    {
        if(IsAdmin())
        {
            return View();
        }

        return RedirectToAction("Index", "Home");
    }

    private bool IsAdmin()
    {
        bool isAdmin = false;

        using(LDSXpressContext db = new LDSXpressContext())
        {
            string name = User.Identity.Name;

            //The User.Identity.Name stores the user email when logged in

            // @see https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.singleordefault
            var currentUser = db.accounts.SingleOrDefault(x => x.clientEmail.Equals(name, StringComparison.OrdinalIgnoreCase));

            // If the email doesn't match a user, currentUser will be null
            if (currentUser != null) 
            {
                //the number 2 indicates admin in my db
                isAdmin = currentUser.role == 2;
            }
        }

        return isAdmin;
    }
}

First off, DbContext instances are meant to used, at most, per the lifetime of an HTTP request. Moving it from the class / controller level and placing it within a using block makes sure that it's properly disposed.

Next, your IsAdmin function really just needs to return a true/false value based on your lookup, and then the AdminPortal action can decide what to do with that result.

Since email seems to be a unique field in your table, use the SingleOrDefault or FirstOrDefault LINQ extension to fetch a single matching record. Which one you use is up to you, but if it's truly a unique value, SingleOrDefault makes more sense (it will throw an exception if more than one row matches). Using the StringComparison flag with the String.Equals extension method makes your search case-insensitive. There are a few culture-specific versions of that, but ordinal matching is what I would normally use, here.

Implementing some version of the Identity framework is a bit too long for an answer here, but it's possible to implement a claims-based authentication scheme without too much work. That's something that probably needs a separate answer, though.

Tieson T.
  • 20,774
  • 6
  • 77
  • 92
  • Oh my gosh this is so helpful thank you so much! So do you believe that it is secure enough to implement the code this way? Like is User.Identity.Name stored as a cookie that can be modified? Again Thank you, you are a hero! – Tyler Edwards Jun 26 '19 at 02:46
  • 1
    @TylerEdwards That's hard to answer without seeing how you've implemented authentication, but as a rule, the cookies that ASP.NET generates are encrypted, assuming you haven't configured them otherwise. If your code works, but you want to know how to improve it, there is [Code Review](https://codereview.stackexchange.com/), but make sure you read their [tour](https://codereview.stackexchange.com/tour) before posting a question. – Tieson T. Jun 26 '19 at 02:56