Displaying custom message in login page when redirected by `AuthorizeAttribute` - asp.net-mvc-3

I have written a custom AuthorizeAttribute to display message in the login page. Here is the code.
public class MyAuthorizeAttribute : AuthorizeAttribute
{
public string Message { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
httpContext.Items["LoginFailMessage"] = Message;
}
return authorized;
}
}
In my action I will do
[MyAuthorize(Message = "Please login to continue")]
public ActionResult Detail()
Now, I cannot access the item HttpContext.Current.Items["LoginFailMessage"] in my view. I realize that the problem is, the item exists only for one redirection call but authorization failure is causing more than one redirection.
So, is there a way I can solve the issue? From exactly where should I pass the message?
Edit
What I am trying to do is, suppose, an Anonymous user is allowed to see a short description of something.
With the description, there is a edit and a detail link. Both edit and detail requires the user to login. So, the user will be redirected to login page if clicks either.
If user clicks edit I will display a message Please login to edit and if clicks detail may be please login to see detail in the login page.

It would be much easier for you to use the ModelState to do this, for example:
[HttpPost]
public ActionResult Login(LoginViewModel model) {
// .. check model state is valid
if (AuthorizationService.Authorize(model.Username, model.Password)) {
// .. user is authenticated
}
else {
ModelState.AddModelError("", "Login failed.");
return View(model);
}
}
Then you can use the ValidationSummary() to show the error on the login page, or, pass the name of the username or password model property in the AddModelError call to use a ValidationMessage().

Related

What is ReturnURL in mvc3

What is Return URL in mvc3? When i write down my url in adress bar of the browser, at the append return url is automatically appended to it. How this happnes?
I provide the following url in adress bar
http://localhost:55875/admin
and after pressing enter it becomes
http://localhost:55875/Account/Logon?ReturnUrl=%2fadmin
I have debugged the logic for Logon action method, but dont see any logi which is appending returnurl to the provided url? How did this happen?
When an unauthenticated user tries to get into a section of your application which requires authentication, then returnUrl comes into the picture.The Url requested by the unauthenticated user is basically stored in returnurl.
for example below controller decorated with Authorize attribute :
[Authorize]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
The login action grabs the value of this parameter and puts it in the ViewBag so it can be passed to the View.
The View then stores this value in the form as shown by this line of code in the View.
#using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl }))
The reason it is stored in the View is so that when the user does a Submit after entering their user name and password, the controller action that handles the post back will have access to this value.
Your application must be having Authentication for login which is handled by [Authorize] attribue. as user is not authenticated it returns to logon page with returnurl as admin
For more on AuthorizeAttribute How to use authorize attribute on MVC3
Below is the standard action of login when you login above returnurl ie admin is passes as second parameter, depending on which user is redirected to the page using return Redirect(returnUrl);
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Routing is one of the core concept of any ASP.NET MVC application. A URL of a MVC application is the combination of your Application root URL followed by Controller name and then Action to which request has been made e.g.
http://localhost:55875/{controller}/{action}/{optional parameters}
You probably have chosen new project with Account Controller and Authorization membership providers. As fellow members has mentioned, the Authorize attribute is probably the main reason you are being redirected to logon page.
From the documentation of Authorize attribute:
If an unauthorized user tries to access a method that is marked with
the Authorize attribute, the MVC framework returns a 401 HTTP status
code. If the site is configured to use ASP.NET forms authentication,
the 401 status code causes the browser to redirect the user to the
login page.
In browsers, whenever you hit enter key in address bar, browser always make a GET request to the server for resources.
That is what could have happened. Either your Admin Controller or its Index() method is decorated with [Authorize] action filter attribute e.g.
public class AdminController : Controller
{
///<summary>
/// This view will open whenever you make a HTTP GET request to your Admin
/// controller without providing any action method name in request explicitly.
/// Because it is decorated with Authorize attribute, any user who has not logged in
/// will be redirected to the login page...
///</summary>
[Authorize]
public ActionResult Index()
{
return View();
}
}
You may wonder why did application redirected to login view?
This is because by default this action has been set in your application inside your web.config file.
<authentication mode="Forms">
<forms loginUrl="~/Account/Logon"/>
</authentication>
MVC leverages the power of built-in authentication logic and redirects the user to view that has been set under loginUrl.
Try removing the [Authorize] action filter just for a change and see what happens. In the end it is your business logic to decide on which views you need to have only authorize or anonymous access.
However, you may also take a look at AllowAnonymous attribute. It allows you to skip authorization for a perticular contoller or action.
[Authorize]
public class AdminController : Controller
{
///<summary>
/// Skips Authorization..
///</summary>
[AllowAnonymous]
public ActionResult Index()
{
return View();
}
///<summary>
/// Only allows authorize access...
///</summary>
public ActionResult Secure()
{
return View();
}
}
You can customize/override the behavior of these action filters as well.
Notice, [Authorize] has been added to controller itself if you have selected the project with internet and membership providers which will make all the actions inside this authorized except those with [AllowAnonymous] filter (if exists).
This article has good overview of Action filters in MVC.
First: you are trying to access an authorized page so every time you are trying to access this page the application automatically redirect you to login page
Second: how this happened?
in web.config file you can find a section for authentication
<authentication mode="Forms" >
<forms loginUrl="~/Account/Logon"/>
</authentication>
this section says that each time you are trying to access authorized page you will be redirected to this page and since its forms authentication so you will be redirected to this page
another thing you may using Authorize an AuthorizeAttribute which tells the application that the following ActionResult can't be accessed by anonymous users you can use this attribute in class level or ActionResult level as follows
[Authorize]
public class HomeController
{
}
Or
public class HomeController
{
[Authorize]
public ActionResult Index()
{
}
}

What is the difference between AuthenticateRequest and AuthorizeRequest

Can you explain the differences between HttpApplication.AuthenticateRequest and HttpApplication.AuthorizeRequest in ASP.NET MVC 3 please? When will they occur? Assume this scenario:
My User has a property called IsBanned and I want to check his/her IsBanned property in each request. If it was true, I redirect the User to an error page. But not for all requests, just requests that their action signed by [Authorize] attribute. OK, atthis type of actions, will HttpApplication.AuthenticateRequest occur or HttpApplication.AuthorizeRequest or anything else?
I know I can check this property in SignIn|LogOn action. But I means this:
A user requests logging in
I check the property IsBanned and it was false
The user logged in
User view some pages of site
The admin banned the user (while he is logged in)
User requests a page (action) that have [Authorize] attribute
User is logged in (before this. remember?)
So I have to show the requested page
But the user give a banned flag by admin
How can I prevent user from viewing requested page?
Thanks in advance.
I dont think you need to deal with either of HttpApplication.AuthenticateRequest or HttpApplication.AuthorizeRequest. I would solve it by using a custom Authorize Attribute.
public class MyAuthorizeAttribute : AuthorizeAttribute {
protected override bool AuthorizeCore(HttpContextBase httpContext) {
bool authorizerPrimarily = base.AuthorizeCore(httpContext);
if(authorizedPrimarily){
return user_not_banned;
}
return authorizerPrimarily;
}
}
You can get user's name from httpContext.User.Identity.Name. Use it to grab data from database.
Update for comment-1
To redirect banned users to a specific page, you may do this:
if(authorizedPrimarily){
if(user_banned){
httpContext.Response.Redirect("url of banned message");
return false;
}
return true;
}

How do I perform error handling in ASP.NET MVC 3 controller?

I have an Account controller which have:
LoginForm (get action)
Login (post action)
RegisterForm (get action)
Register (post action)
In another controller's index action i use render them as:
Html.RenderAction("RegistrationForm", "Acount");
Html.RenderAction("LoginForm ", "Acount");
Everything works ok and I can register a new user, login and validate with unobtrusive validation.
The problem is when do some back-end validation in Register/Login action and if there is an error I don't know how to transfer the error to be rendered.
I've tried with PRG pattern and it works ok. I get the error displayed on the form with the preserved data, but PRG is not the way to do it.
What is an alternative solution to this problem without using ajax to validate or move those methods in the controller where the RegistrationForm/LoginForms are used?
I want to skip using TempData due to session usage in the background.
EDIT CODE SAMPLE:
class AccountController : SomeBaseController{
[HttpGet]
public PartialViewResult RegistrationForm()
{
return PartialView(new RegisterUser());
}
[HttpPost]
public ActionResult RegisterUser(RegisterUser user)
{
if (ModelState.IsValid)
{
var _user;// create domain user from Register user Model;
var _validationOutput = _userService.DoSomeAwsomeServerSideValidation(_user);// do some custom validation
if (_validationOutput.IsFault)
{
// we preseve tempdata in base controller OnActionExecuted
_validationOutput.ErrorMessages.ForEach(x => ModelState.AddModelError(_validationOutput.ErrorCode, _validationOutput));
// redirect to home controller custom error occured
return RedirectToAction("Index", "Home", user);
}
return RedirectToAction("RegistrationInfo");
}
return RedirectToAction("SomeUserInfoAction");
}
}
class HomeController : SomeBaseController {
Index(){
return View();
}}
HomeControllerMarkup {
#{Html.RenderAction("RegistrationForm", "Acount");}
#{Html.RenderAction("LoginForm", "Acount");}
}
You can manually add errors to your ModelState within your post controller using:
ModelState.AddModelError("", #"You didn't perform task XYZ");
You should then be able to return the view and display the errors in a validation summary:
#Html.ValidationSummary(false, "Login was unsuccessful because...")

Require re-authentication for certain actions

For certain actions like changing email settings or administrator activities, I want users to re-authenticate before the action is completed. Is there a good pattern for doing this in ASP.NET MVC 3?
Descpription
You can create your ActionMethod with Username, Password and the field you want to change (Email) for example. Than validate this data in the [HttpPost] of your data. If the authorization has success, change it and if not add the error to the ModelState.
Use a ViewModel for that.
Sample
public class ChangeEmailViewModel
{
public string Username { get; set; }
public string Password { get; set; }
public string EmailAddress { get; set; }
}
public ActionResult ChangeEmail()
{
return this.View(new ChangeEmailViewModel());
}
public Action ChangeEmail(ChangeEmailViewModel model)
{
// authorize
bool isAuthorized = // your logic.
if (isAuthorized)
{
// change email
} else
{
ModelState.AddModelError("Username", "Username is not valid");
}
return this.View(model);
}
If you want to dynamically intercept and re-authenticate someone who is already authenticated, you could probably also handle this with a special cookie. The actions that require re-auth could be decorated with a custom filter that overrides OnAuthorization to check for the cookie, then redirect to take username & password if it is not found. Pattern, no code:
User clicks link to uber-protected action.
Filter on action looks for cookie and does not find it, redirects to sign in.
User signs in, and you write a special cookie
(different from the forms auth cookie),
then redirect back to original action.
Filter on action looks for cookie and finds it authorizing user.
The lifetime of the cookie would at least have to go all the way to the http post of the uber-protected action. You will have to decide when to delete it. For example, after user re-auths for one uber-protected action, do you want them to re-auth for second uber-protected action in the same browser session?

How do I handle a HttpRequestValidationException and return a meaningful error using AddModelError?

I would like to handle HttpRequestValidationExceptions (e.g. when html is inserted into form fields) and return the user back to the page after submission with a meaningful error to highlight the problem.
For example, on a login page, if the user types some invalid characters into the username field, then I would like to catch the HttpRequestValidationException and return the user to the login page with the username field highlighted.
I can catch the error in a class inheriting from HandleErrorAttribute using the OnException method.
Is there a way from here (or any other way) that I can get the Controller *ModelState* to add the error?
Note: I do not want to turn validation off or redirect to an error page.
e.g.:
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception is HttpRequestValidationException)
{
ModelStateDictionary ModelState = <...>
ModelState.AddModelError("Error", "Invalid chars");
filterContext.HttpContext.Response
filterContext.ExceptionHandled = true;
HttpContextBase HttpContext = filterContext.HttpContext;
HttpContext.Response.Redirect(HttpContext.Request.Path);
return;
}
}
Thanks in advance for any help you can give me!
Instead of capturing and handling the HttpRequestValidationException, you could decorate your model's properties with the [AllowHtml] data annotation and your own custom data annotation, which contains the validation rules you require. See this answer for a good example.
Your model's properties may look like this:
[AllowHtml]
[DisallowHtml]
public string SomeProperty{ get; set; }
Which looks a bit silly, but so far it's the cleanest solution I've encountered.

Resources