6

I'm using CloudFront Signed URL to display images and videos from S3 to be secured.

It works well on images and other videos except for .m3u8 file.

I used AWS PHP SDK and here's my code.

<?php
    // Instantiate the CloudFront client.
    $cloudFrontClient = new CloudFrontClient(array(
        'region'        => env('AWS_DEFAULT_REGION'),
        'version'       => 'latest',
        'http'          => [ 'verify' => false ],
        'credentials'   => array(
            'key'           => env('AWS_ACCESS_KEY_ID'),
            'secret'        => env('AWS_SECRET_ACCESS_KEY'),
        )
    ));
    
    // Create a signed URL for the resource.
    $resourceKey = 'https://abcdefg.cloudfront.net/test/file_1000k.m3u8';

    $expires =  time() + 3600;
    $signedUrl = $cloudFrontClient->getSignedUrl([
        'url'         => $resourceKey,
        'expires'     => $expires,
        'private_key' => public_path() . '/pk-ABCD123.pem',
        'key_pair_id' => 'ABCD123ABCD123ABCD123'
    ]);
?>

<video id="hls-example" class="video-js vjs-default-skin" width="640" height="480" controls>
    <source src="<?php echo $signedUrl; ?>" type="application/x-mpegURL">
    Your browser does not support the video tag.
</video>
<script src="https://vjs.zencdn.net/7.2.3/video.js"></script>
<script src="<?php echo asset('public/assets/js/videojs-contrib-hls.min.js'); ?>"></script>
<script>
    var player = videojs('hls-example');
    player.play();
</script>

If I'm not mistaken, it doesn't play because we also need to sign the segmented files (.ts) inside the .m3u8 file.

How do I dynamically change it?

Is there any way we can play .m3u8 file securely so that users can't use the direct link access to download the file?

GROVER.
  • 4,071
  • 2
  • 19
  • 66
Codeblooded Saiyan
  • 1,457
  • 4
  • 28
  • 54

3 Answers3

1

This is a good AWS article to help you decide between (1) cookies and (2) signed URLs: -

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-choosing-signed-urls-cookies.html

I initially looked at the cookie approach but opted for individually signing each URL inside the HLS manifest file. I use ffmpeg to convert a MP4 file to HLS (e.g. Mp4 to HLS using ffmpeg) and the HLS manifest will be something like: -

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:17
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:16.683333,
my-video0.ts
#EXTINF:8.341667,
my-video1.ts
#EXTINF:8.341667,
my-video2.ts

...

#EXT-X-ENDLIST

You would individually sign each segment e.g.

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:17
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:16.683333,
https://cqtgd3b9n5c6qp.cloudfront.net/my-video0.ts?Expires=1609499278&Key-Pair-Id=AIRPEGWQPKAIQ7O3SPLI&Signature=KUvRsV-OpJ014ZQ0dLZF....
#EXTINF:8.341667,
https://cqtgd3b9n5c6qp.cloudfront.net/my-video1.ts?Expires=1609499278&Key-Pair-Id=AIRPEGWQPKAIQ7O3SPLI&Signature=KlVQsz5TVzhEQ8LKs1ZW....
#EXTINF:8.341667,
https://cqtgd3b9n5c6qp.cloudfront.net/my-video2.ts?Expires=1609499278&Key-Pair-Id=AIRPEGWQPKAIQ7O3SPLI&Signature=VR1MBzq~pVsBfOzjZa~M....

...

#EXT-X-ENDLIST

Simple search / replace will dynamically generate this file.

Signing each URL will takes a bit of processing so I add an Expires header (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expires) to the HTTP response when returning the HLS manifest. This ensures subsequent calls in the browser use the cached copy which massively increases performance.

NOTE - it's important the expires in the HTTP response is (slightly) less than the Expires in the signed URL.

I find individually signing each URL is more secure and you don't need to worry about domains etc but probably slightly more complex (code wise) to implement.

bobmarksie
  • 3,282
  • 1
  • 41
  • 54
0

CloudFront signed URLs work great when it is just one file, but like you have discovered it is a problem when you have multiple resources.

For this reason the recommended approach is to use signed CloudFront cookies.

By doing this you only need to sign once to allow all resources from a specific CloudFront distribution and do not need to bother with the signing process on each page load.

Chris Williams
  • 32,215
  • 4
  • 30
  • 68
  • 1
    Ahh, Do you have an idea on how to implement that? – Codeblooded Saiyan Jun 05 '20 at 01:26
  • Its a similar process to the signed URLs, you use the CLI or SDK. This page has more information on implementation details:https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-cookies.html#private-content-overview-sample-code-cookies – Chris Williams Jun 05 '20 at 06:52
0

For anyone who might be struggling with this as of 2023, you can use the hls.js library to stream .m3u8 files with almost zero config (available via NPM, too).

HLS.js is a JavaScript library that implements an HTTP Live Streaming client. It relies on HTML5 video and MediaSource Extensions for playback.

It's as simple as providing the library with the .m3u8 playlist URL and treating the video element as you usually would.

The simplest implementation would be as follows:

// Note that here the Hls class is not instantiated yet. You do this later.
if(Hls.isSupported()){
    let video = document.getElementById("hls-example");
    let hls = new Hls();

    let videoURL = "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8";
    // Load the m3u8 playlist
    hls.loadSource(videoURL);
    // Bind the video element to the media.
    hls.attachMedia(video);
} 
<script src="https://cdn.jsdelivr.net/npm/hls.js"></script>
<video id="hls-example" width="640" height="360" controls></video>

You can read more in the docs here.

GROVER.
  • 4,071
  • 2
  • 19
  • 66