DEV Community

Donnie Prakoso for AWS Community ASEAN

Posted on

Live Video Authorization with Private Channel on Amazon IVS

You can use the Private Channel feature to restrict video playback access using Amazon IVS. In this article, we will build a simple website, serverless API, and JWT signing to implement the Private Channel feature in Amazon IVS.

Github Repository for Demo(s)

Code for this article is available at github.com/donnieprakoso/demo-ivs


In the previous article, we learned how we can build interactive live videos using Amazon IVS. Running live streaming is now much easier and simpler, without worrying about scalability and low latency.

However, there are some use cases where you need to protect your content. In this situation, you need an authorization mechanism to allow only certain viewers — such as those already logged into your app — to access the live video and deny access to viewers who are not registered in your system.


Controlling Access with Private Channel on IVS

The Private Channel feature is one of the features within Amazon IVS that you can use to implement this use case. With the Private Channel feature, you have the flexibility to control access for playback by using a JWT (JSON Web Token) as part of the mechanism to authorize playback.

By using the Private Channel feature, you can restrict access to playback or broadcasting of live video streaming. For example, you can run live video streaming for a virtual conference that can only be accessed by logged-in users. With this, you can provide additional value for your existing customers.


What We Are Going To Build

In this article, we will build a simple application to demonstrate how we can implement a Private Channel with Amazon IVS. The diagram below describes the set of application architectures that we will build.

First, we will do provisioning for the IVS channel. The main difference between provisioning IVS channels in this tutorial and the previous one is that in this tutorial we will enable authorization mode.

Then, we will do video ingesting. In this tutorial, we will use FFMPEG to send videos to the IVS channel.
After that, we will run the webserver to host live video from the IVS channel on a web page. There are 2 buttons in this tutorial — as you can already see in the "Demo Preview" video above. The first button is to run IVS without a token — which will fail for sure. The second button is to run IVS with a token.

The next question is, how do we get the tokens? We will get this token from the AWS Lambda function which will perform the signing request and return the token in the form of a JWT. We can then use this token as one of the parameters in the IVS Playback URL, to gain access to video playback from IVS.

Requirements

This tutorial uses the following requirements and please make sure that your development environment satisfies all requirements described below:

Name Versions Where to get
AWS CDK 2.17.0 github.com/aws/aws-cdk
Python 3.8.13 python.org/downloads/release/python-3813/
ffmpeg 4.4 ffmpeg.org/download.html
Sample videos N/A peach.blender.org/download/
curl N/A https://curl.se/

Step 0: Clone Github repo

If you'd like to do this tutorial, you can clone this repo: github.com/donnieprakoso/demo-ivs. Otherwise, carry on reading this tutorial if you'd like to get big pictures of how everything works.

To clone the Git repo, you can run this command on your development environment:

git clone github.com/donnieprakoso/demo-ivs
Enter fullscreen mode Exit fullscreen mode

We will use the 2-private-channel module inside the repo.

Step 1: Create ECDSA Private/Public Key Pair

Private channels with Amazon IVS work using the Private/Public Key Pair mechanism. We will upload the public key into IVS and we will use the private key to perform signing requests with the AWS Lambda function. The diagram below explains how this mechanism works.

To generate Public/Private key pair, we can use the openssl tool which is generally already installed on Linux. Here's the command to generate the private key:

# Working directory: 2-private-channel/keypair/
openssl ecparam -name secp384r1 -genkey -noout -out private.pem
Enter fullscreen mode Exit fullscreen mode

And to generate the public key, we can use the private key with the following command:

# Working directory: 2-private-channel/keypair/
openssl ec -in private.pem -pubout -out public.pem
Enter fullscreen mode Exit fullscreen mode

If you successfully ran the commands, we can see the files:

# Working directory: 2-private-channel/keypair/
dev> tree
.
├── private.pem
└── public.pem
Enter fullscreen mode Exit fullscreen mode

Step 2: Deploy CDK App

In this step, you only need to run the cdk deploy command in the cdk folder. However, before we move on to deployment, let's first evaluate 2 important things: 1) an overview of the CDK app and 2) using the Lambda function for signing JWT.

CDK App Overview

The first thing defined in the CDK app is the AWS Secrets Manager. In this tutorial, we will use the Secrets Manager to store the private key that we generated in the previous step.

private_key_secret = secrets_manager.Secret(self, "{}-secret-private-key".format(id),
                    secret_name="{}-secret-private-key".format(
                        en)
                    )

Enter fullscreen mode Exit fullscreen mode

In addition, we will also upload the public key into Amazon IVS to be used as a playback key. The code snippet below defines how we can do this:

public_key_pair_file = self.node.try_get_context(
    "publicKeyPair")

public_key_pair_string = ""
with open(public_key_pair_file, 'r') as f:
    public_key_pair_string = f.read()

playback_key = ivs.CfnPlaybackKeyPair(
    self, "{}-keypair".format(id), name="{}-keypair".format(id), public_key_material=public_key_pair_string)
Enter fullscreen mode Exit fullscreen mode

Finally, we can define the IVS channel and notice the authorized parameter which has the value True. This defines an IVS channel that requires authorization to be able to perform playback.

# File: 2-private-channel/cdk/app.py

        ivs_channel = ivs.CfnChannel(self, "{}-channel".format(id),
                                     authorized=True,
                                     latency_mode="LOW",
                                     name=id,
                                     recording_configuration_arn="",
                                     type="STANDARD"
                                     )
Enter fullscreen mode Exit fullscreen mode

The rest of the CDK application is defining AWS Lambda functions and integration with Amazon API Gateway. For details, you can see the source code on Github.

AWS Lambda Function for Signing JWT

To get a token as playback authorization, we need to do a signing request with the public key. In this tutorial, we will implement an API with Amazon API Gateway and AWS Lambda function to perform signing requests with JWT.

The code below shows how we can use the AWS Secrets Manager to retrieve the private key.

# File: 2-private-channel/lambda-functions/sign-requests/app.py

def get_private_key(secret_id):
    client = boto3.client('secretsmanager')
    response = client.get_secret_value(
        secretId=secret_id,
    )
    if not response:
        return None
    if "SecretString" not in response:
        return None
    else:
        return response['SecretString']
Enter fullscreen mode Exit fullscreen mode

After getting the private key, we can now form the JWT using the ECDSA signature and the SHA-384 hash.

# File: 2-private-channel/lambda-functions/sign-requests/app.py

def sign_request(private_key, channel_arn):
    payload = {
        "aws:channel-arn": channel_arn,
        "aws:access-control-allow-origin": "*",
        "exp": datetime.now() + timedelta(days=3)}
    encoded = jwt.encode(payload, private_key, algorithm="ES384")
    return encoded
Enter fullscreen mode Exit fullscreen mode

The token is in base64 form and if you decoded the token, the JWT will have the following headers:

{
  "alg": "ES384",
  "typ": "JWT"
}
Enter fullscreen mode Exit fullscreen mode

In addition, JWT will also have a payload that stores information channel-arn and also origin access * which means it can be accessed from any website. If you want to restrict only loading from your website, then you need to change it to your domain name. In addition, I also added exp — expiration time — as part of the JWT claim which indicates that this token should not be processed after the specified time has passed.


{
    "aws:channel-arn": "<channel_arn>",
    "aws:access-control-allow-origin": "*",
    "exp": <timestamp>
}
Enter fullscreen mode Exit fullscreen mode

App Deployments

Now you understand the main components of this CDK app. The next step is to run cdk deploy to deploy the app.

In this app, it uses context to get the file path for your public key pair. Below is an example on how to run the cdk deploy command:

# Working directory: 2-private-channel/cdk/

cdk deploy --context publicKeyPair=<YOUR_PUBLIC_KEY_FOLDER>/public.pem
Synthesis time: 7.4s

demo2-private-channel: deploying...
[0%] start: Publishing c713b4752fe62d1d6d6c8a9cacd2e576039cd0c7697ea0bf0317b41aa6ec8f40:XXXXXXXXXXXXXX-us-east-1
[100%] success: Published c713b4752fe62d1d6d6c8a9cacd2e576039cd0c7697ea0bf0317b41aa6ec8f40:XXXXXXXXXXXXXX-us-east-1

 demo2-private-channel (no changes)

Deployment time: 8.32s

Enter fullscreen mode Exit fullscreen mode

After the deployment is complete, you will get the following output. You will need the output below, so make sure you save it for later use.

# Working directory: 2-private-channel/cdk/

Outputs:
demo2-private-channel.demo2privatechannelapigatewayEndpoint7F53E47C = https://XXXXXXXXXXXXXX/prod/
demo2-private-channel.demo2privatechanneloutputapigateway = https://XXXXXXXXXXXXXX/prod/
demo2-private-channel.demo2privatechanneloutputchannelarn = arn:aws:ivs:us-east-1:XXXXXXXXXXXXXX:channel/XXXXXXXXXXXXXX
demo2-private-channel.demo2privatechanneloutputchannelingest = XXXXXXXXXXXXXX.global-contribute.live-video.net
demo2-private-channel.demo2privatechanneloutputchannelplayback = https://XXXXXXXXXXXXXX.us-east-1.playback.live-video.net/api/video/v1/us-east-1.XXXXXXXXXXXXXX.channel.XXXXXXXXXXXXXX.m3u8
demo2-private-channel.demo2privatechanneloutputsecretmanagerarn = arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXXXX:secret:demo2-private-channel-secret-private-key-XXXXXXXXXXXXXX
demo2-private-channel.demo2privatechanneloutputstreamkey = XXXXXXXXXXXXXX

Total time: 15.72s
Enter fullscreen mode Exit fullscreen mode

Step 3: Post Deployment — Deploy CDK

At this point, you have a ready-to-use architecture for Private Channel implementation with IVS. After deployment, you need to import the private key into the AWS Secrets Manager.

To import the private key, we first need to convert it into base64 form.

# Working directory: 2-private-channel/keypair/
base64 private.pem > encoded-private.pem
Enter fullscreen mode Exit fullscreen mode

After that, we can import the private key into AWS Secrets Manager using --secret-id which we got in the output of CDK. Note that you also need to define the --region parameter to define the region where you are deploying your app — in this case, I used us-east-1.

# Working directory: 2-private-channel/keypair/
dev> aws secretsmanager put-secret-value --secret-id arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXXXX:secret:demo2-private-channel-secret-private-key-XXXXXXXXXXXXXX --secret-string file: //encoded-private.pem --region us-east-1

{
    "ARN": "arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXXXX:secret:demo2-private-channel-secret-private-key-XXXXXXXXXXXXXX",
    "Name": "demo2-private-channel-secret-private-key",
    "VersionId": "XXXXXXXXXXXXXX",
    "VersionStages": [
        "AWSCURRENT"
    ]
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Adjusting Variables for Web and Video Ingestion Script

Now we need to adjust the variables for the web and video ingestion script.

For the web, we need to change the location of the playback URL in the index.js file so that the Amazon IVS player knows the URL location for the video playback. You need to change this URL playback manually for 2 functions, namely playWithoutToken() and playWithToken()

// File: 2-private-channel/web/index.js
function playWithoutToken() {
  var PLAYBACK_URL = "<PLAYBACK_URL>";
  ...
}

function playWithToken() {
  var PLAYBACK_URL = "<PLAYBACK_URL>";
  ...
}


Enter fullscreen mode Exit fullscreen mode

Apart from that, we also need to change the variables in the video ingestion script, which you can find at 2-private-channel/video-stream/stream-video.sh.

For the STREAM_URL variable, you will need the channel_ingest and stream_key variables which you found when you deployed the CDK app in step 2.

# File: 2-private-channel/video-stream/stream-video.sh
TEST_FILE="<YOUR VIDEO FILEPATH>"
STREAM_URL="rtmps://<YOUR_CHANNEL_INGEST>:443/app/<YOUR_STREAM_KEY>"
Enter fullscreen mode Exit fullscreen mode

Step 5: Test!

Now we have everything we need for testing. Before that, we need to make sure that our API is running properly.

Using the apigateway variable from step 2 — which is the API URL of the Amazon API Gateway — we can test our API by doing a GET request with the following command:

curl -X GET https://<URL_API_ENDPOINT>/sign
Enter fullscreen mode Exit fullscreen mode

Then we will get the following output.

{"token": "JWT_TOKEN_EXAMPLE"}
Enter fullscreen mode Exit fullscreen mode

To finish testing, we need to run a webserver for serving HTML pages and ingesting video to the Amazon IVS channel.

To run the webserver in Python, you can use the following command:

# Working directory: 2-private-channel/web/
python -m http.server 8080
Enter fullscreen mode Exit fullscreen mode

Now, you can access the website by visiting http://localhost:8080.

And to run the video ingestion script, you need to run the following command to make the script executable:


# Working directory: 2-private-channel/video-stream/
chmod +x stream-video.sh
Enter fullscreen mode Exit fullscreen mode

To run the script, you can use the following command:

# Working directory: 2-private-channel/video-stream/
./stream-video.sh
Enter fullscreen mode Exit fullscreen mode

And with this command, we are now ingesting the video to the IVS channel.

Now, when we open the web http://localhost:8080, there are two buttons available. When you click Play without Token, video playback will not be successful because this channel requires authorization with a playback key.

TODO: ADD Screenshot

And when you click Play with Token, you will see your video is running fine because the playback URL has been added to the token that we get from the API.

Congrats! 🥳

Now you can implement private channels with Amazon IVS.

Step 6: Cleanup

Don't forget to remove all resources once you're done with the tutorial. To do cleanup, do the following steps:

cd cd/
cdk destroy
Enter fullscreen mode Exit fullscreen mode

Choose "Yes" and CDK will remove all resources created.

Wrapping Up!

I was quite surprised by how easy it is to implement access control restrictions for video playback using Amazon IVS. The token mechanism, using JWT signing and public/private keys, can be implemented in various authentication and authorization approaches.

And that's a wrap! Hope you enjoyed this tutorial and if you have any questions, please leave your comments.

Happy building!🤘🏻
— Donnie

Top comments (3)

Collapse
 
heyray2 profile image
HeyRay2

I'm getting the following error when running the "cdk deploy" command:

--app is required either in command-line, in cdk.json or in ~/.cdk.json
Enter fullscreen mode Exit fullscreen mode

Any insights you can share on how to correct this?

Collapse
 
heyray2 profile image
HeyRay2 • Edited

This appears to be due to a "cdk.json" file not being present in the "cdk" folder.

I manually created a "cdk.json" in folder using a structure similar to the suggestion at:

docs.aws.amazon.com/code-samples/l...

I got the "cdk deploy" command to run but now I'm getting a warning about an empty zip file being uploaded which halts the stack creation process.

Collapse
 
heyray2 profile image
HeyRay2 • Edited

The empty zip file issue appears to have been a file permissions issue where the deployment process was attempting to write to a local folder that my user did not have full write access to.

Changing folder permissions allowed the deployment to run successfully.

The issue with the missing "cdk.json" was still an issue that you may want to address in your code repository for this tutorial.

Thanks!