Live Video Authorization with Private Channel on Amazon IVS
Tags: aws, amazonivs, lambda, tutorial
Published: 2022-05-09 04:24:10

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. 

{% embed https://www.youtube.com/watch?v=PfRZz2KIebQ %}


> #### Github Repository for Demo(s)
> Code for this article is available at [github.com/donnieprakoso/demo-ivs](https://github.com/donnieprakoso/demo-ivs/tree/main/2-private-channel)

---

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.

![](https://dsc.cloud/2c5cc2/ivs-privatechannel-arch.png)

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](https://github.com/aws/aws-cdk) |
| Python | 3.8.13 | [python.org/downloads/release/python-3813/](https://www.python.org/downloads/release/python-3813/) |
| ffmpeg | 4.4 | [ffmpeg.org/download.html](https://ffmpeg.org/download.html) |
| Sample videos | N/A | [peach.blender.org/download/](https://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](https://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:

```bash
git clone github.com/donnieprakoso/demo-ivs
```

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.

![](https://dsc.cloud/2c5cc2/ivs-privatechannel-public-private.png)

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:

```bash
# Working directory: 2-private-channel/keypair/
openssl ecparam -name secp384r1 -genkey -noout -out private.pem
```

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

```bash
# Working directory: 2-private-channel/keypair/
openssl ec -in private.pem -pubout -out public.pem
```

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

```bash
# Working directory: 2-private-channel/keypair/
dev> tree
.
├── private.pem
└── public.pem
```

### 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

![](https://dsc.cloud/2c5cc2/ivs-privatechannel-cdk.png)

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.

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

```

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:

``` python
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)
```

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.

``` python
# 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"
                                     )
```

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.

![](https://dsc.cloud/2c5cc2/ivs-privatechannel-lambda-sign.png)

The code below shows how we can use the AWS Secrets Manager to retrieve the private key.
``` python
# 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']
```

After getting the private key, we can now form the JWT using the ECDSA signature and the SHA-384 hash.
``` python
# 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
```

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

```json
{
  "alg": "ES384",
  "typ": "JWT"
}
```

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.

```json

{
    "aws:channel-arn": "<channel_arn>",
    "aws:access-control-allow-origin": "*",
    "exp": <timestamp>
}
```

#### 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:

```bash
# 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

```

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.

```bash
# 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
```

### 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. 

![](https://dsc.cloud/2c5cc2/ivs-privatechannel-uploadtosecrets.png)

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

```bash
# Working directory: 2-private-channel/keypair/
base64 private.pem > encoded-private.pem
```

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`.

```bash
# 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"
    ]
}
```

### 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()`

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

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

  
```

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.

```bash
# 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>"
```

### 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:

```bash
curl -X GET https://<URL_API_ENDPOINT>/sign
```

Then we will get the following output.
```json
{"token": "JWT_TOKEN_EXAMPLE"}
```

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:

```bash
# Working directory: 2-private-channel/web/
python -m http.server 8080
```

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:

![](https://dsc.cloud/2c5cc2/ivs-privatechannel-ingestvideo.png)

```bash
# Working directory: 2-private-channel/video-stream/
chmod +x stream-video.sh
```

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

```bash
# Working directory: 2-private-channel/video-stream/
./stream-video.sh
```
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:

```bash
cd cd/
cdk destroy
```

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