Redirecting to login view when unauthorized during POST - asp.net-mvc-3

I am using AuthorizeAttribute to ensure that my controller actions aren't ran when the current user is unauthorized. This works great for GET requests. If the user is not authorized then they are redirected to the login view.
However, this does not work for POST requests as the browser isn't expecting to be redirected during a POST.
The generally agreed upon solution to this is to disallow users from reaching portions of the application which generate POST requests without first being authorized. While this logic makes sense, I don't think it encompasses all real world scenarios.
Consider having the web application open in two tabs. The user is signed in and authorized in both tabs and is viewing content which, when clicked, could initiate a POST request. The user then signs out of one tab and is taken back to the login page. The other tab is unaware that this event has occurred. The user then initiates a POST request from this second tab.
How can I gracefully redirect them to the login page in this scenario?

I believe you are incorrect. I have a custom blog (that still needs a ton of work) which I use the [Authorize] attribute on the Admin controller. This controller handles blog post.
I tested your scenario of:
Opening up two browser screens on my post screen
logging out in the first tab
Attempting to post on the second tab
when it tried to post it redirected me to the login screen.
[Authorize]
public class AdminController : BaseController
{
public ActionResult Post(int? id)
{
if (id != null)
{
var blogPost = _blogService.RequestPost((int)id);
var blogPostViewModel = _blogPostViewModelMapper.CreateViewModel(blogPost);
return View(blogPostViewModel);
}
return View();
}
//
// POST: /Post/
[HttpPost]
public ActionResult Post(BlogPostViewModel blogPost)
{
var stringTags = blogPost.Tags.Split(',');
var tagIds = _blogTagMapper.MapStringsToIds(stringTags);
blogPost.TagIds = tagIds;
_blogService.SaveBlogPost(BlogPostlMapper.CreateEntity(blogPost));
return RedirectToAction("Index", "Home");
}
}

Related

Changing back the url to original if exception thrown and return back to form

I have a thymeleaf signup form, which if we submit then a controller at "/signup_do" is called which validates and saves the user to database:
<form action="/signup_do" method="post">
...
</form>
The controller at "/signup_do" passes the request to the accountRegistration service method, which does the validation:
#PostMapping("/signup_do")
public String register(Account account, HttpSession session) {
session.setAttribute("accountToRegister", account);
accountManagement.accountRegistration(account);
return "Success";
}
The account registration method can throw an exception SignupFormException, which is handled by the #ExceptionHandler defined in that controller class:
#ExceptionHandler(value=SignupFormException.class)
public String handle(HttpSession session, Model response) {
Account returnDataToForm = (Account) session.getAttribute("accountToRegister");
response.addAttribute("name", returnDataToForm.getFirstName());
session.invalidate();
return "signup";
}
Now the problem is that when exception occurs, the inputs entered in the form is passed back to the signup form, and the entered data remains intact, but the url still remains as /signup_do.
I have tried using return "redirect:/signup" instead, which does change the url, but it ends up making a get request to the /signup url like
/signup?name=John...
but my /signup controller is not designed to handle a get request, it just knows to display the form, so the information is lost.
#GetMapping("/signup")
public String signupPage() {return "signup";}
I also tried using forward:/signup, but that just ended up throwing 405 error.
I figured out a clean workaround a few hours after asking this question.
What I did is change the name of the controller that handles the signup process to ("/signup") as well. Since the controller that displays the page is a #GetMapping("/signup") and the one that handles the signup process is a #PostMapping("/signup") there is no clash.
Now even if the controller changes, the url remains the same, since both of them are signup...
#GetMapping("/signup")
public String signupPage() {return "signup";}
#PostMapping("/signup")
public String register(Account account, HttpSession session) {
session.setAttribute("accountToRegister", account);
accountManagement.accountRegistration(account);
return "success";
}
And this works just like I wanted!!
Redirecting will make a get request to the controller looking for the view to display, which in your situation means losing your data for the reasons you give. I can think of two workarounds:
Don't do the redirect and change the URL manually with javascript everytime you enter this view. If you dislike having a "wrong" URL in a view, editing it manually looks the most reasonable and direct approach. You can see how to do this here, including it in a script that executes everytime the page loads/the submit button is pressed.
Do the redirect and avoid losing your info by storing it in the session for a while longer, accessing it in thymeleaf in this way, instead of getting it from a model attribute. This would mean you would have to be careful to remove this session attributes later. It's also not very "clean" that your get request for the form view includes the user info, so I wouldn't go with this solution if avoidable.

Redirect from void action

I have a void function that based on a conditional statement should redirect users to a different action.
public void MyFunction()
{
if (!condition)
{
Redirect(url);
}
...
...
}
Using redirect without a return statement doesn't seem to work. Is there a more suitable method for accomplishing this?
Response.Redirect("Some URL") or Server.Transfer("Some URL") can be used to redirect any web page
Response.Redirect("Some URL") should be used when to :
redirect the request to some plain HTML pages on our
server or to some other web server
not to care about causing additional roundtrips to the server on each request
not need to preserve Query String and Form Variables from the original request
to be able to see the new redirected URL where he is
redirected in his browser (and be able to bookmark it if its
necessary)
Server.Transfer("Some URL") should be used when to:
transfer current page request to another .aspx page on the
same server
preserve server resources and avoid the
unnecessary roundtrips to the server
preserve Query String and Form Variables (optionally) we don't need to show the real URL
where we redirected the request in the users Web Browser
try this
public IActionResult MyFunction()
{
if (!condition)
{
return Redirect(url);
}
...
...
}
About IActionResult
Actions can return anything, but frequently return an instance of
IActionResult (or Task for async methods) that produces
a response. The action method is responsible for choosing what kind of
response. The action result does the responding.
from https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/actions
You may get more information about action methods at above link

Redirecting to a page if session object expires or user did not login to the application

This is a mvc application
I have the links below on my master page
Link1 Link2 Link3 signout signIn
I have a userprofile object that is populated
when authentication is done.
When the session object for the user expires
and I click on the links, I get the yellow page(error page).
I will prefer a situation where when you click on the
links and the session object is expired, you get
redirected to the signin page.
Soln A: I could solve this problem by writing a method
which test for null value of the userprofile object then
make a call to this method on the click on every link.
I dont want this approach because in the future if there
are new controllers, i will need to care for them.
Do you have any idea how I can solve this problem?
I would have a Base Controller than all of your other controllers inherit from. Within this I would have a
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (SessionManager.Instance() == null)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{
{ "Controller", "BaseController" },
{ "Action", "LogOn" }
});
}
base.OnActionExecuting(filterContext);
}
The OnAction Executing will be hit before any method in any of your controllers - this way you can check your session is valid or your user is valid or whatever and handle the redirect to the view you want if that is not the case. If you do a Google search for Filters Actions MVC you will find out much more info.

How to apply single page application functionality on parts of asp.NET MVC3 projects?

So I'm creating a asp.NET MVC3 application and want to apply single page application functionality to parts of the application. I think the easiest way to explain is with an example:
The app consists of an admin area and a public area and is built using ordinary link-structure. I want to convert the admin area to an single page application reusing view and models from the existing application. Is it possible to do this and in that case how?
You have to face two main problems, which makes the difference between SPA and standard application:
Links: In standard application, each link redirects you to a different page.
Forms: When a form is been submitted, a request is been issued with the HTTP method you've specified in the post (usually POST) and it contains in the payload the data the user has entered.
In order to solve that problems, you have to take action both in client-side and server-side.
For explaining propose, lets take the following code:
HomeController.cs:
public class HomeController : Controller {
public ActionResult Index() {
return View();
}
public ActionResult Contact() {
return View(new ContactUsViewModel());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Contact(ContactUsViewModel model) {
if (ModelState.IsValid) {
/* Send mail / Save in DB etc. */
return Redirect("Index");
}
return View(model);
}
}
Index.cshtml:
<p>This is a simple page.</p>
<p>#Html.ActionLink("Click here to contact us", "Contact")
Client-Side:
We should fix up linking between pages, as well as forms submittions.
Links: You can wire up an event in JS (jQuery if you'd like) that'll observe for each link click in the areas you'd like to apply on SPA - then, instead of redirecting the user - you'll load the content via AJAX.
For instance, take a look at this sample:
$("a").click(function(e) {
e.preventDefault(); // Disable standard redirecting
var href = $(e.currentTarget).attr("href");
$.get(href, function(responseText) {
$("#main-content-wrapper").html(responseText);
});
});
Forms: Just like in the approch we've used for links, we can wire up an observer to the form submit event in JS and then transfer the data using AJAX.
For instance:
$("form").submit(function(e) {
e.preventDefault(); // Disable standard submittion
var data = $(e.currentTarget).serialize(); // Serializing the form data
var method = $(e.currentTarget).attr("method");
if (typeof (method) == "undefined") { method = "POST"; }
$.ajax({
method: $(e.currentTarget).attr("method"),
parameters: data,
statusCodes: {
404: function() { /* Handle it somehow */ }
403: function() { /* Handle it... */ }
200: function(response) {
/* Since we've done a form submittion, usurally if we're getting standard OK (200) status code - we've transffered a data - such as JSON data - indicating if the request success or we got errors etc. The code you're writing here depends on how your current application works. */
},
});
});
Server-Side:
Since you don't wish to break your current application logic - you have to still be able to use standard ASP.NET MVC methods - such as View(), Redirect() and so on.
In this case, I recommend to create your own custom base Controller class - which will override ASP.NET basic implementation.
For instance, this is a starting point:
public class MyController : System.Web.Mvc.Controller {
public override View(string viewName) {
if (Request.IsAjaxRequest()) {
return PartialView(viewName); // If this is an AJAX request, we must return a PartialView.
}
return base.View(viewName);
}
}
Few things you have to keep in mind:
You have to somehow distinguish between standard requests and AJAX requests - the way I've used with Request.IsAjaxRequest() is a great way to do so.
Many times when you're handling a form, In the form submittion action, after you finish with the form logic, you're using Redirect() to redirect the user to another page. As you may have guessed, you can't take this approch when developing SPA. However, I can think of few solutions for this problem:
You can create a status handler in the JS code so when redirecting is been issued by the server - you can load the content via AJAX / display a message and so on.
You can override Redirect() and add a specific logic to perform in case of redirection when the request was done by AJAX - for instance, you can request from ASP.NET to perform the action that you're going to be transfered into and then return its content etc.
You can decide that although its an SPA app - when a redirect was issued - you allows the server to perform this redirection.
As you can see - there're many approches you can take, and they depends on the way you've developed your site, how you wish it to work and what is the basic rules you're defining (e.g. "No redirection is permitted never - even after submitting a form", "After form submittion - always in case that the operation success - I'm displaying a message or performing other JS action. Because of that, I can override Redirect() and if this is an AJAX request I can return a JSON object." etc.)

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;
}

Resources