Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reload AntiForgeryToken after a login

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 @Html.AntiForgeryToken() key with the new one from the result login page via jQuery?

If yes, is this recomended and secure?

How can I do it?

EDIT:

In the Layout I have diferent PartialViews:

The Partial to login:

<ul class="menudrt" id="headerLogin">
    @{ Html.RenderAction(MVC.Account.LoginHeader()); }
</ul>

And in another Partial, the hability to send a comment:

<div class="comentsform">

    <!-- Comments form -->
    @{ Html.RenderAction(MVC.Comment.Create()); }

</div>

To send a comment, the user have to login, so after login, the comment form needs to update the AntiForgeryToken or I get the validation error because it's diferent now that the login has been made.

Thanks

like image 271
Patrick Avatar asked May 29 '13 13:05

Patrick


People also ask

Do anti forgery tokens expire?

This cookie is used to store the antiforgery token value in the client side, so clients can read it and sends the value as the HTTP header. Default cookie name is XSRF-TOKEN , expiration time is 10 years (yes, ten years!

Where are anti forgery tokens stored?

ASP.NET Core uses a hidden field to store the anti-forgery token and uses the ValidateAntiForgeryToken attribute to validate the token. As the token is sent to the browser in a hidden field, it is also stored in an HttpOnly cookie.

How does Antiforgery token work?

Anti-Forgery TokensOne token is sent as a cookie. The other is placed in a hidden form field. The tokens are generated randomly so that an adversary cannot guess the values. When the client submits the form, it must send both tokens back to the server.

What is the Antiforgery token could not be decrypted?

Error: The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the <machineKey> configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster.


1 Answers

The issue is occurring because the AntiForgery token contains the username of the currently authenticated user.

So here's what happens:

  1. An anonymous user navigates to your page
  2. An antiforgery token is generated for the comment form but this token contains an empty username (because at that moment the user is anonymous)
  3. You are using an AJAX call to login
  4. The user submits the comment form to the server and the token validation fails because the empty username contained in the initial token is different than the currently authenticated username.

So you have a couple of options to fix this issue:

  1. At step 3. do not use an AJAX call. Use a standard form submit to login the user and redirect him back to the initially requested page. The comment form will of course be reloaded and correct antiforgery token generated for it.
  2. Refresh the antiforgery token after logging-in

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>
like image 81
Darin Dimitrov Avatar answered Oct 11 '22 22:10

Darin Dimitrov