Search code examples
node.jsamazon-web-servicesaws-lambdaamazon-cloudfrontaws-lambda-edge

Basic HTTP Authentication for CloudFront with Lambda@Edge in NodeJS


I am working on protecting a static website with a username and password. I created a basic HTTP Authentication for CloudFront with Lambda@Edge in NodeJS.

I am completely new to NodeJS. Initially, I had the user and the password hardcoded, and this worked properly.

'use strict';
exports.handler = (event, context, callback) => {

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

    // Configure authentication
    const authUser = 'user';
    const authPass = 'pass';

    // Construct the Basic Auth string
    const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');

    // Require Basic authentication
    if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
        const body = 'Unauthorized';
        const response = {
            status: '401',
            statusDescription: 'Unauthorized',
            body: body,
            headers: {
                'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
            },
        };
        callback(null, response);
    }

    // Continue request processing if authentication passed
    callback(null, request);
};

I stored my secrets in SSM and I want to retrieve them through the function. I tested this piece of code separately in Lambda and it returns the credentials as espected.

'use strict';
exports.handler = async (event, context, callback) => {
    const ssm = new (require('aws-sdk/clients/ssm'))();
    let userData = await ssm.getParameters({Names: ['website-user']}).promise();
    let userPass = await ssm.getParameters({Names: ['website-pass']}).promise();
    let user = userData.Parameters[0].Value;
    let pass = userPass.Parameters[0].Value;
    return {user, pass};
};

But when I stitch the two, I get 503 ERROR The request could not be satisfied. Does anyone know what I might be doing wrong? Thank you for your help!

The complete code:

'use strict';

exports.handler = async (event, context, callback) => {
    const ssm = new (require('aws-sdk/clients/ssm'))();
    let userData = await ssm.getParameters({Names: ['website-user']}).promise();
    let userPass = await ssm.getParameters({Names: ['website-pass']}).promise();
    let user = userData.Parameters[0].Value;
    let pass = userPass.Parameters[0].Value;

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

    // Construct the Basic Auth string
    let authString = 'Basic ' + new Buffer(user + ':' + pass).toString('base64');

    // Require Basic authentication
    if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
        const body = 'Unauthorized';
        const response = {
            status: '401',
            statusDescription: 'Unauthorized',
            body: body,
            headers: {
                'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
            },
        };
        callback(null, response);
    }

    // Continue request processing if authentication passed
    callback(null, request);
};

Solution

  • After reading about promises I was able to resolve the error. Here's the solution that worked for me:

    'use strict';
    
    var AWS = require('aws-sdk');
    AWS.config.update({ region: 'us-east-1' });
    var ssm = new AWS.SSM();
    
    function getParameter(param) {
      return new Promise(function (success, reject) {
        ssm.getParameter(param, function (err, data) {
          if (err) {
            reject(err);
          } else {
            success(data);
          }
        });
      });
    };
    
    exports.handler =  (event, context, callback) => {
        let request = event.Records[0].cf.request;
        let headers = request.headers;
        let user = {Name: 'user-path', WithDecryption: false};
        let pass = {Name: 'password-path', WithDecryption: false};
        let authUser;
        let authPass;
        var promises = [];
        promises.push(getParameter(user), getParameter(pass));
        
        Promise.all(promises)
        .then(function (result) {
          authUser = result[0].Parameter.Value;
          authPass = result[1].Parameter.Value;
          console.log(authUser);
          console.log(authPass);
          let authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');
          if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
            const body = 'Unauthorized';
            const response = {
              status: '401',
              statusDescription: 'Unauthorized',
              body: body,
              headers: {
              'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
            },
        };
        callback(null, response);
    }
    
      // Continue request processing if authentication passed
      callback(null, request);
    })
        .catch(function (err) {
          console.log(err);
        });
    };