My project is based on the MVC 5 project template from Visual Studio 2013 (individual user account option). I have been relying on the default Sign In and Sign Out method for my users. But I'm not sure what I did, at some point, users cannot sign out anymore, however they can sign in as another user.
This is the default Logoff method of Account Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
AuthenticationManager.SignOut();
return RedirectToAction("Index", "Home");
}
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.GetOwinContext().Authentication;
}
}
This is the default _LoginPartial.cshtml View that shows user's username.
#using Microsoft.AspNet.Identity
#if (Request.IsAuthenticated)
{
using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", #class = "navbar-right" }))
{
#Html.AntiForgeryToken()
<ul class="nav navbar-nav navbar-right">
<li>
#Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
</li>
<li>Log off </li>
</ul>
}
}
else
{
<ul class="nav navbar-nav navbar-right">
<li>#Html.ActionLink("Register", "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
<li>#Html.ActionLink("Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
</ul>
}
When user signs out, it directs user to the login page, but user's username is still shown which means that they have not signed out. And the url on the browser shows
http://localhost/Account/Login?ReturnUrl=%2FAccount%2FLogOff
It is not taking the user back to Index page of Home. So my guess is that something happened at the statement AuthenticationManager.SignOut();. I'm confused because I haven't changed anything to the Account Controller.
Any lead would be greatly appreciated.
I had the same problem.
Check this Issue on CodePlex:
http://web.archive.org/web/20160403071605/https://aspnetidentity.codeplex.com/workitem/2347
Try replacing AuthenticationManager.SignOut() with
AuthenticationManager.Signout(DefaultAuthenticationTypes.ApplicationCookie);
I hope that I help you. :-)
Simply add this line of code after SignOut():
HttpContext.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);
Also check this out:
Page.User.Identity.IsAuthenticated still true after FormsAuthentication.SignOut()
I figured my problem was not at SignOut(). If you think your problem is at SignOut() (and using Owin Authentication) check out Sergio's link.
For my case is a stupid mistake!
I forgot I added [Authorize(Role = "admins")] to the controller, because I only wanted the admins to use the register method that comes with the default template. And the result is that no one except the admins can log out!
This is what I had:
[Authorize(Roles = "admin")]
public class AccountController : Controller
{
public ActionResult LogOff()
{
}
public ActionResult Register()
{
}
}
What I did is I just move the register methods to a new controller, like below:
[Authorize]
public class AccountController : Controller
{
public ActionResult LogOff()
{
}
}
and
[Authorize(Roles = "admin")]
public class AdminController : Controller
{
public ActionResult Register()
{
}
}
Now everyone can log out and only admins can register users.
(And the default AuthenticationManager.SignOut() works fine.)
Replace
AuthenticationManager.Signout();
with
AuthenticationManager.Signout(DefaultAuthenticationTypes.ApplicationCookie);
as per the issue in Sergio's answer
I have faced with the same problem..
Below is a standard ASP.Net MVC logOff procedure presented by Microsoft and it works, except user after logoff and login you will get wrong, It happens because authentication token. And as soon as you will release that it simple to modify. But nobody says about that.
// POST: /Account/LogOff
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
return RedirectToAction("Index", "Home");
}
As you can see that the standard way of logoff in AccountController
That the html code, with my little hack -> id="logoffbtn"
#using (Html.BeginForm("LogOff", "Account"))
{
#Html.AntiForgeryToken()
<button class="btn btn-default btn-flat" id="logoffbtn"
type="submit">Logout</button>
}
So solution to properly logoff using standard way is to add somewhere in your main java script file little hack:
$(document).ready(function()
{
try
{
$('#logoffbtn').click(function ()
{
// alert('Sign new href executed.');
console.log("Session token start");
sessionStorage.removeItem('accessToken');
console.log("Session token done");
});
} catch (e)
{
console.log("Session token err");
}
});
So what will happen next..basically after pressing logoff button you will clear access Token and now logoff will start working normally.
Hope that helps.
P.S. I have tried really many ways and only this was a hack for me.
Related
Hi Guys' I am getting HTTP 404 in my MVC project and I am not sure where it is coming from.
On the _NavBar I have an ActionLink control that is calling to CreateSite
<li>
#Html.ActionLink("Create Location","CreateSite", "SiteRegistration", routeValues: null, htmlAttributes: new { id = "registerLink" })
</li>
I have an Controller name SiteRegistrationController
public class SiteRegistrationController : Controller
{
SiteInfoManager manager = new SiteInfoManager();
//
// GET: /SiteRegistration/
public ActionResult UserSiteResult()
{
return View();
}
}
The views I have are Folder SiteRegistration and CreateSite.cshtml.
The Error is coming from the SiteInfoManager line in the Controller.
Any help would be great.
You need to update your #Html.ActionLink to:
#Html.ActionLink("Create Location","UserSiteResult", "SiteRegistration", new { id = "registerLink" }, null)
And update your Controller:
public ActionResult UserSiteResult(string id)
{
// you can use id now, as it will be regirsterLink
return View();
}
I am trying to get my head around MVC 5 Web Application template, and I noticed that special attention is given to the security around the LogOff link.
In the scaffold template the "LogOff" link in the _LoginPartial.cshtml view sits inside an HTML form with an AntiForgeryToken in it, and is defined as a JS call to form's submit action, like so:
#if (Request.IsAuthenticated)
{
using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", #class = "navbar-right" }))
{
#Html.AntiForgeryToken()
<ul class="nav navbar-nav navbar-right">
<li>
#Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
</li>
<li>Log off</li>
</ul>
}
}
With the corresponding action method Account/LogOff inside ActionController defined like so:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
AuthenticationManager.SignOut();
return RedirectToAction("Index", "Home");
}
My question is - what is the reasoning behind it? Why does the LogOff action require so much security protection? Why not just have this in the view,
#Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
#Html.ActionLink("Log Off", "LogOff", "Account", routeValues: null, htmlAttributes: new { title = "LogOff" })
And this in the controller:
public ActionResult LogOff()
{
AuthenticationManager.SignOut();
return RedirectToAction("Index", "Home");
}
What security hole would this create?
Thanks.
Please refer to this link: Logout: GET or POST?.
It will answer your question on why Post should be used in logout.
I'm having problem getting my view to display. I'm creating a link
#Html.ActionLink("Add as user", "Register", "Account")
but when I click on it, I'm getting this message:
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /Account/Register
Here's my view:
#model ContactWeb.Models.SimpleUser
#{
ViewBag.Title = "CreateUser";
}
<h2>Create User</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true);
<fieldset>
<legend>Create User</legend>
<div>
#Html.LabelFor(c=>c.Username, "User Name")
#Html.TextBoxFor(c=>c.Username)
#Html.ValidationMessageFor(c=>c.Username)
</div>
<div>
#Html.LabelFor(c=>c.Password, "Password")
#Html.TextBoxFor(c=>c.Password)
#Html.ValidationMessageFor(c=>c.Password)
</div>
<div>
#Html.LabelFor(c=>c.ConfirmPassword, "Confirm Password")
#Html.TextBoxFor(c=>c.ConfirmPassword)
#Html.ValidationMessageFor(c=>c.ConfirmPassword)
</div>
<p>
<input type="submit" value="Register" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "List")
</div>
and my controller is
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
MembershipCreateStatus createStatus;
Membership.CreateUser(model.UserName, model.Password, null, null, null, true, null, out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
FormsAuthentication.SetAuthCookie(model.UserName, false);
return RedirectToAction("List", "Contact");
}
else
{
ModelState.AddModelError("", "The username or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
You do want an overload of the Register method for the initial GET request, but it should not have any parameters:
[HttpGet]
public ActionResult Register()
{
return View(new RegisterModel());
}
[HttpPost]
public ActionResult Register(RegisterModel model)
{
// your existing implementation here that
// checks ModelState and creates the user record
}
This will allow displaying a form with empty/default values when first loading the /Account/Register URL. This will prevent the "already defines a method with the same parameter types" and thus allow the code to compile. Also, I think you will find this advantageous to having just one Register method with no HttpPost attribute because it allows you to have a separate POST-only method to implement the postback logic, and a simpler GET-only method for the initial display. You could even customize the GET-only display to populate the model/view with certain initial values, etc.
Your register action is decorated with [HttpPost] attribute, that means that action can handle only HTTP POST requests. Ordinary links make GET requests, and as there's no handler for GET, you get 404 - not found. To fix this, create another action that will handle GET requests
[HttpGet]
public ActionResult Register()
{
return View();
}
This action will return page, with registration form on it.
I've looked over a bunch of other reports of this, but mine seems to be behaving a bit differently. I am returning PartialViewResults for my child actions, so that's not the source of the recursion. Here's a dumbed down version of what I have.
// The Controller
[ChildActionOnly]
public ActionResult _EditBillingInfo()
{
// Generate model
return PartialView(model);
}
[HttpPost]
public ActionResult _EditBillingInfo(EditBillingInfoViewModel model)
{
// Update billing informatoin
var profileModel = new EditProfileViewModel()
{
PartialToLoad = "_EditBillingInfo"
};
return View("EditProfile", profileModel);
}
[ChildActionOnly]
public ActionResult _EditUserInfo()
{
// Generate model
return PartialView(model);
}
[HttpPost]
public ActionResult _EditUserInfo(EditUserInfoViewModel model)
{
// Update user informatoin
var profileModel = new EditProfileViewModel()
{
PartialToLoad = "_EditUserInfo"
};
return View("EditProfile", profileModel);
}
public ActionResult EditProfile(EditProfileViewModel model)
{
if (String.IsNullOrEmpty(model.PartialToLoad))
{
model.PartialToLoad = "_EditUserInfo";
}
return View(model);
}
// EditProfile View
#model UPLEX.Web.ViewModels.EditProfileViewModel
#{
ViewBag.Title = "Edit Profile";
Layout = "~/Views/Shared/_LoggedInLayout.cshtml";
}
<div>
<h2>Edit Profile</h2>
<ul>
<li class="up one"><span>#Ajax.ActionLink("Account Information", "_EditUserInfo",
new AjaxOptions { UpdateTargetId = "EditProfileDiv", LoadingElementId = "LoadingImage" })</span></li>
<li class="up two"><span>#Ajax.ActionLink("Billing Information", "_EditBillingInfo",
new AjaxOptions { UpdateTargetId = "EditProfileDiv", LoadingElementId = "LoadingImage" })</span></li>
</ul>
<img alt="Loading Image" id="LoadingImage" style="display: none;" src="../../Content/Images/Misc/ajax-loader.gif" />
<div id="EditProfileDiv">
#Html.Action(Model.PartialToLoad)
</div>
</div>
The partial views are both forms for updating either the user information or billing information.
I debugged through this and found what is happening, but cannot figure out why. When a user browses to EditProfile, it load up with the _EditUserInfo partial and the form is there for editing. When you change some info and submit the form it hangs and you get a StackOverflowException in the EditProfile view on the call to #Html.Action(). What happens is on the initial visit to EditProfile, the #Html.Action calls the HttpGet version of _EditUserInfo. You make some changes to the user info and click submit. Once the information is updated the EditProfile view is returned again, but this time #Html.Action calls the HttpPost version of _EditUserInfo which updates the user information again, returns the EditProfile view again and the #Html.Action calls the HttpPost version of _EditUserInfo... You get where this is going. Why after form submission does it call the post version and not the get version like it did for the initial visit to EditProfile?
Thanks for any help!
I might be getting this a bit wrong, it's been a long day so, but in EditProfile you set PartialToLoad (if it's empty) to "_EditUserInfo", then in _EditUserInfo you set it again to _EditUserInfo, won't this create a loop that behaves as what you are experiencing?
Lets suppose that I have some pages
some.web/articles/details/5
some.web/users/info/bob
some.web/foo/bar/7
that can call a common utility controller like
locale/change/es or authorization/login
How do I get these methods (change, login) to redirect to the previous actions (details, info, bar) while passing the previous parameters to them (5, bob, 7)?
In short: How do I redirect to the page that I just visited after performing an action in another controller?
try:
public ActionResult MyNextAction()
{
return Redirect(Request.UrlReferrer.ToString());
}
alternatively, touching on what darin said, try this:
public ActionResult MyFirstAction()
{
return RedirectToAction("MyNextAction",
new { r = Request.Url.ToString() });
}
then:
public ActionResult MyNextAction()
{
return Redirect(Request.QueryString["r"]);
}
If you want to redirect from a button in the View you could use:
#Html.ActionLink("Back to previous page", null, null, null, new { href = Request.UrlReferrer})
If you are not concerned with unit testing then you can simply write:
return Redirect(ControllerContext.HttpContext.Request.UrlReferrer.ToString());
A suggestion for how to do this such that:
the return url survives a form's POST request (and any failed validations)
the return url is determined from the initial referral url
without using TempData[] or other server-side state
handles direct navigation to the action (by providing a default redirect)
.
public ActionResult Create(string returnUrl)
{
// If no return url supplied, use referrer url.
// Protect against endless loop by checking for empty referrer.
if (String.IsNullOrEmpty(returnUrl)
&& Request.UrlReferrer != null
&& Request.UrlReferrer.ToString().Length > 0)
{
return RedirectToAction("Create",
new { returnUrl = Request.UrlReferrer.ToString() });
}
// Do stuff...
MyEntity entity = GetNewEntity();
return View(entity);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(MyEntity entity, string returnUrl)
{
try
{
// TODO: add create logic here
// If redirect supplied, then do it, otherwise use a default
if (!String.IsNullOrEmpty(returnUrl))
return Redirect(returnUrl);
else
return RedirectToAction("Index");
}
catch
{
return View(); // Reshow this view, with errors
}
}
You could use the redirect within the view like this:
<% if (!String.IsNullOrEmpty(Request.QueryString["returnUrl"])) %>
<% { %>
Return
<% } %>
In Mvc using plain html in View Page with java script onclick
<input type="button" value="GO BACK" class="btn btn-primary"
onclick="location.href='#Request.UrlReferrer'" />
This works great. hope helps someone.
#JuanPieterse has already answered using #Html.ActionLink so if possible someone can comment or answer using #Url.Action
I'm using .Net Core 2 MVC , and this one worked for me,
in the controller use
HttpContext.Request.Headers["Referer"];
Pass a returnUrl parameter (url encoded) to the change and login actions and inside redirect to this given returnUrl. Your login action might look something like this:
public ActionResult Login(string returnUrl)
{
// Do something...
return Redirect(returnUrl);
}
You could return to the previous page by using ViewBag.ReturnUrl property.
To dynamically construct the returnUrl in any View, try this:
#{
var formCollection =
new FormCollection
{
new FormCollection(Request.Form),
new FormCollection(Request.QueryString)
};
var parameters = new RouteValueDictionary();
formCollection.AllKeys
.Select(k => new KeyValuePair<string, string>(k, formCollection[k])).ToList()
.ForEach(p => parameters.Add(p.Key, p.Value));
}
<!-- Option #1 -->
#Html.ActionLink("Option #1", "Action", "Controller", parameters, null)
<!-- Option #2 -->
Option #2
<!-- Option #3 -->
Option #3
This also works in Layout Pages, Partial Views and Html Helpers
Related: MVC3 Dynamic Return URL (Same but from within any Controller/Action)
For ASP.NET Core
You can use asp-route-* attribute:
<form asp-action="Login" asp-route-previous="#Model.ReturnUrl">
Other in details example:
Imagine that you have a Vehicle Controller with actions
Index
Details
Edit
and you can edit any vehicle from Index or from Details, so if you clicked edit from index you must return to index after edit
and if you clicked edit from details you must return to details after edit.
//In your viewmodel add the ReturnUrl Property
public class VehicleViewModel
{
..............
..............
public string ReturnUrl {get;set;}
}
Details.cshtml
<a asp-action="Edit" asp-route-previous="Details" asp-route-id="#Model.CarId">Edit</a>
Index.cshtml
<a asp-action="Edit" asp-route-previous="Index" asp-route-id="#item.CarId">Edit</a>
Edit.cshtml
<form asp-action="Edit" asp-route-previous="#Model.ReturnUrl" class="form-horizontal">
<div class="box-footer">
<a asp-action="#Model.ReturnUrl" class="btn btn-default">Back to List</a>
<button type="submit" value="Save" class="btn btn-warning pull-right">Save</button>
</div>
</form>
In your controller:
// GET: Vehicle/Edit/5
public ActionResult Edit(int id,string previous)
{
var model = this.UnitOfWork.CarsRepository.GetAllByCarId(id).FirstOrDefault();
var viewModel = this.Mapper.Map<VehicleViewModel>(model);//if you using automapper
//or by this code if you are not use automapper
var viewModel = new VehicleViewModel();
if (!string.IsNullOrWhiteSpace(previous)
viewModel.ReturnUrl = previous;
else
viewModel.ReturnUrl = "Index";
return View(viewModel);
}
[HttpPost]
public IActionResult Edit(VehicleViewModel model, string previous)
{
if (!string.IsNullOrWhiteSpace(previous))
model.ReturnUrl = previous;
else
model.ReturnUrl = "Index";
.............
.............
return RedirectToAction(model.ReturnUrl);
}