4

I'm building a custom permissions system for ASP.NET MVC, because the authorization system that comes with MVC just isn't flexible enough for our needs. This includes an MVC area that's packaged into a DLL along with views, CSS and JavaScript. I have unit tests already, but now I'm trying to create functional tests through the browser. These functional tests would be run locally from my computer or on a continuous integration server.

The permissions system I'm building doesn't do authentication. It just handles authorization. Ideally I'd like to fake the user log-ins for each test. I would like to create a user session from arbitrary usernames and passwords instead of keeping a separate table of users with their passwords, and authenticating against that.

I tried setting the GenericPrincipal object in the session in my AccountController, and then setting HttpContext.Current.User before each request, but the request is always seen as unauthenticated:

AccountController

[Authorize]
public class AccountController : Controller
{
    //
    // POST: /Account/Login
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public ActionResult Login(LoginViewModel model, string ReturnUrl)
    {
        if (ModelState.IsValid)
        {
            FormsAuthentication.SetAuthCookie(model.Username, model.RememberMe);

            // Add the fake user to the current session so Global.asax can set
            // the user on the current HttpContext to this mock object. (See
            // Global.asax, Application_AcquireRequestState)
            HttpContext.Session["CurrentUser"] = new GenericPrincipal(new GenericIdentity(model.Username), new string[0]);

            if (Url.IsLocalUrl(ReturnUrl))
                return Redirect(ReturnUrl);

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

        ModelState.AddModelError(string.Empty, "Username or Password is incorrect");

        return View(model);
    }
}

Global.asax

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        if (HttpContext.Current.Session != null)
        {
            // Try to get the mock user from the session, which is set in
            // AccountController.Login...
            HttpContext.Current.User = HttpContext.Current.Session["CurrentUser"] as IPrincipal;
        }
    }
}

I created a custom Authorize attribute to be used in my MVC Controllers:

public class PermissionLevelAttribute : System.Web.Mvc.AuthorizeAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        if (!filterContext.HttpContext.Request.IsAuthenticated || CurrentPrincipal == null)
        {
            base.OnAuthorization(filterContext);

            return;
        }

        // Custom permissions logic...
    }
}

Example usage of this attribute in a Controller:

public class BlogPostsController : Controller
{
    [PermissionLevel(Roles="Blogs.Posts.Edit.Update")]
    public ActionResult Edit(int id)
    {
        // ...
    }
}

When I do this, the filterContext.HttpContext.Request.IsAuthenticated property is always false, and it bails out of the OnAuthorization method.


How do I fake or mock a user login in ASP.NET MVC so it creates the HttpContext.Current.Session object and sets HttpContext.Current.User?

Greg Burghardt
  • 17,900
  • 9
  • 49
  • 92
  • http://stackoverflow.com/questions/34873263/testing-a-windows-authenticated-action-method. This question has comments which could help answer your question. You should use Moq to achieve the task. – Vini Jan 28 '16 at 06:29
  • Actually I figured out my problem. It was a clean install of ASP.NET MVC5, which by default uses OWIN middleware for authentication instead of Forms Authentication. I still had a bunch of OWIN authentication config settings cluttering up my Web.config file, specifically the `` tag in the system.web/modules element in Web.config. – Greg Burghardt Jan 28 '16 at 13:56
  • Do you mind sharing your tests? I have same problem as you, and editing web.config did not helped. – Piotr M Feb 24 '16 at 01:37
  • @PiotrM: I had to do a little more than edit web.config. I'll post a more comprehensive answer. – Greg Burghardt Feb 24 '16 at 13:34
  • @PiotrM: I just posted my solution. – Greg Burghardt Feb 24 '16 at 13:44

1 Answers1

1

This solution doesn't really create a "mock" user so much as it creates a fake user and doesn't check the username and password. To do this, you'll need to make two major changes:

  1. Rewrite AccountController so it doesn't actually check the username and password

    [Authorize]
    public class AccountController : Controller
    {
        //
        // GET: /Account/Login
        [AllowAnonymous]
        public ActionResult Login(string returnUrl)
        {
            ViewBag.ReturnUrl = returnUrl;
            return View();
        }
    
        //
        // POST: /Account/Login
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Login(LoginViewModel model, string ReturnUrl)
        {
            if (ModelState.IsValid)
            {
                FormsAuthentication.SetAuthCookie(model.Username, model.RememberMe);
    
                if (Url.IsLocalUrl(ReturnUrl))
                    return Redirect(ReturnUrl);
    
                return RedirectToAction("Index", "Home");
            }
    
            ModelState.AddModelError(string.Empty, "Username or Password is incorrect");
    
            return View(model);
        }
    
        //
        // GET: /Account/LogOff
        public ActionResult LogOff()
        {
            FormsAuthentication.SignOut();
    
            return RedirectToAction("Index", "Home");
        }
    }
    
  2. Remove or comment out the OWIN related config settings in Web.config:

    <configuration>
      <system.webServer>
        <modules>
          <!--<remove name="FormsAuthentication" />-->
        </modules>
      </system.webServer>
      <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
          <!--<dependentAssembly>
            <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" />
            <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" />
            <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" />
            <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" />
            <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
          </dependentAssembly>-->
        </assemblyBinding>
      </runtime>
    </configuration>
    

This will allow you to log in as any arbitrary user with any password.

Important: I wouldn't do this for a production version of a web application, obviously. I was building an MVC Area as a NuGet package and installing that package on a clean MVC project to write automated tests against the integrated NuGet package and MVC application. That's why I was able to use this solution.

Greg Burghardt
  • 17,900
  • 9
  • 49
  • 92
  • Yeah, that is clever solution but as you mentioned, not possible for production. Thanks for sharing – Piotr M Feb 24 '16 at 14:27
  • Check out my question, this is exactly what I'm struggling with. http://stackoverflow.com/questions/35588659/how-can-i-test-methods-which-needs-user-to-be-logged/35600351#35600351 – Piotr M Feb 24 '16 at 14:28
  • Im not sure you should be changing anything about the code so that it works with testing :s – chris31389 Aug 23 '17 at 16:16