MVC4 Partial View Ajax Load with Authorization - c#

I'm going to try an explain this as best as possible. So, here is my issue.
I have a controller that has two actions (we will call this Controller1 with an Index action) one uses the HttpGet attribute, the other has the HttpPost attribute and both use the Authorize attribute to make sure someone is authenticated.
The Controller1.Index view uses a Partial View which calls a View called _Commands (also on the Controller1 controller). This partial view also has the Authorize attribute associated with it in the controller.
If the user isn't authorized, they are forced to the Login controller (LoginController) which has a Get and Post attributes (Index action) associated with it.
The Controller1.Index calls the partial view through an action called GetRecord. If the record exists, it renders the Partial (returns a PartialView), if it doesn't it pretty much just renders a blank DIV. This is all done through Jquery .ajax calls using POST ajax calls.
However, the issue is when a user sits on the page for a large amount of time and their session expires, and he does an jquery event (say change a drop down that fires the ajax call to update the partial), the partial is rendered with the Login.Index view (and calls the LoginController.Index GET action), where the _Command partial should be.
Now my question is, how can I force the login screen to "kick itself" out of any partial call and then reload as the top page?
I've tried a couple things that DID NOT work. For instance, I tried Request.IsAjaxRequest in the Login GET and it had no effect. I also tried IsChildAction, with no effect. Both returned FALSE.
Anyone else have any ideas how I can work around this issue?

Here's what has worked for me on recent projects.
First, in a global js file I setup the following:
$.ajaxSetup({
error: function (xhr, textStatus, errorThrown) {
//make sure this isn't caused by navigating away from the page by checking the xhr readyState
if (xhr.readyState == 4) {
switch (xhr.status) {
case 401:
case 403:
// take them to the login but hang on to their current url
//calling reload does this by leveraging the rest of our framework automagically!
window.location.reload(true);
break;
default:
bootbox.alert('<div class="text-center"><h2>An error was encountered</h2><h3>Sorry, an error has occurred. The system administrators have been notified.</h3></div>');
break;
}
}
}
});
Obviously a full page reload already handles booting the user back to login, so I just detect a 403 and force the page to reload. There's also some other ajax error handling there, not necessary for what you're requesting.
Now, that 403 isn't the default unauth status, so to make that happen I have a custom auth attribute:
public class AuthorizationRequiredAttribute : AuthorizeAttribute
{
#region Overrides of AuthorizeAttribute
public override void OnAuthorization(AuthorizationContext filterContext)
{
var skipAuthorization =
filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true) ||
filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true);
if (skipAuthorization) return;
base.OnAuthorization(filterContext);
//now look to see if this is an ajax request, and if so, we'll return a custom status code
if (filterContext.Result == null) return;
if (filterContext.Result.GetType() == typeof (HttpUnauthorizedResult) &&
filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new ContentResult();
filterContext.HttpContext.Response.StatusCode = 403;
}
}
#endregion
}
There are reasons (that I can't recall now, but pretty sure I got the info from somewhere else on SO) that you can't rely on the standard unauth status code and thus must override it with that 403. Hope this helps!

Related

Kendo grid not redirecting to login page when session is expired

My web app has many partial views in a single page that are loaded using Ajax.
I want all Ajax requests to redirect the user to the login page when his session/cookie is expired instead of the login page being loaded into each partial view container. The web app is using Forms authentication.
I've implemented this solution and it works fine for the most part.
The problem is that the redirect doesn't work when the user refreshes a Kendo grid that uses Ajax binding. I see the redirect request in the network tab but nothing happens. I've tried implementing a custom Authorize attribute and changing the http status code but the ASP.NET framework won't redirect if the code isn't 401. How can I make the redirect work?
Here is my custom Authorize attribute that isn't working. I'm not sure if using a custom attribute is the right solution.
public class AuthorizeUserAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
filterContext.Result = new HttpStatusCodeResult(System.Net.HttpStatusCode.Forbidden);
}
else
{
filterContext.Result = new HttpStatusCodeResult(System.Net.HttpStatusCode.Unauthorized);
}
//filterContext.Result = new HttpStatusCodeResult(System.Net.HttpStatusCode.InternalServerError);
//filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "account", action = "logon" }));
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
The code you posted above will work as long as the mvc pipeline is active. The problem is those ajax calls can bypass your pipeline altogether for various reasons, an ajax call does not constitute a full postback in the same manner as a form post does. Perhaps you can look into global ajax handlers and check for the presence of authentication or lack thereof and redirect as needed. As far as I recall, these handlers are called prior to any of "lower down" result handlers.
$(document).ajaxError(function (event, jqxhr, settings, data) {
...
});
$(document).ajaxSuccess(function (event, jqxhr, settings, exception) {
...
});

ASP.NET MVC route to return user to origin from partial view

I'm using ASP.NET MVC in Visual Studio 2015. The app has the following structure:
MyApp
Controllers
Controller1
Actions
Create
Delete
Details
Edit
IndexPartial
Controller2
Actions
Edit
Controller3
Actions
Edit
Views
Controller1
Create
Delete
Details
Edit
IndexPartial
Controller2
Edit
Controller3
Edit
The app displays Controller1/IndexPartial view on the Controller2/Edit view and on Controller3/Edit. This partial view displays rows of data, each with Edit, Details, Delete buttons which take the user to the Controller1 views for those actions.
When the user is done with the Controller1 action, they need to return to Controller2/Edit or Controller3/Edit via the Back to List button or when the Save/Delete buttons are clicked. But how do we determine where the user originated? Did the user come from the Edit of Controller2 or Controller3?
We've thought of using a session variable. Can RouteConfig.cs be used to track the user's path and help determine where s/he should return? How do we do this via routes in MVC?
Thank you for your help.
Update: This is all done via the server; no JavaScript (Angular, etc.).
The routing engine has nothing to do with what you need. You need to track user navigation and a good way to do this is using ActionFilters.
You can create a custom ActionFilter that checks the UrlReferrer on its OnActionExecuted and decides how to redirect the request to the appropriate Controller/Action.
[Example]
ActionFilter
public class RedirectAfterActionFilter : ActionFilterAttribute, IActionFilter
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
// Your decision logic
if (filterContext.HttpContext.Request.UrlReferrer.AbsolutePath == "something usefull")
{
filterContext.Result = new RedirectToRouteResult("Your Route Name", routeValues: null); // redirect to Home
}
base.OnActionExecuted(filterContext);
}
}
ActionFilter usage
[RedirectAfterActionFilter]
public ActionResult DoSomethingAndGetRedirected()
{
// Save, Edit or Whatever
//...
return new EmptyResult(); // no need to return since the user will be redirected by the filter
}
Extra: Read How to redirect from a action filter if you dislike to use Route names to redirect.
There are two aspects to this:
The "Back to List" link
The "Save/Delete" actions
As far as the "Back to List" link, your controller should be giving the view all the information it needs to produce a viable GUI. Pass an identifier (or even the actual return URL) to the view in the ViewBag as a dynamic property and let the view render the link to the destination.
For the "Save/Delete" actions, it depends on how they are implemented.
If it's all JS with http requests then the same concept above applies.
If you are posting back to the server however, the controller will have to do the redirection with something like RedirectToAction().
How about storing the previous location in a ViewBag and then populate your button href with the ViewBag content...
Or
You can use Url Referrer, which fectches the previous url that linked to current page.
Of course the best method will depend on your implementantion, without seeing your code those two are the best option that I can think of.

Dealing with Session timeout in ASP.NET MVC

I am working on a MVC application and I have a requirement of dealing with errors and session timeouts by redirecting the user to different error pages based on few parameters in the query string.
The issue I am facing is that i tried to implement this by saving the required parameters from querystring into a session and then redirecting to error pages. But before every HttpGet and Post action in my controllers I am checking if session is active.
So in case of a situation where session values are lost and not able to read them.
How can I implement this thing in any other way?
You need to check whether the session exists, has the fields you expect and is active. If the session does not exist or does not have a fields you expect, then handle the case when the session does not exist yet/expired. If it is not active, then handle the case when the session is no longer active. If everything is ok, then handle the request normally. If the session expired, then handle it as expired.
to check about session, you can use an ActionFilter like this:
public class SessionActiveFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var activeSession = Session["user"];
if (activeSession == null)
//Here, do a redirect
base.OnActionExecuting(filterContext);
}
}
Also, you can use a third option to save the session, like Redis Cache http://blogs.msdn.com/b/webdev/archive/2014/05/12/announcing-asp-net-session-state-provider-for-redis-preview-release.aspx
I know this is a dead story now. But I post this answer for the new comers. Please see the nice tutorial in codeproject about how to check session values in Action Filters.
In a dynamic web application, the session is crucial to hold the information of current logged in user identity/data. So someone without authentication cannot have access to some Page or any ActionResult, to implement this kind of functionality, we need to check session exists (is not null) in every action which required authentication.So, the general method is as follows:
[HttpGet]
public ActionResult Home()
{
if(Session["ID"] == null)
return RedirectToAction("Login","Home");
}
We have to check the above 2 statements each time and in each ActionResult, but it may cause 2 problems.
Repeat Things: As per the good programming stranded, we don't have to repeat the things. Create a module of common code and access it multiple times/repeatedly
Code missing: We have to write code multiple times so it might happen some time we forget to write code in some method or we missed it.
How To Avoid?
The ASP.NET MVC provides a very great mechanism i.e., Action Filters. An action filter is an attribute. You can apply most action filters to either an individual controller action or an entire controller.
If you want to know more about action filter, please click here.
So we will create a custom Action Filter that handles session expiration and if session is null, redirect to Login Action.
Create a new class in your project and copy the following code:
namespace YourNameSpace
{
public class SessionTimeoutAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
if (HttpContext.Current.Session["ID"] == null)
{
filterContext.Result = new RedirectResult("~/Home/Login");
return;
}
base.OnActionExecuting(filterContext);
}
}
}
Now our Action Filter is created and we are ready to use it. The following code will show you how we can apply attribute to Action or to complete controller.
Apply to Action
[HttpGet]
[SessionTimeout]
public ActionResult MyProfile()
{
return View();
}
Apply to Controller
[SessionTimeout]
public class HomeController : Controller
{
}
Now all actions of Home Controller will check for session when hit with the help of Action Filter. So we have reduced the code and repetitive things. This is the benefits of Action Filters.

ASP.NET MVC ajax call security issue

I am using asp.net mvc3 to develop application.
I protect my pages agaings CSRF by aniforgerytoken.
Imagine that i have Delete button above the datagrid. If user clicks on button, ajax call will post id's of selected items to e.g Countries/Delete action.
Problem is, that user can try to change request's id's (when he look into source code page, he will se $.post(...)), so he can delete countries, which was not selected (in the worst case, for which he hasn't privileges to delete them)
How can i protect my pages againts this? I don't wanna check on delete action, if user really has rights to delete items.
I hear about some ajax call hashing, but didn't found any useful tutorials or something to do that in MVC.
I don't think you need to do anything on the client side. Instead, you should check on the server side that the user has privileges to delete the countries that were selected for deletion, and only delete those that they have privileges to delete.
If you are using a post use the below
[Authorize]
[ValidateAntiForgeryToken]
If you are using a get use
[Authorize]
You can also use this custom attribute below
public class HttpAjaxRequestAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
if (!controllerContext.HttpContext.Request.IsAjaxRequest())
{
throw new Exception("This action " + methodInfo.Name + " can only be called via an Ajax request");
}
return true;
}
}
Then, decorate your action as below
[Authorize]
[HttpAjaxRequest]
public ActionResult FillCity(int State)
{
//code here
}
Remember to "Mark/Tick" if this solve your problem.

What is a good method for preventing a user from submitting a form twice?

I have a purchase page and I don't want the user to be able to refresh the page and resubmit the form once they get to the 'order complete' page because it automatically sets them up in our system via database values and charges their card via paypal (only want these to happen ONCE)... I have seen some sites that say 'Don't hit refresh or you will get charged twice!' but that is pretty lame to leave it open to possibility, what's a good way to only allow it to be submitted once or prevent them from refreshing, etc?
PS: I saw a few similar questions: PHP: Stop a Form from being accidentally reprocessed when Back is pressed and How do I stop the Back and Refresh buttons from resubmitting my form? but found no satisfactory answer... an ASP.NET MVC specific answer would be ideal too if there is a mechanism for this.
EDIT: Once they click submit it POSTS to my controller and then the controller does some magic and then returns a view with an order complete message, but if I click refresh on my browser it does the whole 'do you want to resend this form?' that is bad...
The standard solution to this is the POST/REDIRECT/GET pattern. This pattern can be implemented using pretty much any web development platform. You would typically:
Validate submission after POST
if it fails re-render the original entry form with validation errors displayed
if it succeeds, REDIRECT to a confirmation page, or page where you re-display the input - this is the GET part
since the last action was a GET, if the user refreshes at this point, there is no form re-submission to occur.
I 100% agree with RedFilter's generic answer, but wanted to post some relevant code for ASP.NET MVC specifically.
You can use the Post/Redirect/Get (PRG) Pattern to solve the double postback problem.
Here's an graphical illustration of the problem:
What happens is when the user hits refresh, the browser attempts to resubmit the last request it made. If the last request was a post, the browser will attempt to do that.
Most browsers know that this isn't typically what the user wants to do, so will automatically ask:
Chrome -
The page that you're looking for used information that you entered.
Returning to that page might cause any action you took to be repeated.
Do you want to continue?
Firefox - To display this page, Firefox must send information that will repeat any action (such as a search or order confirmation) that was performed earlier.
Safari -
Are you sure you want to send a form again?
To reopen this page Safari must resend a form. This might result in duplicate purchases, comments, or other actions.
Internet Explorer -
To display the webpage again, the web browser needs to
resend the information you've previously submitted.
If you were making a purchase, you should click Cancel to
avoid a duplicate transaction. Otherwise, click Retry to display
the webpage again.
But the PRG pattern helps avoid this altogether by sending the client a redirect message so when the page finally appears, the last request the browser executed was a GET request for the new resource.
Here's a great article on PRG that provides an implementation of the pattern for MVC. It's important to note that you only want to resort to a redirect when an non-idempotent action is performed on the server. In other words, if you have a valid model and have actually persisted the data in some way, then it's important to ensure the request isn't accidentally submitted twice. But if the model is invalid, the current page and model should be returned so the user can make any necessary modifications.
Here's an example Controller:
[HttpGet]
public ActionResult Edit(int id) {
var model = new EditModel();
//...
return View(model);
}
[HttpPost]
public ActionResult Edit(EditModel model) {
if (ModelState.IsValid) {
product = repository.SaveOrUpdate(model);
return RedirectToAction("Details", new { id = product.Id });
}
return View(model);
}
[HttpGet]
public ActionResult Details(int id) {
var model = new DetailModel();
//...
return View(model);
}
While serving up the order confirmation page you can set a token that you also store in the DB/Cache. At the first instance of order confirmation, check for this token's existence and clear the token. If implemented with thread safety, you will not be able to submit the order twice.
This is just one of the many approaches possible.
Note that the PRG pattern does not completely guard against multiple form submissions, as multiple post requests can be fired off even before a single redirect has taken place - this can lead to your form submissions not being idempotent.
Do take note of the answer that has been provided here, which provides a workaround to this issue, which I quote here for convenience:
If you make use of a hidden anti-forgery token in your form (as you
should), you can cache the anti-forgery token on first submit and
remove the token from cache if required, or expire the cached entry
after set amount of time.
You will then be able to check with each request against the cache
whether the specific form has been submitted and reject it if it has.
You don't need to generate your own GUID as this is already being done
when generating the anti-forgery token.
Give each visitor's form a unique ID when the page is first loaded. Note the ID when the form is submitted. Once a form has been submitted with that ID, don't allow any further requests using it. If they click refresh, the same ID will be sent.
Simply do a redirect from the page that does all the nasty stuff to the "Thank you for your order" page. Having done that, the user can hit refresh as many times as he likes.
If you doesn't like redirect the user to other page, then by using my way you dose not need Post/Redirect/Get (PRG) Pattern and the user remain on the current page without fear of the negative effects of re-submitting of the form!
I use a TempData item and a Hidden field (a property in the ViewModel of the form) to keep a same Guid in both sides (Server/Client) and it is my sign to detect if the form is Resubmitting by refresh or not.
Final face of the codes looks like very short and simple:
Action:
[HttpPost]
public virtual ActionResult Order(OrderViewModel vModel)
{
if (this.IsResubmit(vModel)) // << Check Resubmit
{
ViewBag.ErrorMsg = "Form is Resubmitting";
}
else
{
// .... Post codes here without any changes...
}
this.PreventResubmit(vModel);// << Fill TempData & ViewModel PreventResubmit Property
return View(vModel)
}
In View:
#if (!string.IsNullOrEmpty(ViewBag.ErrorMsg))
{
<div>ViewBag.ErrorMsg</div>
}
#using (Html.BeginForm(...)){
#Html.HiddenFor(x=>x.PreventResubmit) // << Put this Hidden Field in the form
// Others codes of the form without any changes
}
In View Model:
public class OrderViewModel: NoResubmitAbstract // << Inherit from NoResubmitAbstract
{
// Without any changes!
}
What do you think?
I make it simple by writing 2 class:
NoResubmitAbstract abstract class
ControllerExtentions static class (An Extension class for System.Web.Mvc.ControllerBase)
ControllerExtentions:
public static class ControllerExtentions
{
[NonAction]
public static bool IsResubmit (this System.Web.Mvc.ControllerBase controller, NoResubmitAbstract vModel)
{
return (Guid)controller.TempData["PreventResubmit"]!= vModel.PreventResubmit;
}
[NonAction]
public static void PreventResubmit(this System.Web.Mvc.ControllerBase controller, params NoResubmitAbstract[] vModels)
{
var preventResubmitGuid = Guid.NewGuid();
controller.TempData["PreventResubmit"] = preventResubmitGuid ;
foreach (var vm in vModels)
{
vm.SetPreventResubmit(preventResubmitGuid);
}
}
}
NoResubmitAbstract:
public abstract class NoResubmitAbstract
{
public Guid PreventResubmit { get; set; }
public void SetPreventResubmit(Guid prs)
{
PreventResubmit = prs;
}
}
Just put them in your MVC project and run it... ;)
Off the top of my head, generate a System.Guid in a hidden field on the GET request of the page and associate it with your checkout/payment. Simply check for it and display a message saying 'Payment already processed.' or such.
Kazi Manzur Rashid wrote about this (together with other asp.net mvc best-practices). He suggests using two filters to handle data transfer between the POST and the follwing GET using TempData.

Categories

Resources