7

I'm developing a ASP WebAPI (ASP MVC 4) application with a WPF (.NET 4.0) client, using Visual Studio 2012. The client needs to login to the server. I use FormsAuthentication with an authentication cookie to login. The login already works fine in ASP MVC.

The problem is that, although the login is sucessfully executed on the server and the cookie is sent back to the client, the cookie is not sent in subsequent calls to the server, even though the CookieContainer is reused with the auth cookie set.

Here is a simplified version of the code:

CLIENT

public async Task<UserProfile> Login(string userName, string password, bool rememberMe)
{           
    using (var handler = new HttpClientHandler() { CookieContainer = this.cookieContainer })
    using (var httpClient = new HttpClient(handler))
    {
        httpClient.BaseAddress = new Uri("http://localhost:50000/");

        httpClient.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));

        var result = await httpClient.PostAsJsonAsync("api/auth/login", new
        {
            username = userName,
            password = password,
            rememberMe = rememberMe
        });

        result.EnsureSuccessStatusCode();

        var userProfile = await result.Content.ReadAsAsync<UserProfile>();

        if (userProfile == null)
            throw new UnauthorizedAccessException();

        return userProfile;
    }
}

public async Task<ExamSubmissionResponse> PostItem(Item item)
{
    using (var handler = new HttpClientHandler() { CookieContainer = this.cookieContainer })
    using (var httpClient = new HttpClient(handler))
    {
        httpClient.BaseAddress = new Uri("http://localhost:50000/");

        var result = await httpClient.PostAsJsonAsync("api/Items/", item);
    }
}

SERVER

[HttpPost]
public HttpResponseMessage Login(LoginModel model)
{
    if (this.ValidateUser(model.UserName, model.Password))
    {
        // Get user data from database

        string userData = JsonConvert.SerializeObject(userModel);

        var authTicket = new FormsAuthenticationTicket(
            1,
            model.UserName,
            DateTime.Now,
            DateTime.Now.AddMinutes(10 * 15),
            model.RememberMe,
            userData
        );              

        string ticket = FormsAuthentication.Encrypt(authTicket);
        var cookie = new CookieHeaderValue(FormsAuthentication.FormsCookieName, ticket);
        var response = Request.CreateResponse(HttpStatusCode.Created, userModel);
        response.Headers.AddCookies(new CookieHeaderValue[] { cookie });

        return response;
    }
    return null;
}

First I debugged the problem using Fiddler2 (I used the base address as "http://localhost.fiddler:50000/" to view local traffic). Then I suspected that fiddler might be interfering, so I just debugged with Visual Studio 2012.

What I have tried and verified:

  • The server is reached by the Login method

  • The user is sucessfully authenticated with the data sent from the client

  • The cookie is set on the server

  • The cookie is in the response (verified with fiddler)

  • The cookie is in the CookieContainer after the operation. There is a strange thing here: the domain of the cookie in the container is set as "localhost" (verified with VS2012 debugger). Shouldn't it be "http://localhost:50000" ? When I try to get the cookies of the container using cookieContainer.GetCookies(new Uri("http://localhost:50000")) it returns nothing. When I try it using cookieContainer.GetCookies(new Uri("localhost")) it gives me an invalid Uri error. Not sure what's going on here.

  • The cookie is in the container just before the PostItem request is made. The container is correctly set in the HttpClient when the statement httpClient.PostAsJsonAsync is reached.

  • The cookie is not sent to the server (I checked it with fiddler and in the Application_PostAuthenticateRequest method in the Global.asax.cs, verifying this.Request.Cookies)

I suspect the cookie is not being sent due to a domain mismatch in the CookieContainer, but why the domain is not set as it should in the CookieContainer in the first place?

4

1 回答 1

7

您的问题是您没有在从 Web Api 控制器发回的 cookie 上设置任何路径。

有两件事可以控制 cookie 的发送位置:

  1. cookie 的域
  2. 饼干的路径

关于域,共识似乎是端口号不再(但仍然可能)是评估 cookie 域的一个因素。有关端口号如何影响域的更多信息,请参阅此问题。

关于路径:Cookie 与其域中的特定路径相关联。在您的情况下,Web Api 正在发送一个 cookie 而没有指定它的路径。默认情况下,cookie 将与创建 cookie 的请求/响应的路径相关联。

在您的情况下,cookie 将具有路径api/auth/login。这意味着 cookie 将被发送到该路径的子路径(因为没有更好的术语),但不会发送到路径或兄弟路径。

要对此进行测试,请尝试:

cookieContainer.GetCookies(new Uri("http://localhost/api/auth/login")

这应该给你cookie。所以应该这样:

cookieContainer.GetCookies(new Uri("http://localhost/api/auth/login/foo/bar")

另一方面,这些将找不到 cookie:

cookieContainer.GetCookies(new Uri("http://localhost/")
cookieContainer.GetCookies(new Uri("http://localhost/api/")
cookieContainer.GetCookies(new Uri("http://localhost/api/auth/")
cookieContainer.GetCookies(new Uri("http://localhost/api/auth/foo")
cookieContainer.GetCookies(new Uri("http://localhost/api/Items/")

要解决此问题,只需在发送响应之前将路径“/”(或者可能是“/api”)添加到 cookie:

...
string ticket = FormsAuthentication.Encrypt(authTicket);
var cookie = new CookieHeaderValue(FormsAuthentication.FormsCookieName, ticket);
cookie.Path = "/";
var response = Request.CreateResponse(HttpStatusCode.Created, userModel);
response.Headers.AddCookies(new CookieHeaderValue[] { cookie });
...
于 2013-07-05T06:43:33.567 回答