Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the XsrfKey used for and should I set the XsrfId to something else?

In my MVC 5 web app I have this (in AccountController.cs):

    // Used for XSRF protection when adding external sign ins
    private const string XsrfKey = "XsrfId";

and

        public string SocialAccountProvider { get; set; }
        public string RedirectUri { get; set; }
        public string UserId { get; set; }

        public override void ExecuteResult(ControllerContext context)
        {
            var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
            if (UserId != null)
            {
                properties.Dictionary[XsrfKey] = UserId;
            }

            context.HttpContext.GetOwinContext().Authentication.Challenge(properties, SocialAccountProvider);
        }

How exactly is it being used for protection?

Should I set the value of XsrfKey to something more random?

like image 518
Yovav Avatar asked Aug 20 '15 14:08

Yovav


2 Answers

Take a look at ManageController methods LinkLogin and LinkLoginCallback:

    //
    // POST: /Manage/LinkLogin
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult LinkLogin(string provider)
    {
        // Request a redirect to the external login provider to link a login for the current user
        return new AccountController.ChallengeResult(provider, Url.Action("LinkLoginCallback", "Manage"), User.Identity.GetUserId());
    }

    //
    // GET: /Manage/LinkLoginCallback
    public async Task<ActionResult> LinkLoginCallback()
    {
        var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
        if (loginInfo == null)
        {
            return RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error });
        }
        var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login);
        return result.Succeeded ? RedirectToAction("ManageLogins") : RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error });
    }

These are the methods that handle linking of external accounts (i.e. Google, Facebook, etc.). The flow goes like this:

  1. User clicks "Link Account" button, which calls a POST to LinkLogin method.
  2. LinkLogin returns ChallengeResult object, with callback url set to LinkLoginCallback method.
  3. ChallengeResult.ExecuteResult is called by MVC framework, calls IAuthenticationManager.Challenge, which causes a redirect to the specific external login provider (let's say: google).
  4. User authenticates with google, then google redirects to callback url.
  5. The callback is handled with LinkLoginCallback. Here, we want to prevent XSRF and verify that the call was initiated by a user, from a page served by our server (and not by some malicious site).

Normally, if it was a simple GET-POST sequence, you would add a hidden <input> field with an anti-forgery token and compare it with a corresponding cookie value (that's how Asp.Net Anti-Forgery Tokens work).

Here, the request comes from external auth provider (google in our example). So we need to give the anti-forgery token to google and google should include it in the callback request. That's exactly what state parameter in OAuth2 was designed for.

Back to our XsrfKey: everything you put in AuthenticationProperties.Dictionary will be serialized and included in the state parameter of OAuth2 request - and consequentially, OAuth2 callback. Now, GetExternalLoginInfoAsync(this IAuthenticationManager manager, string xsrfKey, string expectedValue) will look for the XsrfKey in the received state Dictionary and compare it to the expectedValue. It will return an ExternalLoginInfo only if the values are equal.

So, answering your original question: you can set XsrfKey to anything you want, as long as the same key is used when setting and reading it. It doesn't make much sense to set it to anything random - the state parameter is encrypted, so no one expect you will be able to read it anyway.

like image 63
qbik Avatar answered Nov 04 '22 07:11

qbik


Just leave it as is:

As the name of the member states it is a key:

private const string XsrfKey = "XsrfId";

It is defined in this manner to avoid "magic numbers" and then is used a little down in the scaffold code:

public override void ExecuteResult(ControllerContext context)
{
    var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
    if (UserId != null)
    {
        properties.Dictionary[XsrfKey] = UserId;
    }
    context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
}

The value of the dictionary item is then set to the UserId property in the above code by using the XsrfKey member as the key.

IOW the code is already setting the XSRF dictionary item to the value of the user ID in the snippet. If you change the XsrfKey members value to anything else you will cause problems down the line, since the expected key "XsrfId" will have no value set.

If by changing it to something more random you are implying to change the value and not they key of the dictionary, or in other words, not set it to the user id then please see the following for an explanation of the anti forgery token inner workings.

http://www.asp.net/mvc/overview/security/xsrfcsrf-prevention-in-aspnet-mvc-and-web-pages

like image 28
Anthony Spruyt Avatar answered Nov 04 '22 05:11

Anthony Spruyt