Introduction
In the 5th and final installment of our serverless Video on Demand (VoD) system series, we're focusing on securing our endpoints. Only authorized users will have the ability to upload and fetch streaming files. We'll be integrating new AWS services like Cognito for user authentication and linking it with API Gateway and Lambda for accessing and verifying tokens at API Gateway endpoints.
The series is structured as follows:
-
Part 1 - Architecture Overview: We lay the groundwork, detailing the architecture and AWS services involved in our serverless VoD solution.
-
Part 2 - Secured File Upload: We'll cover the video upload process, how to securely upload to s3 bucket using signed url
-
Part 3 - Converting to Stream Format: This part will explain how to manage events from s3 upload to trigger conversion job to transform them into streaming format
-
Part 4 - Saving, Retrieving, and Playing: We will go through the saving and retrieval of video links, then play them using HLS Player on Chrome
-
Part 5 - Securing the Streaming: The final part will focus on securing the application to ensure that only authenticated users can access the videos.
Recap
Before we move forward, let's review our progress:
- Architecture Understanding: We have explored the full architecture and identified the AWS services required for our VoD system.
- Preparation and Upload Flow: A secure upload process was established, allowing files to be uploaded through a signed URL.
- Conversion Using S3 and Lambda: We've set up a system where newly uploaded files trigger a Lambda function to convert them for streaming.
- Save, Retrieve, and Play Stream Files: Created resources for saving and retrieving converted files and provided a quick tutorial on playing them using Chrome’s HLS Plugin.
Now let's proceed to the next step.
Process Overview
Here we will set up the following resources:
- Cognito User Pools: For user authentication
- API Gateway Authorizer: To secure endpoints.
- API Gateway Callback URL: Used as a redirect URL after user authentication with Cognito.
- Lambda Function to Granting Access Token: Handles the redirect URL from Cognito and returns the access token.
Section 1: Cognito User Pool
- Create cognito user pool.
- Follow the settings provided in these images during creation.
- Review and create the user pool.
- Create a cognito domain. We will use this later to authenticate users.
- Remember the API Gateway we have been using? let's copy that and add /authorize at the end(e.g. https://es8m6m6ezd.execute-api.us-east-1.amazonaws.com/dev/authorize) and it will server as our callback url
- Scroll down on your selected user pool to look for App client List and click the app client we just created.
- Click Edit beside the HostedUI.
- Add the callback url from step 5. Then fillout the remaining details based on the image below then save changes.
- Copy the client id and client secret and save it somewhere, we will use it later
- Go back to user pool we created and copy the cognito domain.
- Now let's create a user by clicking Create user.
- Since this is our user then let's automatically verify it by ticking the Mark email as verified and do not send invitation.
Section 2: Lambda Function for Token Exchange
- Go to lambda functions and click create.
- We named our function cognito-exchange-token, if you named it differently then take note because we will use this in next section
- Copy and paste the following code
import json
from urllib.parse import urlencode
import urllib3
client_id = 'client_id_from_cognito_app_created_above'
client_secret = 'client_secret_from_cognito_app_created_above'
redirect_uri = 'the /authorize endpoint of our api gateway domain'
url = 'your.cognito.domain.created.earlier/oauth2/token'
def lambda_handler(event, context):
if code := event.get('queryStringParameters', {}).get('code', None):
http = urllib3.PoolManager()
print(code)
data = {
'grant_type': 'authorization_code',
'client_id': client_id,
'client_secret': client_secret,
'redirect_uri': redirect_uri,
'code': code
}
encoded_data = urlencode(data).encode('utf-8')
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
response = http.request(
'POST',
url,
body=encoded_data,
headers=headers,
)
tokens = json.loads(response.data.decode('utf-8'))
return {
'statusCode': 200,
'body': json.dumps(tokens, indent=4)
}
else:
return {
'statusCode': 400,
'body': 'invalid code'
}
- Replace the client_id and client secret with the one we copied from the cognito app integration
- Replace the redirect_uri with the api gateway url we created in the step 5 above (used as call back url), in my case it is https://es8m6m6ezd.execute-api.us-east-1.amazonaws.com/dev/authorize. If you are lost then go back to your api gateway -> stages -> dev (this is the one we created in previous tutorial) -> Invoke URL. then add /authorize at the end
- Replace url with the cognito domain created in step 4 above then add /oauth2/token (e.g. https://vod-app-auth.auth.us-east-1.amazoncognito.com/oauth2/token)
- Deploy your function. NOTE, this is obviously a bad practice to store sensitive data, but we are only doing it for quick tutorial, you can store this in AWS Secrets for better protection
Section 3: Create API Gateway Callback Endpoint
- Create a new resource named authorize.
- Create a GET method to the authorize resource with lambda function integration and choose the lambda function you created above.
- Deploy the api to publish latest changes
- This endpoint will be accessible via api-gateway-endpoint/authorize (e.g. https://es8m6m6ezd.execute-api.us-east-1.amazonaws.com/dev/authorize)
Section 4: Connect the Authorizer
- Go to API gateway -> APIs-> Select the One we created before (mine is cloudfleet-vod-api)-> Authorizer -> Create Authorizer
- Create an authorizer with the following values:
- Name: cognito-user-auth
- Type: Cognito
- Cognito User Pool: vod-user-pool (or use whatever name you used for cognito pool creation earlier)
- Token Source: Authorization
- Go back to API Gateway -> APIs -> Your API -> Resources
- Go to /streams resource end click edit
- Update the following:
- Authorization: Select cognito-user-auth (this is the Authorizer we just created)
- Request validator: None
- Do the same to /upload resource
- Deploy the API and wait for 5 to 10 minutes to make sure everything is updated
- Now try to Call the /streams endpoint of our api gateway again (e.g. https://es8m6m6ezd.execute-api.us-east-1.amazonaws.com/dev/streams) and you be blocked now because you are unauthorized.
- To fix this, proceed to the last section to obtain tokens
Section 5: Getting authorization token
- Open the following url now [your-cognito-domain]/authorize?response_type=code&client_id=1ehvmahqd3jfk6m6rdu63qrrf7&redirect_uri=[endpoint-from-step-4-just-above-this]. For example: https://vod-app-auth.auth.us-east-1.amazoncognito.com/authorize?response_type=code&client_id=1ehvmahqd3jfk6m6rdu63qrrf7&redirect_uri=https://es8m6m6ezd.execute-api.us-east-1.amazonaws.com/dev/authorize
- The result should give you list of tokens, copy the id_token because we will need that for the authorization this time.
- Go to Postman -> Get Streams -> Headers
- Add the header with key = Authorization and value = id_token. Then run the request again, you should now have access.
- Do the same for the /upload endpoint and you should be able to get access again now.
- Note that the token expires for 1 hour so you will need to start with the signin url again (step 1 of this section) to get new token
- On you actual project, you will need to implement using the refresh token so you do not always need to have your users signin every hour.
Conclusion
That's it, we have finally completed this entire series on creating a secured serverless video on demand. There is one last thing that you can add here is to also make the streaming link secured through signing but that requires a lot more code modification, route redirection, and cloudfront rules; feel free to email me if you wish to learn that part as well.