Reload AntiForgeryToken after a login

前端 未结 2 1579
感动是毒
感动是毒 2021-01-02 04:48

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

相关标签:
2条回答
  • 2021-01-02 05:37

    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>
    
    0 讨论(0)
  • 2021-01-02 05:49

    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.

    0 讨论(0)
提交回复
热议问题