Set-Cookie in Response Header not being created in browser - Using http POST

11,031

I wasted a full day on the same problem. What I've found is that these are the important bits:

1. You need to add the following headers on the server (replace with your client host address). These are needed for CORS, which you need because your two sites are using different port numbers.

Access-Control-Allow-Origin: http://localhost:5555
Access-Control-Allow-Credentials: true

For WebAPI, this means placing the following right at the top of WebApiConfig.Register:

var cors = new EnableCorsAttribute("http://localhost:5555", "*", "GET,POST");
cors.SupportsCredentials = true;
config.EnableCors(cors);

2. Be precise about how you set your cookies

Look at these:

response.Headers.AddCookies(new CookieHeaderValue[] 
{ 
  new CookieHeaderValue("WorkingCookie", cookieValue) { Path="/" }, 
  new CookieHeaderValue("NotWorkingCookie", cookieValue) 
});

I couldn't get a CORS cookie to work without specifying Path. Also, this SO answer claims that in production environments, you'll need to specify Domain as well. I'm not there yet, so I'm just noting it for now.

3. Be aware of back-end limitations

Not really an issue with Web API that I know of, but for PHP multiple Set-Cookie headers don't work well. I could only get the last one listed to be persisted on the client.

4. Use withCredentials on your HTTP request*

This is my take on your call listed earlier:

return this._http.post(this._globalVariables.BACKEND_API_HANDLE_LOGIN,
    loginModel, {headers: headers, withCredentials: true})
    .map((response : Response) => response);

Note withCredentials being passed through as part of the options.

This was the missing part for you and is absolutely key. The other steps are there to ensure the cookies get sent through correctly by the server and are persisted by the browser, but withCredentials governs whether the browser will include its cookies in the request header.

You'll want to ensure you're using a newer version of Angular though; even some of the RC versions just flat-out ignored the withCredentials option and could therefore not be used for CORS calls like these at all.

Final note If you are creating a CORS authentication system with cookies, there are two additional things to take care of:

  1. Mark your session cookie HttpOnly in Web API, so it may not be tampered with by injected JavaScript.
  2. Generate and store server-side a key that you return in a (non-HttpOnly) cookie named XSRF-TOKEN. On each API call, compare this stored value with the value of the X-XSRF-TOKEN header that Angular will return. This is needed for protection against CSRF attacks.
Share:
11,031
Simon Azzopardi
Author by

Simon Azzopardi

Updated on June 04, 2022

Comments

  • Simon Azzopardi
    Simon Azzopardi almost 2 years

    I have been stuck on such issue related to cookies.

    My scenario is this:

    • I have my backend on server (localhst:12456)
    • I have my Angular 2 app running on another server (localhost:5555)

    My backend is just an ASP.NET application and I am trying to authenticate user from an apiController. the api Controller is like this:

    [System.Web.Mvc.HttpPost]
            public HttpResponseMessage HandleLogin([FromBody] LoginModel loginModel)
            {
                if (!String.IsNullOrEmpty(loginModel.Username) && !String.IsNullOrEmpty(loginModel.Password))
                {
    
                    if (Members.Login(loginModel.Username, loginModel.Password))
                    {
                        var resp = Request.CreateResponse<UserAuthenticationModel>(
                             HttpStatusCode.OK,
                            new UserAuthenticationModel() { IsAuthenticated = true}
                        );
    
                        //create and set cookie in response
                        var cookie = new CookieHeaderValue("customCookie", "cookieVal");
                        cookie.Expires = DateTimeOffset.Now.AddDays(1);
                        cookie.Domain = Request.RequestUri.Host;
                        cookie.Path = "/";
                        resp.Headers.AddCookies(new CookieHeaderValue[] { cookie });
    
                        return resp;
                    }
                }
    
                return Request.CreateResponse<UserAuthenticationModel>(
                      new UserAuthenticationModel() { IsAuthenticated = false }
                );
            }
    

    Now, from my angular app, I am calling an http post:

    headers.append('Access-Control-Allow-Credentials', true);

        return this._http.post(this._globalVariables.BACKEND_API_HANDLE_LOGIN, loginModel, {headers: headers})
                    .map((response : Response) => response);
    

    this._loginService.getLoginModel() .subscribe( loginModel => this._signInPageModel = loginModel, error => console.log(error) ); Now, in my api controller method (HandleLogin) I have a test cookie and another one for FormsAuthentication and when user logged in successfully, and return back to my angular app, no cookies are created.

    Now, the cookies can be seen as well as post of my response headers:

    enter image description here

    I'm quite confusing about it and appreciate any help in order to get the login process successfully.

    Thank you in advance.