Problems with clearing a session context immediately after logout with MVC3 - asp.net-mvc

I have a problem with the user Login and Logout in an MVC3 application. Here's an example of my logout actions in my user controller:
public ActionResult Logout()
{
FormsService.SignOut();
Session.Clear();
return RedirectToAction("index", "home");
}
Once a user clicks Logout this action is called, it clears the session information and then the user is redirected to the index page.
On my index page I call a C# helper to display a message like this:
#section links { #Html.HdrLinks("Home", "", Context.User) }
Here's part of the code of the helper:
System.Security.Principal.IPrincipal user
if (user.Identity.IsAuthenticated)
{
return new MvcHtmlString("Welcome " + user.Identity.Name);
}
My problem is that when the index page displays IMMEDIATELY AFTER the logout it still shows the same session information. In other words it still shows a message like "Welcome John". Only AFTER I refresh the page in the browser does it correctly see that there user name is not set in the session.
Does anyone have any ideas on what I can do to make it so that after a logout and a redirect then the index page does not still think the user is logged in? For example is there some call other than RedirecToAction that I could execute that would help me.
UPDATE:
It would seem that when I change my code to the following then it does what I want and when it gets to display the index page then it does not show me I am still logged in. However I am not sure why this works.
FormsService.SignOut();
Session.Clear();
Session.Abandon();
return new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Home", action = "Index" }));

It might be a cache problem. Have you checked that when your code ask a browser to make redirect then the browser really makes a new request to the index page? Just set a breakpoint at index action and take a look to Session property.

Here's a Link to another posting that describes the two things you need to do in more detail:
You need to clear the Session, Auth Cookie, and Session Cookie and return the cookies in the response.
You need to Invalidate the Cache on the Client so user's can't visit cached pages by hitting the browser's back button.
See the Link for details....

Related

Prevent access to page based on Authentication

I have an ASP.Net MVC 4 Website. When I started this site I had little to no web programming experience, and I don't believe I set up all of it appropriately.
I have pages, such as Home, Login, Register, which I consider public,
pages like, VerifyPIN and AccountCreated, which I consider internal
and pages like Dashboard and Profile, which are private.
I feel public pages should be accessed by anonymous users, but Login and Register should not be accessible once a user logs in.
Internal pages I want to only be available upon server redirect and never through the browser, aka I don't want a user to be able to type in www.MySite.com/AccountCreated.
And private pages are for a logged in user only.
I feel I have private pages working fine, but I don't know how to limit access to the other pages as described above. Perhaps my notion to not allow a logged in user to access the log in page is miss-found.
My site is relatively small due to the ajax nature of the site. I use [AllowAnonymous] on all public pages but then a logged in user can still access those and I am not sure how to prevent that.
My questions are,
How can I prevent a user from accessing a page via the address bar (www.MySite.com/AccountCreated)
How can I prevent access to a [AllowAnonymous] page once a user has logged in.
EDIT
Is it normal to prevent logged on users from accessing certain anonymous pages, such as login?
You may always check if user is already logged in. If he did, he will be redirected to another page:
public ActionResult AccountCreated(some params)
{
if (Request.IsAuthenticated)
{
return RedirectToAction("Index")
}
else
{
// login logic here
}
}
You may also check it directly in View:
#if (Request.IsAuthenticated)
{
<span> You are already logged in</span>
}
else
{
//Login form here
}
well for your first question you can use the [Authorize] action filter.
For your other question, I guess you will have to write your own implementation. You will need to create a new IAuthorizationFilter and use it instead of [AllowAnonymous]
Just some ideas(didn't try them actually).
For question 1 - if AccountCreated is an action that means that the registration form actually POSTs to that URL - i.e. It is accessible from outside. I suggest you apply an HttpPost attribute to it so it will process only POST requests - there is not much you can do here
For point two you can do something like this in your action method
if (HttpContext.User.Identity.IsAuthenticated)
{
return RedirectToAction("Index", "Home");
}
else return View();
Just a thought, Signout user when a user goes to Login or Register page .
Hope you have not provided any link on private pages for a user to go to login or Register
page or check if user is authenticated redirect it to Home page, frankly you can not stop a user to write a URL and pressing the enter button, or just give a notification that You'll be logged out if you proceed to whatever page.

Logout in MVC using Session without JavaScript/JQuery

Hi I'm relatively new to MVC 3 and I'm working with logging out my application, I managed to do the logging in, staying logged in even if website is opened on another tab.
All of my views (except for the home, of course) has this:
Logout
and in my controller I have this:
public ViewResult Logout()
{
Session.Abandon();
return View("Index", "Home");
}
and the app is not logging out, instead it is returning the current view.
Please help me understand what to do, and I would like to take note that I am using ViewResult instead of ActionResult, I'm not also going to use JavaScript or JQuery, because I'm making this app to show how MVC works.
Shouldn't you be pointing to the logout action?
Logout
Assuming the path to you're logout action is \Home\Logout
UPDATE:
Another old fashioned way is..
Upon successful login..
Session["Login"] = true; //or any object that describes the user's identity
On every page you need to check
var login = Session["Login"];
if(Convert.ToBoolean(login)){ //or cast to your expected object
//do something
}
else{
//redirect to logout/login page
}
Upon logout,
Session["Login"] = null;
If you are using forms authentication you should also clear the authentication cookie and redirect after logging out:
public ActionResult Logout()
{
Session.Abandon();
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}

ASP.NET MVC - How to maintain ModelState from a different controller?

I have a HomeController with an Index action that shows the Index.aspx view. It has a username/password login section. When the user clicks the submit button, it POSTs to a Login action in the AccountController.
<% Html.BeginForm("Login", "Account", FormMethod.Post); %>
In that action, it tests for Username/Password validity and if invalid, sends the user back to the Login page with a message that the credentials were bad.
[HttpPost]
public ActionResult Login(LoginViewModel Model, string ReturnUrl)
{
User user = MembershipService.ValidateUser(Model.UserName, Model.Password);
if (user != null)
{
//Detail removed here
FormsService.SignIn(user.ToString(), Model.RememberMe);
return Redirect(ReturnUrl);
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
// If we got this far, something failed, redisplay form
return RedirectToAction("Index", "Home"); // <-- Here is the problem. ModelState is lost.
}
But here's the problem: the ValidationSummary is always blank because we're losing the Model when we RedirectToAction.
So the question is: How do I send the user to the action on a different controller without a Redirect?
As others have said it's common to return the view if validation fails but as you are calling from your account controller you will want to specify the full path of your view
return View("~/Views/Home/Index.aspx", model);
Or
It is also common to have a seperate login page and redirect to that page if the login fails. Both pages will submit to the same login action. Facebook does this for example.
Or
As you only want to display an error message
return RedirectToAction("Index", "Home", new { LoginAttempts = 1 });
then in your Index action read the LoginAttempts parameter and choose to display the error message accordingly.
Use TempData to save state between requests. Use special attributes for convenience as shown here.
Few moments to mention:
Don't return View directly from your POST-action, respect Post-Redirect-Get pattern.
Don't overuse TempData. It's only supposed to save model state right before redirect and to retrieve it right after being redirected.
Well you could always do this
return View("~/Views/Home/Index.aspx", myModel);
It's not a real redirect, the clients url will still point to /login/ but at least you have your modalstate
Three options
You could call the action directly, but the client side will not have its URL changed. So instead of calling RedirectToAction you could call the Index() method of the HomeController class directly.
HomeController c = new HomeController();
c.ViewData = this.ViewData;
return c.Index(data);
The one is a bit tricky. Maybe you will have to set other things as well apart from ViewData which is needed for ModelState.
You could as well use TempData dictionary and fill it with whatever data you want and use that.
The simplest one where you provide full path to the view
return View("~/Views/Home/Index.aspx", data);
A better suggestion used by big players
If we look at how other sites do this kind of scenario. Take for instance Twitter (As #David says Facebook apparently does it the same). You can sign in from the Home/Index action (so to speak if it was developed using Asp.net MVC). But when login fails it displays a separate login page, that displays validation errors. In your case it would be Account/SignIn. Which would make sense and you could directly return its view with validation errors. When everything would be ok, you'd do it as you do it now. Redirect back to Home/Index.
Try using
return View("Index", "Home", Model)

asp.net mvc: TempData and AuthorizeAttribute

As a followup to this question, I'm wondering what's happening to my TempData.
Scenario 1:
user logs in
user provides email address
user receives email with validation code
user clicks on validation url
user is validated
success msg is displayed via TempData set in Validate action
Scenario 2:
user logs in
user provides email address
user logs out/times out
user receives email with validation code
user clicks on validation url
user is validated
success msg is not displayed via TempData set in Validate action
Now, I don't see a reason for the user to be logged in to validate. In Scenario 1, I put a "Success" message in TempData, and return RedirectToAction("Index"). Index action has an AuthorizeAttribute - if they're not logged in, they're redirected to the login screen (seperate controller).
I would like the login screen to display my message, but TempData appears to get cleared in this scenario. Am I misunderstanding the TempData lifecycle? Does it only apply to requests within the same controller?
The problem is that the AuthorizeAttribute is introducing another redirect into the cycle if the user is not logged in. You are redirecting the user to another action then, if the user is not logged in, the AuthorizeAttribute redirects them to the login page. TempData only lives over one request cycle, so the extra redirect (request) is emptying it and it isn't available on the login page.
You might consider just putting it in the Session directly instead of the TempData front-end to the Session. It should still be there as long as the Session lives.
[Authorize] introduces an extra redirect, which clears the TempData (Tvanfosson has explained the details). So for this to work, you can use a flag on the method you redirect to, for example
return RedirectToAction("Confirm", new { status = "Success!" });
(given that you have the following route and action method declared:)
routes.MapRoute("Confirmation",
"Account/Confirm/{status}",
new { controller = "Account", action = "Confirm", status = "" });
public ActionResult Confirm(string status)
{
return View(status);
}

How to create a view that is not accessible directly but can only be redirected to?

I'm currently working on the user registration in my project. After the registration is done I wish to show some confirmation to the user. I decided to create another view. That's fine.
Now, if after the registration I just return the view like:
public class MyController : Controller
{
[AcceptVerbs (HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult Registration (FormCollection form)
{
/* Some logic goes here */
return View ("ConfirmationView");
}
}
Everything is working as desired. No changed url in the title bar. But... If I click the refresh button, the browser will submit the data from the form again which I do not want.
Then I decided to create a separate action, but that means it will produce a new url in the address bar. I do not want the user to click refresh now because this view will not be able to sensibly display the confirmation information again. Is there any way to make an action not accessible directly? Or at least any way to determine whether it was called directly or by redirection? In the latter case I would just take the user away from that page to maybe the home page.
Any way to accomplish this?
So I found the solution myself.
One can use TempData to detect the repeated or external action calls.
public class MyController : Controller
{
[AcceptVerbs (HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult Registration (FormCollection form)
{
/* Some logic goes here */
TempData["RedirectCall"] = true;
return RedirectToAction ("Confirmation");
}
[AcceptVerbs (HttpVerbs.Get)]
public ActionResult Confirmation ()
{
if (TempData["RedirectCall"] == null)
return RedirectToAction ("StartPage", "Home");
return View ();
}
}
Nice and simple. :)
One way to solve your problem is to attach a guid or similar type of "random" data to a user session, and check for a valid session when the page is requested. If there is none, you redirect to a page saying that this url is not available at the moment, and that the user will soon be redirected (and then redirect to home after 5 seconds or so using js).
Roughly it would work like this:
When the user is registered, a session cookie is created with for example a GUID. The GUID is also stored in a database table, in which you have one column for the UserID primary key and one for the GUID. You also create an authentication cookie, thus logging the user on to your site.
When all datacalls etc are done, the user has been successfully registered and so on, you redirect to the confirmation page.
When the confirmation page is loaded, the user is automatically logged on (because you created the authentication cookie in step 1). You can then check for a row in the UserID-GUID table corresponding to the logged on user.
a) If there is such a row, you delete the row, and display the confirmation page with all the information.
b) If there is no such row, you display the error message and redirect. As you deleted the row when you showed the message the first time, the user will not be able to access the confirmation page again.
Note: If you use this approach (or some other that makes the confirmation page available only once) you should make sure that it is clearly stated on the confirmation page that the user won't be able to access that page again.
if(Request.UrlReferrer == null)
{
return RedirectToAction("Index");
}

Resources