Widevine License Acquisition Reverse Proxy Server
Licensing Servers are the building blocks in streaming digitally protected media content. When the player receives the Encrypted stream it then asks the licensing server for the keys of the encrypted media which is processed by CDM (Content Decryption Module) and used to play the video securely in the application.
Thus, it becomes of the utmost importance to protect the licensing server from unauthenticated requests.
📃 Table of Contents:
Whenever we are in development mode or setting up streaming service for clients we must make sure not to expose the Licensing Server (The Core) directly this could lead to misuse of licensing Server or exploiting it.
So, instead of exposing the Server directly, we set up a Reverse Proxy By integrating server where we can set up middleware for Authentication and Authorization.
There also might be a case where the vendor allows direct license requests without setting up any API Gateway. By integrating our own reverse proxy server we open many doors for expansion like including Authentication, API Rate Limitation, Request Filter, and a lot more.
2️⃣ Setting Up Shaka Player
For the simplicity of set up, playback, and testing I will be using a popular Open Source media player ShakaJS. You are free to use any player as the concept remains the same. 😀 For this, I am using compiled version 3.1.10 of the Shaka with a basic Setup.
Once you have set up the player, make sure you add a Network Request Filter on
LICENSE(2) Request. This network request filter should add a header for
Content-Type with value
Application/Octet-Stream when requesting license so just add these to
request.header object inside the filter. The reason for this will be explained later in the post.
3️⃣ Creating the Reverse Proxy Server
In NodeJS, I will set up an Express Server that runs on
localhost and has a GET route that will simply reply with a response message.
With this now, We will install a few requirements using
🧰 pnpm? Wait, did I misspelled. Haha, No pnpm is also a new popular package manager that works just like npm but is more advanced in terms of features especially disk space management. 😌 Do check that out here
axios:For HTTP request handling
body-parser:A middleware for handling data in POST body.
Once installed, We will now set up a route that accepts POST requests for the license. Now here, As explained in the concept we need to request the actual license server and return the response to client.
I have created an axios config with the required keys. But there are a few important things to note at this point, Let’s take a look at them.
- Since the data in the post request body is an array buffer we need to ensure that Express doesn’t modify it as we could lose the data. So, we will set up a body parser for the raw data in the body as an octet-stream (Array with 8-bit integer data). Refer to line 10 here in the code.
- The data that needs to be passed to the new request should not be sent directly, The licensing servers accept data either an
ArrayBufferView(Reference from Shaka Player Issue, Joe Parrish). So we need to convert the data into an array buffer using
new Int8Array. Refer to line 28 here in code.
- Just like the request, the response also always contains the array buffer, binary data. So, we also need to tell axios about this, to make sure it processes it that way, so we add a new key
responseTypein axios config and set its value to
With this now done, we are now properly able to handle the license data. 🎉
Is that it? Yes 🥳 Congrats. Let us now test the streams.
4️⃣ Testing the Playback
I will now open the player in the browser and pass the manifest and proxy server URL to the player.
It works great; I also tested the server with a few more different licensing servers and have faced no issue. Let’s understand few more things. As told earlier, adding a Network Request filter over the license request that was done to make sure express parses the body for the appropriate requests only. If we hadn’t added the
Content-Type header the express would have passed an empty JSON Parsed body to the licensing server, So we must have that header.
This is a small issue in my code and I’m still trying to figuring out the solution. The issue is open here on my github repository. I’ll make sure to update it as soon as I solve it.
Back to the player, we see that we are also able to request the Server Certificate by sending the 2 Byte payload
("CAQ=" => B64E) in POST Body. Also, in the case of the rotating license key, it works fine. With this, Now I feel I have major test cases 😎
Don’t forget to ⭐ the repository if you liked it. 🙂 Thanks!
TL;DR: The code is available here on my Github Repository
🔨 Optimization / #FollowUP:
- For better performance, we can also bring http2 (SPDY) into view.
- Making it work over text or UTF-8 type response.
- Bringing a cache layer as middleware which serves the access tokens to request license if being used. (Optional)
- Keep connection alive with server for clients who are requesting license again and again.
- Testing with other DRM Providers, the Concept remains the same.
I hope it worked for you; I also encourage you to look at follow-up and share your ideas/solutions in the issues tab of the repository as I am working on them. If you face any issues, feel free to raise an issue in repository or contact me via my social profiles. 🤝 Moreover, If you feel something needs improvement here, feel free to leave a private Note.