Using Lambda at Edge and Python to Prompt Users for HTTP Authentication

As recently mentioned, Perspect makes extensive use of Lambda at Edge.  One such use is to password protect certain URLs or even the entire site.  This is useful when you have a test/dev part of the site living somewhere off the main URL or when you have the entire site protected before going live.  

We are able to secure the configured URL by prompting users to authenticate by means of HTTP authentication which we then validate against a bcrypt hashed password stored in DynamoDB.

Here’s the main code for the Lambda function:

 


from chalice import Chalice

from chalice import UnauthorizedError, ChaliceViewError

from chalicelib.responses import generate_response, prompt_http_auth

import base64, json

 

app = Chalice(app_name='http_authentication')

 

@app.lambda_function(name="http_auth")

def http_auth(event, context):

 

    import boto3

    from boto3.dynamodb.conditions import Key, Attr

    from botocore.exceptions import ClientError

 

    request = event['Records'][0]['cf']['request']

    headers = request['headers']

    verified_user = False

 

    original_host_in_request = headers['host'][0]['value']

    host_in_request = headers['host'][0]['value']

 

    if 'authorization' not in headers or headers['authorization'] == None:

        return prompt_http_auth()

 

    else:

        b64_data_full = headers['authorization'][0]['value']

        basic, b64_data = b64_data_full.split(" ")

        user_pass = base64.b64decode(b64_data).decode()

        username_in_request, password_in_request = str(user_pass).split(":")

 

    

    try:

        #  Do whatever you need to verify the user, check bcrypt password hash, etc

        verified_user = verify_user(host_in_request, username_in_request, password_in_request)

        if not verified_user:

            return prompt_http_auth()

 

    except ClientError as e:

        print("There was a ClientError trying to fetch a value from Dynamo: {}".format(e))

        response = generate_response(500, "text/html", "Internal Server Error")

 

    except UnauthorizedError as e:

        return prompt_http_auth()

 

    except Exception as e:

        response = generate_response(500, "text/html", "Internal Server Error")

 

    else:

        return request


 

Two helper functions are used for the above Lambda function:  generate_response() and prompt_http_auth(). 

 


def generate_response(code, value, data):

 

    response = {

            'status': code,

            'statusDescription': 'OK',

            'headers': {

                'cache-control': [

                    {

                        'key': 'Cache-Control',

                        'value': 'max-age=100'

                    }

                ],

                "content-type": [

                    {

                        'key': 'Content-Type',

                        'value': value

                    }

                ]

            },

            'body': data

        }

 

    return response


 

prompt_http_auth() is the HTTP response that is sent to the client is actually, well, prompt for an HTTP authentication. ?

 


def prompt_http_auth():

 

    response = {

            'status': "401",

            'statusDescription': 'Unauthorized',

            'headers': {

                'www-authenticate': [{

                'key': 'WWW-Authenticate',

                'value': 'Basic realm="Secure Area"'

                }]

            },

            'body': 'Unauthorized'

        }

 

    return response


 

An important note:  you could authenticate users in any number of ways, so create a verify_user() function of your own to validate passwords as your service requires. ?