I need to reload an AntiForgeryToken in a form located in a view, after a successfull login in another view in the same page.
Can I make an update in the form input
The issue is occurring because the AntiForgery token contains the username of the currently authenticated user.
So here's what happens:
So you have a couple of options to fix this issue:
The obviousness of solution 1. doesn't make it a good candidate for covering it in my answer. Let's see how the second solution could be implemented.
But first let's reproduce the problem with an example:
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login()
{
FormsAuthentication.SetAuthCookie("john", false);
return Json(new { success = true });
}
[HttpPost]
[ValidateAntiForgeryToken()]
public ActionResult Comment()
{
return Content("Thanks for commenting");
}
}
~/Views/Home/Index.cshtml:
<div>
@{ Html.RenderPartial("_Login"); }
</div>
<div id="comment">
@{ Html.RenderPartial("_Comment"); }
</div>
<script type="text/javascript">
$('#loginForm').submit(function () {
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function (result) {
alert('You are now successfully logged in');
}
});
return false;
});
</script>
~/Views/Home/_Login.cshtml:
@using (Html.BeginForm("Login", null, FormMethod.Post, new { id = "loginForm" }))
{
@Html.AntiForgeryToken()
<button type="submit">Login</button>
}
~/Views/Home/_Comment.cshtml:
@using (Html.BeginForm("Comment", null, FormMethod.Post))
{
@Html.AntiForgeryToken()
<button type="submit">Comment</button>
}
Alright now when you navigate to the Home/Index the corresponding view will be rendered and if you press on the Comment button without logging-in first it will work. But if you login and then Comment it will fail.
So we could add another controller action that will return a partial view with a simple Html.AntiForgeryToken call in order to generate a fresh token:
public ActionResult RefreshToken()
{
return PartialView("_AntiForgeryToken");
}
and the corresponding partial (~/Views/Home/_AntiForgeryToken.cshtml):
@Html.AntiForgeryToken()
And the final step is to refresh the token by updating our AJAX call:
<script type="text/javascript">
$('#loginForm').submit(function () {
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function (result) {
$.get('@Url.Action("RefreshToken")', function (html) {
var tokenValue = $('<div />').html(html).find('input[type="hidden"]').val();
$('#comment input[type="hidden"]').val(tokenValue);
alert('You are now successfully logged in and can comment');
});
}
});
return false;
});
</script>
You can achieve this by simply returning the AntiForgeryToken after they log in.
No need to re-use the same token 2 times.
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model)
{
// do something with login
// return new token as a partial to parse and get value
return this.PartialView("_AntiForgeryPartial");
}
_AntiForgeryPartial:
@Html.AntiForgeryToken()
You can use JS similar to this to load ONLY the new AntiForgeryToken value into the comment form.
View:
$("#LoginForm").submit(function (e) {
e.preventDefault();
var $this = $(this);
$.ajax({
type: $this.attr("method"),
url: $this.attr("action"),
data: $this.serialize(),
success: function (response) {
// get the new token from the response html
var val = $(response).find('input[type="hidden"]').val();
// set the new token value
$('.commentsform input[type="hidden"]').val(val);
}
});
});
When the comment form does the POST, you should be able to validate against the new unique AntiForgeryToken.
Steven Sanderson has a great post on the AntiForgeryToken() if you would like to learn more on how to use it and what it's for.