Set-Cookie on Browser with Ajax Request via CORS
Asked Answered
I

3

48

Attempting to implement an ajax login / signup process (no refresh site with authentication). Using cookies for preserving state. I thought I'd have this right by now but for some reason browser doesn't set cookies after it gets them back from the server. Can anyone help? Here are the request and response headers:

Request URL:http://api.site.dev/v1/login
Request Method:POST
Status Code:200 OK

Request Headers

Accept:application/json, text/plain, */*
Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:57
Content-Type:application/json;charset=UTF-8
Host:api.site.dev
Origin:http://site.dev
Referer:http://site.dev/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
withCredentials:true
X-Requested-With:XMLHttpRequest
Request Payload
{"email":"[email protected]","password":"foobar"}

Response Headers

Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:X-Requested-With, Content-Type, withCredentials
Access-Control-Allow-Methods:GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin:http://site.dev
Connection:Keep-Alive
Content-Length:19
Content-Type:application/json
Date:Tue, 08 Jan 2013 18:23:14 GMT
Keep-Alive:timeout=5, max=99
Server:Apache/2.2.22 (Unix) DAV/2 PHP/5.4.7 mod_ssl/2.2.22 OpenSSL/0.9.8r
Set-Cookie:site=%2B1THQQ%2BbZkEwTYFvXFVV5fxi00l2K%2B6fvt9SuHACTNsEwUGzDSUckt38ZeDsNbZSsqzHmPMWRLc84eDLZzh8%2Fw%3D%3D; expires=Thu, 10-Jan-2013 18:23:14 GMT; path=/; domain=.site.dev; httponly
X-Powered-By:PHP/5.4.7

I also see the cookie in chrome network tools, as returned from the server:

Response Cookies

Name: site
Value: %2B1THQQ%2BbZkEwTYFvXFVV5fxi00l2K%2B6fvt9SuHACTNsEwUGzDSUckt38ZeDsNbZSsqzHmPMWRLc84eDLZzh8%2Fw%3D%3D
Domain: .site.dev
Path: /
Expires: Session
Size: 196
Http: ✓
Iliad answered 8/1, 2013 at 18:32 Comment(2)
Is this a domain-matching issue as per Section 4.3.2 of RFC2109? I'm not entirely clear on whether your host (api.site.dev) and cookie-domain (.site.dev) are "domain-matching" as required. The CORS side of this doesn't make this any easier to deduce, either :)Amick
Possible duplicate of CORS request - why are the cookies not sent?Georgena
I
36

Your AJAX request must be made with the "withCredentials" settings set to true (only available in XmlHttpRequest2 and fetch):

    var req = new XMLHttpRequest();
    req.open('GET', 'https://api.bobank.com/accounts', true); // force XMLHttpRequest2
    req.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
    req.setRequestHeader('Accept', 'application/json');
    req.withCredentials = true; // pass along cookies
    req.onload = function()  {
        // store token and redirect
        let json;
        try {
            json = JSON.parse(req.responseText);
        } catch (error) {
            return reject(error);
        }
        resolve(json);
    };
    req.onerror = reject;

If you want a detailed explanation on CORS, API security, and cookies, the answer doesn't fit in a StackOverflow comment. Check out this article I wrote on the subject: http://www.redotheweb.com/2015/11/09/api-security.html

Imaginary answered 12/11, 2015 at 9:5 Comment(3)
Problem:- A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true. Origin 'null' is therefore not allowed access.Boeotian
Francois, I read your article, I am consuming one api which is a web service. It requires system authentication (Not a db driven). I use with credentials and able to login. Now is it possible to store this credentials in cookie and bypass authentication. Thanks for article.Capable
You must also set the response header Access-Control-Allow-Credentials to 'true', otherwise the browser will block (deny) the request.Kristopherkristos
E
13

I had a similiar problem, and it turned out that the browser settings were blocking third-party cookies (Chrome > Settings > Advanced Settings > Privacy > Content Settings > Block third-party cookies and site data). Unblocking solved the problem!

Eugenides answered 19/5, 2013 at 13:6 Comment(5)
Indeed. And since that is the default setting in Safari, and possible in most other browsers, making a site, that relies on third-party-cookies via CORS should be done with some consideration. Few users may like to be told to enable third party cookies for a site to work.Parous
I just spent three hours troubleshooting and looking for support before I realized this was my problem. Thanks!Hobble
I am having the same problem, but "Block third-party cookies and site data" is already disabled.Ninefold
If you've chosen a solution that requires your users to alter a setting in Chrome then sadly it won't work for you in productionZechariah
It also is default in 'Brave', good discussion.Swacked
Q
2

I needed to pass cookies from multiple subdomains to a single API domain using AJAX and PHP and handeling CORS correctly.

This was the challenge and solution:

1 - Backend PHP on api.example.com.

2 - Multiple JS front ends such as one.example.com, two.example.com etc.

3 - Cookies needed to be passed both ways.

4 - AJAX call from multiple front-ends to PHP backend on api.example.com

5 - In PHP, I do not prefer to use $_SERVER["HTTP_ORIGIN"], not always reliable/safe in my opinion (I had some browsers where HTTP-ORIGIN was always empty).

The normal way to do this in PHP with single front end domain is starting PHP code with:

header('Access-Control-Allow-Origin: https://one.example.com');  
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token');  
header('Access-Control-Allow-Credentials: true');  

And in JS on one.example.com domain:

jQuery.ajax({
    url: myURL,
    type: "POST",
    xhrFields: {withCredentials: true},
    dataType: "text",
    contentType: "text/xml; charset=\"utf-8\"",
    cache: false,
    headers: "",
    data: myCallJSONStr,
    success: function(myResponse) {.....}

However, this is not workable as I am using multiple subdomains to call my API domain.

And this solution will NOT work as I want to pass on cookies:

header('Access-Control-Allow-Origin: *');  

It conflicts with the pass on cookie setting on the JS site:

xhrFields: {withCredentials: true}

Here is what I did:

1 - use GET parameter to pass the Subdomain.

2 - Hardcode the Main domain in PHP so only (all) Subdomains are allowed.

This is the JS/JQuery AJAX part of my solution:

function getSubDomain(){
    
    let mySubDomain = "";
    
    let myDomain = window.location.host;
    let myArrayParts = myDomain.split(".");
    if (myArrayParts.length == 3){
        mySubDomain = myArrayParts[0];
    }
    
    return mySubDomain;
    
}

And in the AJAX call:

let mySubDomain = getSubDomain();
if (mySubDomain != ""){
    myURL += "?source=" + mySubDomain + "&end"; //use & instead of ? if URL already has GET parameters
}

jQuery.ajax({
    url: myURL,
    type: "POST",
    xhrFields: {withCredentials: true},
    dataType: "text",
    contentType: "text/xml; charset=\"utf-8\"",
    cache: false,
    headers: "",
    data: myCallJSONStr,
    success: function(myResponse) {.....}

Finally, the PHP part:

<?php

$myDomain = "example.com";
$mySubdomain = "";

if (isset($_GET["source"])) {
    $mySubdomain = $_GET["source"].".";
}

$myDomainAllowOrigin = "https://".$mySubdomain.$myDomain;
$myAllowOrigin = "Access-Control-Allow-Origin: ".$myDomainAllowOrigin;

//echo $myAllowOrigin;

header($myAllowOrigin);  
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token');  
header('Access-Control-Allow-Credentials: true');

IMPORTANT, don't forget to set the cookies for all subdomains, in this case the domain for the cookie would be: .example.com (so with a dot in front of the main domain):

<?php

    //////////////// GLOBALS /////////////////////////////////
    
    $gCookieDomain = ".example.com";
    $gCookieValidForDays = 90;
    
    //////////////// COOKIE FUNTIONS /////////////////////////////////
    
    function setAPCookie($myCookieName, $myCookieValue, $myHttponly){
        global $gCookieDomain;
        global $gCookieValidForDays;
        
        $myExpires = time()+60*60*24*$gCookieValidForDays;
        setcookie($myCookieName, $myCookieValue, $myExpires, "/", $gCookieDomain, true, $myHttponly);   
        
        return $myExpires;
    }

This solution allows me to call the API on api.example.com from any subdomains on example.com.

NB. for situation where there is only a single calling subdomain, I prefer using .htaccess for setting CORS instead of PHP. Here is an example of .htaccess (linux/apache) for only one.example.com calling api.example.com:

<IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "https://one.example.com"
    Header set Access-Control-Allow-Headers "Origin, Content-Type, X-Auth-Token"
    Header set Access-Control-Allow-Credentials "true"
</IfModule>

And place this .htaccess in the root of api.example.com.

Queen answered 12/2, 2021 at 0:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.