Statische Website mit serverloser Authentifizierung

Posted on 2020/09/26 by Dirk van der Laarse

This post is derived from thisIn einem neuen Fenster öffnen guide from Douglas Duhaime. All credit to him.

TLDR: We're going with a Static HTML site from a HTML template hosted on S3 served via Cloudfront, using Lamda@Edge function to handle authorisation/access.

Setting up the static website

Create a new bucket on AWS S3

  • Turn on Static website hosting

Warnung

Make sure that public access is not blocked. Note that this will open up your bucket to the whole world.

In einem neuen Fenster öffnen

Now you can upload your .html, .css and .js files to the bucket, with public access to all files

Idee

I've found some useful templates on html5up.netIn einem neuen Fenster öffnen.

Next, you need to further configure your S3-hosted website. This guideIn einem neuen Fenster öffnen goes into more detail:

  • Register or transfer a domain
  • Get an SSL cert issued
  • Create CloudFront Distributions

Creating IAM Credentials

Idee

What follows is a very ClickOps-y way to configure services on AWS. You are more than welcome to use Infrastructure-as-Code principles instead

In order to configure Lambda to work with an S3 bucket, we’ll need to create an IAM profile that has access to the bucket. To do so, navigate back go the AWS console and click the link for the IAM service. Once there, click “Roles” in the left-hand sidebar, then “Create role”. On the next screen, under “Choose the service that will use this role” click “Lambda”, then click “Next: Permissions” at the bottom of the screen. Search for and select the “AWSLambdaExecute” role:

In einem neuen Fenster öffnen

Then click “Next: Review” at the bottom of the page. On the next screen, name your role “lambda-execute-role”, then click “Create role”:

In einem neuen Fenster öffnen

On the next page you should see that your Lambda role has been created. Once it’s created, click on the link to the role, then click “Trust relationships”, then click “Edit trust relationship” and replace the contents with the following:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": [
            "lambda.amazonaws.com",
            "edgelambda.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

This update allows the policy to interact with Lambda@Edge, which is the service that provides the authentication logic. Once that’s all set, you are ready to proceed to using this role in Lambda itself.

Creating the Authentication Layer with AWS Lambda

With all the stage-setting in place, we can now create the actual logic that will handle user-authentication. To do so, return once again to the AWS console. Once you’re there, take a look at the black navigational bar at the top of your screen. Off to the right you should be able to select the “region” in which you wish to operate. For this next step you must be in the N. Virginia region (a.k.a. us-east-1). Once you’re in the N. Virginia region, click the link for “Lambda”.

Lambda is a piece of AWS’s “serverless” stack that allows one to run serverside code without having to build, run, and maintain a whole server. We’ll use it to run our authentication logic. On the Lambda landing page, click the orange button that says “Create a function”:

On the next page, keep “Author from scratch” selected. Name the function “authentication”, select Node.js as the runtime, select “Choose an existing role” , and select “lambda-execute-role” as the existing role to use (this is the role we just created in the IAM console):

In einem neuen Fenster öffnen

Finally, click “Create function” at the bottom of the page. Scroll down to the code editor and paste the following snippet into the input field:

exports.handler = (event, context, callback) => {

  // Get the request and its headers
  const request = event.Records[0].cf.request;
  const headers = request.headers;

  // Specify the username and password to be used
  const user = 'user';
  const pw = 'password';

  // Build a Basic Authentication string
  const authString = 'Basic ' + new Buffer(user + ':' + pw).toString('base64');

  // Challenge for auth if auth credentials are absent or incorrect
  if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
    const response = {
      status: '401',
      statusDescription: 'Unauthorized',
      body: 'Unauthorized',
      headers: {
        'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
      },
    };
    callback(null, response);
  }

  // User has authenticated
  callback(null, request);
};

This snippet exports a single function that takes as input the three default arguments Lambda provides to Node.js functions docsIn einem neuen Fenster öffnen. The function then pulls out the user’s HTTP request and its headers, specifies the correct username and password, and checks to see if the user’s request contained the username and password in its authentication headers. If not, it prompts the user to authenticate; if so it allows the user into the site.

After defining the function, click "Deploy"”

Next, under the “Designer” section toward the top of the page, click “CloudFront”, which will move CloudFront into the triggers portion of the displayed diagram:

In einem neuen Fenster öffnen

If you then scroll down a bit, you’ll see a section titled “Configure triggers”. Select your CloudFront distribution’s ID under the “Distribution” selector (this is displayed under the ID column in your CloudFront distribution list), make sure you select “Viewer request” as the CloudFront event that will trigger the function defined above, and click the box that says “Enable trigger and replicate”:

In einem neuen Fenster öffnen

If you then try to access the address specified under the “Domain Name” column in your CloudFront distribution list [example], you’ll be prompted for a username and password:

In einem neuen Fenster öffnen

If you type “user” and “password” as the credentials (or whichever values you set as the username and password in your lambda password), you’ll see the site itself!

Changing from Basic Auth to Bearer Auth

So this is where I got stuck or "ran out of time". The best way to implement this is using Authentication Cookies with Lambda@EdgeIn einem neuen Fenster öffnen.

Mitwirkende: Dirk van der Laarse