Unit Testing ASP.NET MVC Authorize Attribute to Verify Redirect to Login Page

Unit testing ASP.Net MVC Authorize attribute to verify redirect to login page

You are testing at the wrong level. The [Authorize] attribute ensures that the routing engine will never invoke that method for an unauthorized user - the RedirectResult will actually be coming from the route, not from your controller method.

Good news is - there's already test coverage for this (as part of the MVC framework source code), so I'd say you don't need to worry about it; just make sure your controller method does the right thing when it gets called, and trust the framework not to call it in the wrong circumstances.

EDIT: If you want to verify the presence of the attribute in your unit tests, you'll need to use reflection to inspect your controller methods as follows. This example will verify the presence of the Authorize attribute on the ChangePassword POST method in the 'New ASP.NET MVC 2 Project' demo that's installed with MVC2.

[TestFixture]
public class AccountControllerTests {

[Test]
public void Verify_ChangePassword_Method_Is_Decorated_With_Authorize_Attribute() {
var controller = new AccountController();
var type = controller.GetType();
var methodInfo = type.GetMethod("ChangePassword", new Type[] { typeof(ChangePasswordModel) });
var attributes = methodInfo.GetCustomAttributes(typeof(AuthorizeAttribute), true);
Assert.IsTrue(attributes.Any(), "No AuthorizeAttribute found on ChangePassword(ChangePasswordModel model) method");
}
}

Unit testing ASP.Net MVC Authorize attribute to verify redirect to login page

You are testing at the wrong level. The [Authorize] attribute ensures that the routing engine will never invoke that method for an unauthorized user - the RedirectResult will actually be coming from the route, not from your controller method.

Good news is - there's already test coverage for this (as part of the MVC framework source code), so I'd say you don't need to worry about it; just make sure your controller method does the right thing when it gets called, and trust the framework not to call it in the wrong circumstances.

EDIT: If you want to verify the presence of the attribute in your unit tests, you'll need to use reflection to inspect your controller methods as follows. This example will verify the presence of the Authorize attribute on the ChangePassword POST method in the 'New ASP.NET MVC 2 Project' demo that's installed with MVC2.

[TestFixture]
public class AccountControllerTests {

[Test]
public void Verify_ChangePassword_Method_Is_Decorated_With_Authorize_Attribute() {
var controller = new AccountController();
var type = controller.GetType();
var methodInfo = type.GetMethod("ChangePassword", new Type[] { typeof(ChangePasswordModel) });
var attributes = methodInfo.GetCustomAttributes(typeof(AuthorizeAttribute), true);
Assert.IsTrue(attributes.Any(), "No AuthorizeAttribute found on ChangePassword(ChangePasswordModel model) method");
}
}

Authorize attribute redirects to LoginPath instead of AccessDeniedPath

The Authorize attribute returns error 401, and that will usually redirect to login with the correct credentials. The AccessDeniedPath is used when handling error 403, I believe.

We recently implemented a custom authorization attribute specifically to handle separation of 401 and 403 errors.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class CustomAuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
//If user is authenticated, send a 403 instead of 401
if (filterContext.HttpContext.Request.IsAuthenticated)
{
filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);//403
}
else
{
//if user is not authenticated, throw 401
base.HandleUnauthorizedRequest(filterContext); //401
}
}
}

Which is the simplest way to redirect to page when user is not logged in in ASP.NET MVC?

You can use Authorize attribute which as part of Forms authentication in ASP.NET-MVC. To do that you need to setup forms authentication first in your web.config:

Under the <system.web> element, place:

<authentication mode="Forms">
<forms loginUrl="~/Login/Index" timeout="45" slidingExpiration="true" cookieless="UseCookies" protection="All" requireSSL="false" enableCrossAppRedirects="false" defaultUrl="~/Home/Index" path="/" />
</authentication>

Now in your LoginController, when you are authenticating a user, it will be something like this:

public class LoginController : Controller
{
public LoginController()
{

}

public IActionResult Index()
{
return View();
}

[HttpPost]
[AllowAnonymous]
public IActionResult ValidateUser()
{
//your user authentication logic here
if(userAuthenticated)
{
FormsAuthentication.SetAuthCookie(userModel, false);
}

return View("Index");
}
}

Once you have authenticated your user, then you can place the Authorize attribute either on class action or method depending on your need:

Require authorized user only to access entire class and its methods:

[Authorize]
public class HomeController : Controller
{
public HomeController()
{

}

public IActionResult Index()
{
return View();
}

}

Require user to only be authorized for Index method:

public class HomeController : Controller
{
public HomeController()
{

}

[Authorize]
public IActionResult Index()
{
return View();
}

}

How do I unit test a controller method that has the [Authorize] attribute applied?

You need to mock a context for your controller. Try using Moq

Your arrange would then look like:

var controller = new UserController();
var mock = new Mock<ControllerContext>();
mock.SetupGet(x => x.HttpContext.User.Identity.Name).Returns("SOMEUSER");
mock.SetupGet(x => x.HttpContext.Request.IsAuthenticated).Returns(true);
controller.ControllerContext = mock.Object;

You should be able to then do your Act & Assert.

If you haven't already, I would highly recommend looking through NerdDinner as an example MVC site.

How to unit test a [System.Web.Http.Authorize] filter in ASP.net with Xunit

Here are a couple simple tests you can try:

[Fact]
public void CrowdSourcedData_Valid()
{
// Arrange
var message = new HttpResponseMessage();
var apps = new List<Apps>();
var dataProcessor = new Mock<IDataProcessor>(); // Assuming "IDataProcessor" or something
dataProcessor.Setup(x => x.CrowdSourcedData(apps)).Returns(message);
var controller = new Controller(dataProcessor.Object); // Assuming you inject the "_dataProcessor" here

// Act
var result = controller.CrowdSourcedData(apps);

// Assert
dataProcessor.Verify(x => x.CrowdSourcedData(apps), Times.Once);
Assert.Equal(message, result);
}

[Fact]
public void CrowdSourcedData_Invalid()
{
// Arrange
var message = new HttpResponseMessage();
var apps = new List<Apps>();
var dataProcessor = new Mock<IDataProcessor>();
dataProcessor.Setup(x => x.CrowdSourcedData(apps)).Returns(message);
var controller = new Controller(dataProcessor.Object);
controller.ModelState.AddModelError("FakeError", "FakeMessage"); // Here you set the invalid "ModelState"

// Act
var result = controller.CrowdSourcedData(apps);

// Assert
dataProcessor.Verify(x => x.CrowdSourcedData(apps), Times.Never);
Assert.Null(result);
}

How to create a custom attribute that will redirect to Login if it returns false, similar to the Authorize attribute - ASP.NET MVC

You can create a custom AuthorizeAttribute and override AuthorizeCore() and HandleUnauthorizedRequest() as required. Add your own logic which will do the check and redirect if necessary.

I'm just showing a simple example using MVC's ActionFilterAttribute (which is not the best place to do authentication/authorization)

public class VerifyUserAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var user = filterContext.HttpContext.Session["UserID"];
if (user == null)
filterContext.Result = new RedirectResult(string.Format("/User/Login?targetUrl={0}",filterContext.HttpContext.Request.Url.AbsolutePath));
}
}

Do not forget to set the Session["UserID"] variable in your /User/Login action method after proper user validation.



Related Topics



Leave a reply



Submit