We have an ASP.NET MVC web application where the user is redirected to the original url after login if the user had tried to reach a certain url while not being authenticated.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel viewModel, string returnUrl = null) //relative url
{
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
The problem is that some urls contain encoded characters, e.g. /bla/bla?param=abc%23. They are decoded by .NET (correct?) so that the url looks like /bla/bla?param=abc# on the server. This causes the redirect to stop working, as param is interpreted as 'abc' in the target action.
What is best practice to handle this problem? I tried WebUtility.UrlEncode(returnUrl), but it seems that the routing engine cannot handle the resulting url.
Please correct anytime if my reasoning is wrong.
Related
I just started fiddling around with OWIN/Katana and MVC.NET 5.0. The default Visual Studio 2013 ASP.NET Web Application/MVC Template has an AccountController with a LogOut() action:
public ActionResult LogOff() {
AuthenticationManager.SignOut();
return RedirectToAction("Index", "Home");
}
As expected, this works just fine. However, when I change the response status code, e.g. by:
Response.SetStatus(HttpStatusCode.SeeOther);
... The AuthenticationManager.SignOut() method no longer causes the user to become logged off. Why is that?
I tried different approaches for setting the http status code for the response, as well as changing http headers like Location, and always with the same result - the user is not logged off when the LogOff() action is executed, if I get into tempering with the response.
I tried not using RedirectToAction (which explicitly implements a 302 redirect - that's another story), and not returning an ActionResult, but that made no difference - not that I'd really expect it to.
Using Fiddler I can tell that the response as it appears to the browser looks fine, not holding any surprises.
I also tried looking through the source code of the OWIN middleware at work, but the architecture is still unfamiliar to me, and I found no answers that I could grasp in there. I need your help in sorting this out, so thank you in advance!
The reason AuthenticationManager.SignOut() fails is that Response.SetStatus(HttpStatusCode.SeeOther) internally ends the response:
public static void SetStatus(this HttpResponseBase response, int httpStatusCode)
{
response.StatusCode = httpStatusCode;
response.End();
}
(See System.Web.WebPages.ResponseExtensions)
After this, naturally the ResponseManager cannot manipulate the response to remove cookies etc.
This works fine for me with the following LogOut method, are you doing something slightly differently?
//
// POST: /Account/LogOff
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
Response.StatusCode = 303;
AuthenticationManager.SignOut();
return RedirectToAction("Index", "Home");
}
When people register on my ASP.NET MVC website I send an activation link via email so they can activate their account and login. I want to show a message when the registration is successful and the email is sent. To do that I redirect to another page.
I do not want to show this page when people go directly to this URL because it is not a normal page. I use TempData to check if they are coming from the registration page.
public ActionResult Register()
{
AccountRegisterView accountView = InitializeAccountRegisterViewWithIssue(false, "");
return View(accountView);
}
[HttpPost]
public ActionResult Register(AccountRegisterView accountView)
{
if (!ModelState.IsValid)
{
return View(accountView);
}
// Register user and send activation link via email...
TempData["success"] = true;
return RedirectToAction("RegisterEmail");
}
public ActionResult RegisterEmail()
{
if (TempData["success"] != null)
{
return View();
}
return RedirectToAction("Login");
}
I would like to know if this is considered best practice. Or should I do this differently?
I think this is a perfectly good use of TempData[]. You need a variable for a one time request to decide if you should show the page or not.
I suppose you could use a session variable, but you would need to remember to clear the session. You could use some type of registration key, but then you would need to track those as well. Long story short, nope, you are good.
I am trying to set a cookie upon login and having issues with getting the current user id after login. In the below, intUserId is -1 and WebSecurity.IsAuthenticated is false. Is this not the correct place to put this code? After this, it redirects to the home page...so not sure why this is not the correct place.
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
//TODO: set current company and facility based on those this user has access to
int intUserId = WebSecurity.GetUserId(User.Identity.Name);
int intUserId2 = WebSecurity.GetUserId(model.UserName);
UserSessionPreferences.CurrentCompanyId = 1;
UserSessionPreferences.CurrentFacilityId = 1;
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
Login only sets the Forms Authentication cookie.
The way asp.net authentication works is that it must read the cookie to set the authenticate the request, but since the cookie did not exist when the Login page was started, the framework doesn't know anything about the user.
Reload the page, and you will find the information is available.
FYI, this is nothing new with SimpleMembership or WebSecurity, this is the way Forms Authentication has always worked.
i'm using RedirectToAction to redirect after a post to another controller and it's working. so i tried to Redirect to another action in the same controller and it's NOT working too.
public ActionResult finalize(int id)
{
Meeting meeting = db.Meetings.Find(id);
meeting.meetingStatus = "finalized";
db.SaveChanges();
return RedirectToAction("Index");
}
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(Meeting meeting)
{
if (ModelState.IsValid)
{
db.Meetings.Add(meeting);
db.SaveChanges();
// return RedirectToAction("Invitation");
return (RedirectToAction("finalize", new { id = meeting.meetingID}));
}
return View(meeting);
}
you cant make 2 redirects in the same call , a redirect just return an http redirect code to another page , redirecting 2 times just wont work, don't treat action methods like normal methods they are different
for example when you request a page example.com/controller/action
action will be executed and for example it has a return value with RedirectToAction("NotFound")
what will happen in this scenario is
action body will be executed and the return value will send to the client an http header of 302 that says your new destination is /controller/NotFound
so RedirectToAction just return an http code to the client its not calling another method
Update
i was wrong i checked with fiddler2 , you can use redirect to action many times , what will happen the server will send multiple http redirect headers for each one
i jumped to conclusion this fast because i didnt think multiple redirect is the right way to do it, so after i tried it , i can say it works , just create a new project to see where is the problem exactly or use a tool like fiddler2
I have master page that has this control that accepts Post verb. My LogOn page is also tied to the master page. When I enter wrong username/password, method of that control that accepts verb also gets fired along with the method to accept username password.
This is on HomeConroller:
[ActionName("ControlTemp"), AcceptVerbs(HttpVerbs.Post)]
public ActionResult ControlTemp(TempClass temp)
{
return PartialView("ControlTemp");
}
This is on AccountController:
[AcceptVerbs(HttpVerbs.Post)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
Justification = "Needs to take same parameter type as Controller.Redirect()")]
public ActionResult LogOn(string userName, string password, bool rememberMe, string returnUrl)
{
if (!ValidateLogOn(userName, password))
{
return View();
}
FormsAuth.SignIn(userName, rememberMe);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
Now, why do you think things are being posted to ControlTemp too?
EDIT: This is how I am referring to ControlTemp control on masterpage.
<div id = "divControlTemp"> <% Html.RenderAction("ControlTemp", "Home"); %></div>
So after watching debugger, I saw it returns View() if username/password is invalid. It then hits the divControlTemp control, but instead of firing
public ActionResult ControlTemp(), it fires
[ActionName("ControlTemp"), AcceptVerbs(HttpVerbs.Post)]
public ActionResult ControlTemp(TempClass temp)
and that is all I can see from the debugger. So my question is why do you think it thinks it is a post? Is it because the reurn View() was called from a method that was accepting Post verb?
If your question is "can a single http requests fire two separate action methods", the answer is no. Unless of course there is a redirect from one action method to the other or one of the methods calls the other directly. If there are redirections, you can see them in the Firebug console.
But frankly, if this was my software, I'd simply fire up the debugger and see what's going wrong. It shouldn't be too difficult to spot the problem this way.