Gatsby + Cognito Authentication

Gatsby + Cognito Authentication

I create videos that I don't want to upload to Youtube or Vimeo. There's nothing sketchy about my videos; I just don't want to share my family videos with the world, nor do I need Google's machine learning dissecting my kids into advertising data points. So, I want to self host.

Also, I want to easily share these videos with my extended family. And that needs to be easy for them. Not everyone in my family gets excited by technical challenges. So, I need some simple authentication layer. Finally, I really want my video hosting site to render differently for authenticated and unauthenticated users. This is because I want Open Graph meta headers to give information about a page on the site, even if the page does not render restricted content to unauthenticated users. In other words, I want links to my content that are posted in social media and messaging apps to be descriptive and interesting. Like so:

Finally, I wanted to ditch the traditional server approach that I have used in the past and try to leverage AWS services to build my video hosting site serverless.

The Plan

  • Use Gatsby to generate static pages for the site. I like React. I sorta like GraphQl. Gatsby seemed like a useful thing to learn.
  • Use AWS Cognito to manage users and to allow users to login with Facebook.
  • Somehow manage a list of allowed users. Anyone could attempt to login with Facebook, but only those users on the list would actually succeed.
  • Use client-side security to show unauthenticated users a restricted version of the site. This is obviously not secure, so...
  • Restrict all access to the actual videos to authenticated users. Even if a malicious user circumvented the javascript, they still wouldn't be able to access the stuff that I actually want to keep private.

The Plan

This proved to be possible with AWS and also quite a bit more complicated than I imagined. As I worked through the problem I discovered that AWS is both over-documented and under-documented. AWS services are always completely documented with respect to available API endpoints, parameters, allowed values, etc. But it is often the purpose of a service, or how it is intended to be incorporated into larger projects that is missing. I think AWS documentation is teleologically under described. ;)

But I eventually figured it out. I figured out the difference between AWS Cognito User Pools and Identity Pools. I figured out why one should secure content with signed cookies rather than with IAM permissions. I figured out how to use Cognito's various tokens. I figured out the separation of concerns between API Gateway, Lambda, Cognito, and CloudFront. And I figured out how to manage it all with CloudFormation - a good step for learning how to operate with infrastructure as code. You can see the whole project in GitHub.

I configured my Cognito User Pool to allow sign ups and logins from a 3rd party identity provider: Facebook. Additionally, because you can configure your user pool to connect to a Lambda function on certain triggers, I was able to use a Lambda function to ensure that the user is on my allow list.

My Gatsby content is stored in S3 and distributed through a CloudFront distribution. This distribution is public. I created a second CloudFront distribution to serve the video content. This distribution requires that requests include signed cookies. The user obtains those signed cookies by making a request to an API Gateway endpoint. That request must include an Authorization header containing the access token from Cognito. The API Gateway endpoint integrates with a Lambda function that creates the signed cookies.

And the whole thing is described in a CloudFormation template.  The template includes 31 AWS resources! Many of these are access-related. Most resources need to be explicitly given access to the other resources with which they communicate. Access is granted by creating more resources.

I also used a handful of scripts to orchestrate creating the CloudFormation stack from the template and tearing it down. One particularly important function of the setup script is creating a key pair with OpenSSL, passing the private key to the signed cookie lambda function and the public key to the video CloudFront distribution.

Here are a couple diagrams:

Resource Diagram

Resource Diagram

UML Sequence Diagram

Sequence Diagram

Summary

This was definitely not the simplest way to rig up a private video hosting site. But it worked and I learned a lot about Gatsby, AWS, and infrastructure as code.