Token based security
THEOlive offers the option to enable JWT token security on channel distribution level. This can be interesting if you only want valid users to access your stream. Read more about the feature and configuring it on your channels on the token based security guide.
This page will demonstrate how to configure the React Native Player SDK for playback of channels with token based security enabled.
Setting up the React Native THEOplayer SDK for THEOlive
Refer to the getting started guide for the prerequisite steps in getting the React Native SDK up and running for THEOlive playback.
Configuring THEOplayer to pass the token
The THEOlive API provides a simple property to configure your token:
const token = getToken(); // Generate or request your token, for more information check the token based security guide linked above.
player.theolive.authToken = token;
This will ensure the player includes your token in the authorization header on all subsequent requests it performs for playback of your THEOlive channel.
Dealing with token expiry and rotation
If your tokens are short-lived, you want to make sure to update the token being passed to the player and requests before it expires, to allow playback to continue beyond expiry. This can simply be done by updating the header on the player in the same way. For example, one could check on an interval that makes sense for your token lifespan whether the token is about to expire and update when necessary, for example:
let token;
// Helper function to check whether your token will expire within one minute from now
function tokenWillExpireSoon() {
const payload = JSON.parse(atob(token.split('.')[1]));
const exp = payload.exp; // in seconds
const now = Math.floor(Date.now() / 1000); // current time in seconds
return exp - now <= 60;
}
function maybeUpdateToken() {
if (!token || tokenWillExpireSoon(token)) {
token = getToken(); // Generate or request your token, for more information check the token based security guide linked above.
player.theolive.authToken = token;
}
}
maybeUpdateToken();
setInterval(maybeUpdateToken, 30000); // Check every 30 seconds
Clearing the token
If the token isn't needed anymore, e.g. when switching to an unprotected channel or a non-THEOlive source altogether, the header can be simply removed as follows:
player.theolive.authToken = undefined;
Enabling Token Based Security for Safari browsers on iOS <17
Apple devices running an iOS version lower than 17.1 do not support MSE, therefore there are limitations with what is possible when playing a THEOlive stream. One such limitation is that the above network API approach does not work on those devices. Instead, a service worker needs to be registered to support playback of JWT enabled streams.
The service worker needs to intercept the fetch
requests originating from the app, to be able to include the Authorization
header to the requests.
A code snippet for the service worker code is shared below.
Service worker registration is only possible in a secure environment (https://
) or on localhost
. There can also only be one service worker active, so if your environment or application already has a service worker active, you will need to include the additional functionality in that service worker.
self.addEventListener('install', () => {
console.log('Service worker installed!');
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
console.log('Service Worker activated');
// Claim clients so the service worker is in effect immediately
event.waitUntil(self.clients.claim());
});
// Intercept the fetch event and add in your JWT
self.addEventListener('fetch', (event) => {
event.respondWith(
(async function () {
try {
const url = new URL(event.request.url);
if (!url.origin.endsWith('theo.live')) {
// Requests not made by the player for playback of the THEOlive channel should not be modified.
return fetch(event.request);
}
const token = getToken(); // Generate or request your token, for more information check the token based security guide linked above.
// Clone the request and add the JWT header
const modifiedRequest = new Request(event.request, {
headers: {
...Object.fromEntries(event.request.headers.entries()),
Authorization: `Bearer ${token}`,
},
});
return fetch(modifiedRequest);
} catch (error) {
console.error('Error in fetch handler:', error);
return new Response('Service Worker Error', { status: 500 });
}
})()
);
});
In order for the service worker to be able to intercept requests made from the player library, its scope must include the player SDK files. That means the player and service worker must be hosted on the same path or the player must be hosted in a subfolder of the path where the service worker is hosted.
To register this service worker in to your code, you can attach it this way.
function registerServiceWorker() {
if (!('serviceWorker' in navigator)) {
console.error('Service worker not supported!');
return;
}
const serviceWorkerScope = "/service/worker/path" // Replace with your own path to the service worker location.
const serviceWorkerPath = "${root}/service-worker.js"; // Replace with the filename of your service worker.
// We recommend unregistering actively before (re-)registering as we've seen issues where hard reloads could cause issues if the service worker wasn't unregistered.
const registration = await navigator.serviceWorker.getRegistration(serviceWorkerPath);
await registration?.unregister();
navigator.serviceWorker
.register(serviceWorkerPath, {
scope: serviceWorkerScope,
})
.then((reg) => {
if (reg.active) console.log('Service worker registered!');
})
.catch((err) => {
console.error('Could not register service worker!', err);
});
};
// Initialise the service worker some time early in the processs.
if (!(window.MediaSource || window.ManagedMediaSource)) {
registerServiceWorker();
}