Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing User data in WebAPI from Context/Cookie

Update:

I have changed my code to FormsAuthentication.SetAuthCookie(_model.UserName, true);. I do have 2 Web.config files, 1 for MVC and the other for WebAPI. In MVC config, I define the

<authentication mode="Forms">
  <forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>

Both applications are on the same domain.


Update: Should we even be using cookies in WebAPI?


Currently, I have a MVC project which uses forms authentication and a WebAPI project. The problem is that I can't get the user associated with the request in the WebAPI project. I thought this would be possible or perhaps the implementation is wrong?

NB: I put the cookie code in the WebAPI controller method as a test, it's not where it should be.

MVC - handles login request, create auth ticket.

// POST: /Account/Login
[AllowAnonymous]
[HttpPost]
public ActionResult Login(LoginModel _model, string _returnUrl)
{
    if (ModelState.IsValid)
    {
        if (Membership.ValidateUser(_model.UserName, _model.Password))
        {
            FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(_model.UserName, true, 15);
            string encryptedTicket = FormsAuthentication.Encrypt(ticket);
            HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
            Response.Cookies.Add(cookie);

            // set redirect
        }
    }

    // If we got this far, something failed, redisplay form
    return View(_model);
}

WebAPI - handles update request

[AcceptVerbs("PUT")]
public HttpResponseMessage UpdateInfo(int _id, ExampleModel _model)
{
    try
    {
        HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];

        if (authCookie != null)
        {
            string encTicket = authCookie.Value;

            if (!String.IsNullOrEmpty(encTicket))
            {
                // decrypt the ticket if possible.
                FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(encTicket);

                var userData = ticket.UserData;
            }
        }

        // do update
        return Request.CreateResponse(HttpStatusCode.OK, data, GlobalConfiguration.Configuration);
    }
    catch (Exception err)
    {
        m_logger.Error(err);
        throw;
    }
}
like image 730
user1883004 Avatar asked Apr 23 '13 09:04

user1883004


1 Answers

Why mess with creating the forms authentication ticket manually? Why can't you just do this?

if (Membership.ValidateUser(_model.UserName, _model.Password))
{
    FormsAuthentication.SetAuthCookie(_model.UserName, true);

    // set redirect
}

To control the timeout, you can set that in your web.config:

<authentication mode="Forms">
    <forms timeout="15" />
</authentication>

If you do this, you should not have to check the cookie to get the user's name. It should automatically be set on your controller's User.Identity.Name property. This works for both MVC Controllers and ApiControllers.

Also, if you have your MVC application and your WebAPI application hosted on different nodes in your web server (or on different servers), both the MVC project and the WebAPI project must have the same machineKey in their web.config files. They also must be on the same domain, but if they are separate applications with separate web.config files, they must have the same machineKey values (decryption, decryptionKey, validation, and validationKey). Those are the values needed to validate, encrypt, and decrypt the .ASPXAUTH cookie.

Should we even be using cookies in WebAPI?

If you use the pattern above, you shouldn't have to manually get any cookies, at least for authentication / authorization. Cookies are part of the HTTP specification, along with methods, headers, response codes, etc. WebAPI has them in there for a reason. If you need them, use them. If you don't need them, don't use them.

What you should try to avoid is getting them by doing this:

HttpCookie authCookie = HttpContext.Current.Request.Cookies["name"];

You can get to the cookies from an ApiController like so:

IEnumerable<CookieHeaderValue> cookies = this.Request.Headers.GetCookies("name");
if (cookies.Any())
{
    IEnumerable<CookieState> cookie = cookies.First().Cookie;
    if (cookie.Any())
    {
        string value = cookie.First().Value;
    }
}

Update

I always use this generation tool: http://aspnetresources.com/tools/machineKey

Just click the "Generate key" button, and then paste the resulting <machineKey ../> section somewhere under the <system.web> section of both web.config files.

Update

There can be security issues depending on who has access to view your web.config file. If you check it into your source control repository like this, any person who can access your source code can see the validation and encryption keys.

What I do is a bit convoluted, but it keeps these strings secured. You can instead store the validationKey and encryptionKey values in appSettings, and then encrypt the appSettings sections of your config files (using something like CloudConfigCrypto). This means those values cannot be read out by anyone who does not have access to your encryption certificate's private key. You can then use the Microsoft.Web.Administration.ServerManager class to change the machineKey values at runtime, during Application_Start. More details about this are out of scope for this question though.

Another option would be to just keep these out of source control, but then you have to manually update the web.config each time you deploy.

like image 72
danludwig Avatar answered Nov 09 '22 01:11

danludwig