7

I'm trying to set up a video streaming using Cloudfront HLS capabilities but I'm having trouble getting Hls.js to send my credential cookies in the request.

I already have Cloudfront configured to forward cookies and to forward Access-control headers. I also have set my S3 CORS policies to include GET, HEAD.

The problem I'm having is that even though I'm setting the xhr.withCredentials=true and the cookies are defined in the session, when I look at the request using chrome console, I can see that the HLS request has no cookies. As a result I get an error response from cloudfront saying I need to include the credential cookies.

Code: First I do an ajax request to my server to generate the cookies. The server returns three Set-Cookies headers stored as session cookies in the browser:

$.ajax(
{
type: 'GET',
url: 'http://subdomain.mydomain.com:8080/service-
webapp/rest/resourceurl/cookies/98400738-a415-4e32-898c-9592d48d1ad7',
success: function (data) {
        playMyVideo();
},
headers: { "Authorization": 'Bearer XXXXXX' }
});

Once the cookies are stored the test function is called to play my video using HLS.js:

function test(){
  if (Hls.isSupported()) {
  var video = document.getElementById('video');
  var config = {
    debug: true,
    xhrSetup: function (xhr,url) {
        xhr.withCredentials = true; // do send cookie
    xhr.setRequestHeader("Access-Control-Allow-Headers","Content-Type, Accept, X-Requested-With");
        xhr.setRequestHeader("Access-Control-Allow-Origin","http://sybdomain.domain.com:8080");
    xhr.setRequestHeader("Access-Control-Allow-Credentials","true");
    }
  };
  var hls = new Hls(config);
  // bind them together
  hls.attachMedia(video);
  hls.on(Hls.Events.MEDIA_ATTACHED, function () {
    console.log("video and hls.js are now bound together !");
    hls.loadSource("http://cloudfrontDomain.net/small.m3u8");
    hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
      console.log("manifest loaded, found " + data.levels.length + " quality level");
    });
  });
}
video.play();
}

As you can see below HLS OPTIONS and GET request do not set the session cookies:

HLS OPTIONS request:

OPTIONS /hls/98400738-a415-4e32-898c-9592d48d1ad7/small.m3u8 HTTP/1.1
Host: cloudfrontDomain.net
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: subdomain.mydomain.com:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36
Access-Control-Request-Headers: access-control-allow-credentials,access-control-allow-headers,access-control-allow-origin
Accept: */*
Referer: http://subdomain.mydomain.com:8080/play.html
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8,es;q=0.6

CloudFront response:

HTTP/1.1 200 OK
Content-Length: 0
Connection: keep-alive
Date: Fri, 07 Jul 2017 00:16:31 GMT
Access-Control-Allow-Origin: http://subdomain.mydomain.com:8080
Access-Control-Allow-Methods: GET, HEAD
Access-Control-Allow-Headers: access-control-allow-credentials, access-control-allow-headers, access-control-allow-origin
Access-Control-Max-Age: 3000
Access-Control-Allow-Credentials: true
Server: AmazonS3
Vary: Origin,Access-Control-Request-Headers,Access-Control-Request-Method
Age: 845
X-Cache: Hit from cloudfront
Via: 1.1 cloudfrontDomain.net (CloudFront)
X-Amz-Cf-Id: XXXXXX

HLS subsequent GET request missing the cookies:

GET /hls/98400738-a415-4e32-898c-9592d48d1ad7/small.m3u8 HTTP/1.1
Host: cloudfrontDomain.net
Connection: keep-alive
Origin: http://subdomain.mydomain.com:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36
Access-Control-Allow-Origin: http://subdomain.mydomain.com:8080
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Accept, X-Requested-With
Accept: */*
Referer: http://subdomain.mydomain.com:8080/play.html
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8,es;q=0.6

I've spent 4 days trying to figure this out. I've done plenty of research but I just can't figure out the solution. I'm new to CORS so maybe I'm not understanding some principle. I thought if the cookies are stored in the session they would get set if you enabled xhr with credentials but it doesn't seem to be the case.

Another thing I noted is that the GET request generated by HLS.js is not setting any xmlhttprequest header.

Thanks for your help :)

Ismakun
  • 151
  • 1
  • 1
  • 6
  • 1
    An XHR request from a page on example.com that sends a cross-origin request with credentials to example.net will send the cookies previously *returned by* example.net, not the cookies returned by example.com. – Michael - sqlbot Jul 07 '17 at 01:27
  • So I'm facing a dilemma here. My webservice inerfaces with amazon aws to generate the cookies. The problem is the webservice is running in a different domain. That means I can't pass any cookies to my CORS request unless cloudfront domain returns the set-cookie header? I don't think cloud front support generating cookies as part of the response based on a canned policy... Any ideas or suggestions? – Ismakun Jul 07 '17 at 01:37
  • 1
    Create a second Origin in CloudFront, with the web service as the origin hostname, and create a cache behavior with the path to the service routing to that new origin. The request to get the cookies goes to CloudFront, which sends it to the origin to get the cookies, and the response with cookies comes back through CloudFront and they're set on the correct domain. That works in principle. Would it work with your service? – Michael - sqlbot Jul 07 '17 at 01:50
  • 1
    Plan B. Create `sub.subdomain.example.com`, pointing to CloudFront. Set the cookies from `subdomain.example.com` using `domain=.subdomain.example.com`, noting the leading dot. These cookies should work when you send a request to `sub.subdomain.example.com`. – Michael - sqlbot Jul 07 '17 at 01:52
  • Thanks! Plan B works as in now sends the cookies. But now CloudFront simply denies any requests from my new sub.subdomain.example.com AccessDenied Access denied I checked my s3 bucket and its set to allow http://* https://* – Ismakun Jul 07 '17 at 03:57
  • AccessDenied wouldn't be CORS-related, and I don't think that message is from CloudFront. It looks like it's being passed back from S3. Have you verified that CloudFront works and S3 allows the requests, if you configure CloudFront *not* to require the signed cookies? – Michael - sqlbot Jul 07 '17 at 04:26
  • So its finally working... Plan B did not work because S3 was rejecting my subdomain request and I couldn't figure out the correct policy to make it work. I ended up going back to your plan A and figuring out how to set cloudfront to serve object based on my different origins. At first it was not working but later I realized it didn't work at the time because cloudfront was propagating the changes to the edges. Thanks for your help. After a week I was finally able to do it. – Ismakun Jul 08 '17 at 07:39
  • If you prefer to go with plan B do make sure you update the code generating your cookies to make use of the associated domain name pointing to your Cloudfront domain. It was what was throwing Access Denied error for me. @Ismakun – Fanan Dala May 16 '22 at 17:40

2 Answers2

8

I was finally able to make it work. Thanks Michael for helping! Turns out it was a mix of not understanding how CORS principles work and properly configuring aws services. The main issue is to avoid cross domain requests by using cloudfront to serve both your webservice and s3 bucket. One important note I want to add is that any change you make in aws you have to wait for it to propagate. As a new aws dev I didn't know that and got very frustrated making changes that had no effect. Here is the solution:

1) Create your S3 bucket.

2) Create a Cloudfront distribution.

3) In the distribution set as the default origin your web-service domain.

4) Add a second origin and add a behavior in the distribution to forward all .m3u8 and .ts files to your S3 bucket.

5) When you add your bucket origin make sure you mark restrict access and also update bucket policy checkboxes.

6) In your bucket distribution behavior make sure you forward all white list headers and cookies. This can all be set in aws console.

7) If you are using different ports in your service make sure you set those too in the distribution.

8) Go to your S3 bucket settings and update the CORS config to the following:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration> 

It is important that if your are using HLS.js to set the following config:

var config = {
debug: true,
xhrSetup: function (xhr,url) {
xhr.withCredentials = true; // do send cookie
xhr.setRequestHeader("Access-Control-Allow-Headers","Content-Type, Accept, X-Requested-With");
    xhr.setRequestHeader("Access-Control-Allow-Origin","http://sybdomain.domain.com:8080");
xhr.setRequestHeader("Access-Control-Allow-Credentials","true");
}
};
var hls = new Hls(config);

Other important notes:

When you serve a cookie with your web service you can set the Path to be "/" and it will apply to all request in your domain.

Ismakun
  • 151
  • 1
  • 1
  • 6
  • hey buddy, I am facing the same issues and I am stuck on this issue for the last few days. Can you please explain steps 4, 5, and 6 as I am totally beginner in AWS? Thanx in advance. – Faisal Shaikh Dec 16 '20 at 17:45
1

For anyone that might be having this issue only on Chrome for Android, our problem was that the browser was caching the m3u8 files and giving the same CORS error. The solution was to append a timestamp parameter to the querystring of the file url:

var config = {
  xhrSetup: function (xhr, url) {
    xhr.withCredentials = true; // do send cookies
    url = url + '?t=' + new Date().getTime();
    xhr.open('GET', url, true);
  }
};
var hls = new Hls(config);
Ale Felix
  • 379
  • 5
  • 7