Guides: How to use signed URLs to secure video playback

0:00

this video that you're watching right now is meant  to be public anybody at any time should be able  

0:06

to load it up in their browser and click the play  button and it should just work but not every video  

0:12

is meant for a public audience maybe there's a  requirement that the viewer should be a paying  

0:19

member or maybe they need to have a specific  user permission to access the next video in  

0:26

a course whatever the case is it's possible that  you want to limit who can and cannot watch your  

0:33

video content and in this video we're going to  talk about how you can Implement secure playback  

0:40

using muks and our signed playback policies let's  Jump Right In we have a guide available that  

0:47

makes it easy to go through all of the different  permutations of what it takes to implement signed  

0:55

playback policy but this is a complicated topic  so in this video we're just going to walk through  

1:04

an actual code sample of how you can Implement  signed playback in a production application I'm  

1:12

going to use code sandbox to write and store all  of this code and we'll be using nextjs but you  

1:19

don't need to have any sort of nextjs experience  to understand this video you should be able to  

1:24

apply those same principles to whatever code base  you're working in I'm here in a very B basic page  

1:31

that will have some sort of interaction with the  server when the browser makes a request and then  

1:37

it will render something to the browser that can  be viewed so right now on this homepage this in  

1:45

nextjs is our code that runs on the server and  then here we're rendering a player uh and if I  

1:54

go over to my player file we'll see that this is  a client component that is rendering a MX Player  

2:03

we're using a playback ID from a specific MX Video  asset that's just open to the public so by passing  

2:12

this string over to our MX player component  we see that the video is showing up here on  

2:18

the right hand side looks like it's working that's  good now what we're going to do here is switch to  

2:25

a playback ID that's signed that's protected and  take take a look at what actually happens in that  

2:32

case so first of all I'm going to go over to my M  dashboard and we're going to have to create a new  

2:41

asset one that is protected by a signed playback  policy so I'm going to go to assets create new  

2:49

asset and we can just leave all the defaults  here for this case other than this playback  

2:57

policy value right now it's set to public but we  don't want this particular video to be public we  

3:04

want only certain folks to be able to watch it  so I'm going to change the playback policy value  

3:10

from public to signed if I click run request  we'll see that the asset is now preparing and  

3:20

we should be able to go to the view asset page to  see some more about what we've just done so take a  

3:28

look here even the player in the M dashboard is  not showing the video because there's no public  

3:34

playback ID by default this video is not going to  load for anybody unless you have a signed token so  

3:42

now we have to do that work we have to understand  what it takes to sign a token and what instances  

3:49

it makes sense to be able to issue sign tokens  for those users that were authorizing to view  

3:55

the video so I'm scrolling down here just a bit  and we can see that this particular playback ID  

4:02

is signed so I'm going to copy this playback ID  and I'm just going to bring it back over to our  

4:09

nextjs application remove the existing playback  ID and replace it with our signed playback ID so  

4:17

if I give this a Reload look at this uh we have  an invalid playback URL because it's basically  

4:25

acting like this video does not exist we haven't  set any sort of uh permissions to be able to tell  

4:32

the player we do have authorization to view this  video so let's go ahead and do that now the first  

4:38

thing we'll have to do is create a signing key  within the MX dashboard this key is used almost  

4:45

as uh like a key to a door that tells you that you  have the permissions or authority to be able to  

4:51

access what is behind that door let's go ahead and  create that signing key right now back over in the  

4:57

MX dashboard I'm going going to go to the settings  area and click on signing Keys now we'll want  

5:04

to make sure that we have the same environment  selected that we've uploaded that signed video

5:11

to right now I have a production environment  selected but I actually uploaded that video to  

5:20

an environment called delete me so I'm going to  make sure that I'm generating that signing key in  

5:26

the same environment that my protected video  content has been uploaded it says I haven't  

5:32

created any signning Keys just yet I'm going to  go ahead and click generate new key and click  

5:38

the green button to actually generate the key so  we have two values here we have the signing key  

5:43

ID and then this private key that's listed  below we can use both of these in different  

5:50

ways the way that I'm going to load it into the  nextjs environment is by setting these values as  

5:58

environment variables within nextjs so let's start  by copying the signing key ID heading over to our  

6:05

nextjs code sandbox and pasting that value and I'm  also going to add a MX signing key secret and that  

6:16

one is going to be set to that second value which  is a really long string I'll go ahead and save and  

6:23

we won't see anything change just yet we're still  have a little bit of work left to do I'm heading  

6:28

back to the homepage now and within that serers  side code is exactly where we'll want to generate  

6:36

our signed playback tokens this should not be  done in the browser environment we don't want  

6:43

to expose your signing key ID or secret to the  browser or the client because then anybody can  

6:50

go around signing playback tokens and pretending  like they're the ones that have authorization to  

6:57

determine who is able to watch those videos we  don't want that so we're going to make sure that  

7:03

this signing occurs in a serers side protected  environment I'm over here in the secure server  

7:10

code for our nextjs codebase and I'm going to  use a helper for signing JWT Json web tokens  

7:19

uh and this particular package in JavaScript  is called Json web token so I'm going to add  

7:26

this to my project depending ending on which  language you're using you probably have your  

7:32

own implementation for handling signing requests  within that language codebase we just do a quick  

7:39

search for a Json web token signing Library so now  we have Json web token installed with our project  

7:48

and I'm going to take a look at the docs here  for how we actually use this particular Library  

7:54

there's really one method in particular we're  interested in for this video and it's this signing  

8:00

method right here so we can see that we're going  to call something JWT doign we're going to pass a  

8:08

payload and then also pass the secret or private  key as the second argument so here's an example  

8:16

implementation of how you can actually sign a  payload using this Library I'm going to select  

8:21

it and copy it since this is a typescript project  I'm also going to install the types for Json web  

8:28

token okay we should be in a good spot to be able  to import this library and start putting it to

8:34

use so I'll import the library and then in  our server side code I'm going to call the  

8:45

JWT sign method now this is documented within this  particular library's documentation uh depending on  

8:53

the library that you're using you might have a  different name for this method you can see that  

8:58

this signing method accepts a few different  arguments the first is the payload that we're  

9:03

going to sign and then there's another one right  after that that is looking for the secret key so  

9:09

let's take a look at what we should pass for  that first payload argument back over on the  

9:15

MX documentation we'll be able to look at what  we should be including within this payload that  

9:22

needs to be signed in order to give us access to  be able to view that video there's a few different  

9:29

claims here that are going to be required that  we should pass values in for and in return we'll  

9:35

generate a signed token for playback the first  one I'm looking at here is called sub and that's  

9:42

going to be the MX video playback ID that we are  looking to sign so I'm going to go back to this  

9:50

code base here and I want to pass that sub value  as the same value that we have for the playback  

9:58

ID okay next let's go back over to the m docks  and we have odd for audience or essentially what  

10:09

are we signing here in this case we are just  signing the playback for this video so I'm  

10:14

going to pass the value of V to this odd claim  next up here is the EXP claim and this is when  

10:23

this signing token should be expired now these  tokens are only shortlived they will only last  

10:32

for as long as you set them to be good for um  this allows us to have playback URLs that work  

10:40

for a little while but then no longer work once  they have passed the expiration time that you  

10:46

define so I'm going to set a value of one day  from now um for this particular expiration time  

10:59

and then finally here we're looking at the kid  claim and this is going to be the key ID that  

11:05

we're using that we got when the signing key was  created now remember we put that value within our  

11:13

environment variables here and it's going to  be this top one this Muk signing key I could  

11:19

probably actually put anore ID on this just  so it's a little more clear which value we're

11:25

using all right all right now we have those  four values in place and we're seeing an  

11:32

error here that says that we're looking  to have a value for the secret or private  

11:37

key and that's the second argument for this  signing method so I'm going to pass that right

11:44

here if I go back to this file  I can see what the key name

11:52

is and now the error has gone away but let's  make sure that we are getting the token as

12:03

expected all right so I'm seeing a value generated  here which is good news but we're still not able  

12:17

to access that playback well this value needs to  be used in correlation with the MX Player library  

12:25

or whatever video player that you're using to be  able to tell uh the browser that it can access  

12:33

the particular playback ID uh and here's proof  here's the token that we just generated and just  

12:40

to note if you're using a player other than M  player you'll need to pass the entire playback  

12:47

URL to the player and include the token by adding  a token query parameter to the end of the URL so  

12:56

I'm going to take this token value and hand it off  to the player in react that might look something  

13:05

like this and all this is doing is defining that  the player should the player component should now  

13:13

accept a token value and in MX Player the way  that we pass tokens for use is through another  

13:22

property called The Tokens property so I am going  to specify Within in this tokens value that the  

13:32

token for playback is going to be the token that  I've generated I'll hit save and reload the page  

13:42

all right and we're still not quite there this  brings up another part of what is going to be  

13:49

common really with signing tokens is sometimes  there's a little bit of debugging that you have  

13:55

to do in order to figure out what's going on  here why isn't this this video playback working  

14:00

as expected so I want to walk through a couple of  different things that we should think about when  

14:05

debugging an issue like this and how we can get to  the bottom of what's going on so we already logged  

14:11

out that this MX Player is getting the value  that it's expecting but the value is just not  

14:17

quite right I'm going to go back to the top level  page and double check on what our signing method  

14:26

is looking like I'm back over in the server  code of our homepage and I'm taking a look at  

14:34

this signing method here um so let's double check  everything we have the sub as the signed playback  

14:41

ID here that looks correct to me we have the odd  value as V for video which is what we're looking  

14:49

to sign right now we have the EXP set to a date  um of now which creates the unix's time for this  

15:00

moment in time plus 86400 which is the number  of seconds in a day uh so this will expire one  

15:08

day from when the token is generated and we have  the kid which is the key ID value for the signing  

15:16

key this is looking correct to me um so I would  double check that these values are getting picked  

15:23

up appropriately and uh looks like for me they are  if I just log them out and so what I did was just  

15:33

a quick look again at the mug dos and I noticed  something here that we want to actually be signing  

15:39

using a different algorithm than what the default  algorithm is with this Json web token package you  

15:49

can see that the default algorithm is signed  with this hmac setup but uh to use a different  

15:56

RSA approach we need to specify ify that as the  third argument during the signing process so I'm  

16:03

going to go ahead and copy this rs256 config value  go back over to my signing method and add that  

16:14

as the third argument we're not quite out of the  woods just yet when we change the algorithm it's  

16:21

now telling us that our secret key is expected to  be in a different format from what we're seeing uh  

16:29

as the string we've provided up here so I'm  back over in this libraries documentation I  

16:35

can see that when they are reading the private  key they are actually loading that from a local  

16:43

file instead of the environment variable and if  we were to download this file from this PM file  

16:51

from MX and just crack this thing open um what  I'm kind of interested in is what does it look  

16:59

like uh what will we see here that might look  a little bit different from what we've copied  

17:04

and pasted into our environment over here this ls0  value so I've downloaded the file and I'm going to  

17:16

create uh I want to paste its contents right here  just so we can look at it so it does look pretty  

17:23

different to me uh when I've opened this file and  pasted it here below so what is the difference  

17:30

here what is going on well I'm back over here in  the MX dashboard and notice this private key is  

17:38

base 64 encoded so in order to use it within our  sign signing package we need to decode it from  

17:46

Bas 64 format there's all kinds of gotas like  this when it comes to security this is why I  

17:54

picked making videos as a career instead so let's  clean up this environment uh file to look like it  

18:01

used to look and I'm going back to our server  code and I need to base 64 decode this signing  

18:11

key secret that's going to look in something  like this within a node JavaScript [Music]

18:23

environment so I am decoding uh using this  approach right here I now have the secret  

18:35

key value and I'm going to replace this second  argument with the new decoded secret key value  

18:42

I'll hit save and look we still get an error I  probably should have tested some of this before I  

18:52

went in cold okay so I've done a little bit of off  camera debugging uh because you sometimes have to  

19:02

Dive Right into these kinds of things so a couple  things that I've uncovered here first of all this  

19:07

particular decoding method needed a little bit  of work to get the decoded base 64 value from  

19:14

our signing key secret but you can see down here  this is looking as expected now when I log out  

19:19

the secret key and the other thing that I noticed  is uh this expiration value isn't exactly in the  

19:27

correct format so there's some uh different math  that we need to do to get the expiration value set  

19:34

up correctly and I have that uh preconfigured here  so we're doing some uh Division and just to kind  

19:42

of break this out this is now an hour this token  will expire an hour from now so that's 60 seconds  

19:49

time 60 minutes is that right 60 seconds time 60  will lead to 1 hour and then uh um as we pass this  

19:59

new expiration we've got this base 64 decoding  method in progress in place now we should be  

20:06

able to save and click reload to get that signed  playback loading we did it uh this stuff uh does  

20:17

get a little complicated but I assure you it does  work when you have it all configured appropriately  

20:24

all right great so we have a signed token that's  working we did it but here's the thing we don't  

20:30

want to give this token out to just anybody that  loads this page right we only want to generate the  

20:36

token if that particular user has permission  to watch that video so it's probably not the  

20:44

right call to just put this token generation right  within every single page load that sort of defeats  

20:51

the purpose we might as well use a public playback  ID uh in that case so let's instead move this to a  

21:00

different area that is going to determine whether  or not we should issue the playback token so for  

21:08

demonstration purposes let's move this code  to an API endpoint just so we can encapsulate  

21:14

and visualize everything that's happening on the  server and potentially call it from a client side  

21:20

application in the future so I have a API endpoint  that I've set up just as an example here going to  

21:27

paste that right in um and let's make sure that  we also bring over the JWT Library which by the  

21:36

way I found out some people pronounce that jot  uh I don't know this is like the new GIF vers  

21:41

GIF argument I'm going to go with JWT um okay so  we have that imported here we have our secret key  

21:50

and everything's loaded up appropriately so all  I'm going to do here is change this response to  

21:56

include the token that we've generated so I  can remove the console log and now anytime a  

22:05

get request goes to this particular endpoint it's  going to execute the code that we wrote sign the  

22:12

token and then return it in the Json payload  so I'll click save there head back over to the  

22:18

homepage and we can see that in the server still  I'm making that fetch request and this is the  

22:25

endpoint to the API route that we've just created  created we receive the response save it in this  

22:30

Dynamic data variable and then we convert that  uh response body to a Json object so I'm doing a  

22:39

console log here of that response and uh we should  assume just to get rid of this error here for a  

22:46

moment uh we should assume that the uh response  token is going to exist within the token key in  

22:54

that Json payload but let's hit save and reload  and see what we see here so perfect so here is  

23:03

that payload that we're seeing as a result of that  API call there's a token property and the value of  

23:10

the token is right there so that's working and the  video is loading over here as well that's great  

23:17

now here's the thing over on that uh API route  we're still just issuing this token regardless  

23:25

of any circumstance right we probably don't want  to do that so here um is some example code that  

23:33

just basically is something that you might want  to implement on your side and it depends on what  

23:37

your business use case is perhaps you would look  at the current logged in user and if they have a  

23:44

specific permission then you would go ahead  and issue that token um or check the [ __ ]  

23:50

database maybe in this example and determine  whether or not the user is on the plan the paid  

23:56

plan that has access to this particular piece of  content that sort of business logic is up to you  

24:03

to determine what's the right fit for your use  case just for demonstration purposes what we'll  

24:08

do here is we will let's let's come up with a  reason why this user might not have access to  

24:16

this token so instead of returning the correct  token here what I'm going to do is return a an  

24:23

error so right here maybe let's just just get  the um error response set up so that that's  

24:32

returning correctly so instead of a successful uh  token we're going to return an actual error we'll  

24:42

call it not allowed I'm going to hit save reload  yeah and look we are actually getting a different  

24:50

message now is that right so so I'm returning  an error response now I'm going to go back to  

24:57

the page page and before we attempt to convert  it to Json let's take a look at this Dynamic

25:05

data I'm scrolling through and I'm seeing  that this particular response object has  

25:14

that status code of 401 which we would  send if the user is unauthorized to view  

25:20

the content so let's come up with one way  that we might handle this error we'll put a  

25:27

uh variable for the token here and we're going  to continue with this fetching just as is but  

25:36

instead of immediately trying to convert to  Json I'm going to wrap this in a tri catch

25:44

block and then I'm only going to  render the player if we have a valid

25:59

token so let's hit reload here and  we are seeing that we're getting  

26:30

an error in response to attempting to make our

26:34

call I should probably move  this guy right in here as

26:41

well so here we're seeing that the token is not  being issued it's not allowed but if I go back  

26:53

to this route and I change the requirements I'm  going to remove this and again you would check  

27:01

to see if your requirements are met if they're not  met you can return an error like this if they are  

27:07

met then you can go ahead and issue that token  in which case that token will make it over to  

27:14

our player and allow for a successful playback  so we finally got it set up so that we have  

27:20

signed playback working within our application but  there's still a little bit more to know about um  

27:28

what we should consider here is that every asset  that's included on MX has different elements that  

27:36

will allude to the contents of that particular  asset let's think about that when you generate  

27:43

uh thumbnails for a video you might have some  previews of what uh the contents of that video  

27:49

are within the thumbnail same for a text track  or some captions and all of those also need to  

27:56

be protected but there's some use cases where it  could be helpful to provide the user with access  

28:04

to a thumbnail of a video but then protect the  actual video playback so this is where it can  

28:12

get extra tricky right we set this up in a way  so that each element will require its own signed  

28:20

playback token let me say that again you won't use  the same playback token to uh authorize access to  

28:29

a video that you would use to authorize access to  a thumbnail that's going to be a different token  

28:36

entirely so throughout that process what you would  end up changing when you were trying to generate a  

28:42

token for let's say a thumbnail or a storyboard  instead of just the video is this AUD claim the  

28:51

audience claim so here again we passed a V for  video if we also wanted to generate a token for  

28:59

being able to show thumbnails for this particular  video we would have to do that separately so let's  

29:06

look again at the MX documentation we are going to  look at the claims and we can see different values  

29:17

here for thumbnail for the GIF and for story  boards so let's go ahead and sign let's create  

29:24

another signed token for access ing the thumbnail  of this video most of the hard work is done here  

29:31

I'm going to go back and create a another token  so I'm copying this exact method that we've used  

29:40

above and pasting it here and the only thing I  want to change is this odd value from a v to a t  

29:50

I'm also going to clarify that this token is now  the playback token and this one is the thumbnail

29:59

token let's change the payload that  we return in a successful response  

30:08

playback token and thumbnail token great  and now over here on our homepage I'm  

30:20

going to update this as well to  say playback token let thumbnail

30:28

token and then the [Music]

30:45

setter and I'm going to go over  to the player as well and specify  

31:02

that this component now accepts two [Music]

31:06

[Music] tokens and look at that now we're actually  getting a different preview and that little broken  

31:24

image icon has gone away that's because this this  token that we're passing to the player tells the  

31:30

player we do have the appropriate permissions to  access the thumbnails that player will then make  

31:36

the request uh to get that thumbnail with that  token attached to it and as long as it succeeds  

31:42

it will show the thumbnail right here on the  screen as expected so we covered a lot in this  

31:50

tutorial but we talked through a successful  setup about signing tokens and why you might  

31:56

want to use sign playback to begin with we went  through the different approaches to do that within  

32:01

a nextjs application and a node environment  and we talked through different ways that you  

32:07

might approach debugging some situations when  things might go arai hopefully this video has  

32:14

equipped you with the tools that you need to  make a successful signed playback integration  

32:19

in your app and if you have any questions at  all please hit us up and we will be more than  

32:25

happy to help make your integration a success  thanks for watching we'll see you in the next

32:31

video