I am currently implementing a video streaming solution that utilizes the HTTP Live Streaming (HLS). As part of the token generation process, I am utilizing a secret key obtained from the "Token Authentication Key" section under the "Stream > Security > Token Authentication Key" in our CDN service management interface. Using this secret key, I am signing the HLS Playlist URLs in a Node.js environment.
However, despite following the prescribed procedures for URL signing, I am encountering an HTTP 403 Forbidden error when attempting to access the HLS playlist URLs (.m3u8).
Interestingly, when I apply the same signing process to the URLs of video thumbnail files (e.g., thumbnail.jpg), I am able to access the files without issue. This successful authentication with the thumbnail URLs validates the functionality of the token generation and authentication process to some extent.
I tried
- "Token Authentication Key" from the section "CDN > Security > Token Authentication Key"
- CDN Token Authentication (Basic) but still thumbnail works but .m3u8 not working
Following are codes that works for thumbnail.jpg but not for playlist.m3u8:
var crypto = require('crypto'),
securityKey = 'key_from_stream_security',
path = '/a1640499-52ef-4721-9ba9-4e659cada6f5/playlist.m3u8';
// Set the time of expiry to 4 days from now
var expires = 1688885053;
var hashableBase = securityKey + path + expires;
// Generate and encode the token
var md5String = crypto.createHash('md5').update(hashableBase).digest('binary');
var token = new Buffer(md5String, 'binary').toString('base64');
token = token.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
// Generate the URL
var url =
'https://vz-9ac10aa0-92d.b-cdn.net' +
path +
'?token=' +
token +
'&expires=' +
expires;
console.log(url);
and
var queryString = require('querystring');
var crypto = require('crypto');
var URL = require('url').URL; // require URL constructor
function addCountries(url, a, b) {
var tempUrl = url;
if (a != null) {
var tempUrlOne = new URL(tempUrl);
tempUrl += (tempUrlOne.search == '' ? '?' : '&') + 'token_countries=' + a;
}
if (b != null) {
var tempUrlTwo = new URL(tempUrl);
tempUrl +=
(tempUrlTwo.search == '' ? '?' : '&') + 'token_countries_blocked=' + b;
}
return tempUrl;
}
function signUrl(
url,
securityKey,
expirationTime = 3600,
userIp,
isDirectory = false,
pathAllowed,
countriesAllowed,
countriesBlocked,
) {
/*
url: CDN URL w/o the trailing '/' - exp. http://test.b-cdn.net/file.png
securityKey: Security token found in your pull zone
expirationTime: Authentication validity (default. 86400 sec/24 hrs)
userIp: Optional parameter if you have the User IP feature enabled
isDirectory: Optional parameter - "true" returns a URL separated by forward slashes (exp. (domain)/bcdn_token=...)
pathAllowed: Directory to authenticate (exp. /path/to/images)
countriesAllowed: List of countries allowed (exp. CA, US, TH)
countriesBlocked: List of countries blocked (exp. CA, US, TH)
*/
var parameterData = '',
parameterDataUrl = '',
signaturePath = '',
hashableBase = '',
token = '';
var expires = expirationTime;
var url = addCountries(url, countriesAllowed, countriesBlocked);
var parsedUrl = new URL(url);
var parameters = new URL(url).searchParams;
if (pathAllowed != '') {
signaturePath = pathAllowed;
parameters.set('token_path', signaturePath);
} else {
signaturePath = decodeURIComponent(parsedUrl.pathname);
}
console.log(parsedUrl.pathname);
parameters.sort();
if (Array.from(parameters).length > 0) {
parameters.forEach(function (value, key) {
if (value == '') {
return;
}
if (parameterData.length > 0) {
parameterData += '&';
}
parameterData += key + '=' + value;
parameterDataUrl += '&' + key + '=' + queryString.escape(value);
});
}
hashableBase =
securityKey +
signaturePath +
expires +
(userIp != null ? userIp : '') +
parameterData;
token = Buffer.from(
crypto.createHash('sha256').update(hashableBase).digest(),
).toString('base64');
token = token
.replace(/\n/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
if (isDirectory) {
return (
parsedUrl.protocol +
'//' +
parsedUrl.host +
'/bcdn_token=' +
token +
parameterDataUrl +
'&expires=' +
expires +
parsedUrl.pathname
);
} else {
return (
parsedUrl.protocol +
'//' +
parsedUrl.host +
parsedUrl.pathname +
'?token=' +
token +
parameterDataUrl +
'&expires=' +
expires
);
}
}
console.log(
signUrl(
'https://vz-9ac10aa0-92d.b-cdn.net/548789ea-d0d7-4dac-b656-cc2acedf0f5c/playlist.m3u8',
'key_from_stream_security',
1688885053,
'',
false,
'/548789ea-d0d7-4dac-b656-cc2acedf0f5c/playlist.m3u8',
),
);