by Miriam Greis and Philipp Krauss

In this tutorial, we will give you a basic understanding of how an AWS Lambda authorizer works and how you can pass information from it to an Amazon API Gateway and other Lambda functions. To authorize users, we use a federated login, namely Google Sign-in, to produce a small full-working example. If you finish the tutorial, your static Google Sign-in page will display a personal greeting for the signed-in user by making an HTTP call to your API. The diagram provides an overview of all services you will build and use throughout this tutorial.

Please be aware that this tutorial does not aim to build a fully secure authorization, but instead provides some basic knowledge about AWS Lambda authorizers and how to connect different services in AWS.

This tutorial is made up of three steps:

First, we set the scene and explain how to create a basic Lambda function, an API at Amazon API Gateway, and a static website with a Google Sign-in button.

Second, we explain how to create and wire up an AWS Lambda authorizer.

Third, we show how to transfer information from the AWS Lambda authorizer to the Amazon API Gateway and other Lambda functions.

Prerequisites

If you have never worked with AWS Lambda, Amazon API Gateway, and S3 before, you might want to complete this tutorial first, where we focus more on the basics of creating an AWS Lambda function and how to setup an API Gateway. We need several of the services created in the other tutorial here too and will refer to it at the specific steps.

To complete this tutorial, you also need several accounts and software installed. You will specifically need the following:

AWS Account

To carry out this tutorial, you need an AWS account. If you do not yet have one, you can find more details on how to create it in the AWS documentation. All of the services that we use in this tutorial are free tier eligible, so you can use them free of charge.

In this tutorial, we used the Frankfurt Region (eu-central-1) for all our services, since it is closest to our location. If you are using a different region, please be aware that links and input might change for you in some of the steps.

Python

You will use Python to program the AWS Lambda authorizer in this tutorial. Thus, you need Python to be installed on your computer to create a valid deployment package for your AWS Lambda function. You additionally need to install two Python libraries (google-auth and requests). If you do not have Python or the libraries installed yet, do not worry about it, we will get back to this later.

Text File for Notes

There are several values from this tutorial that you will need again in later steps of the tutorial. To avoid searching for these values later, we advise you to create a text file with notes containing the following values. Fill them in when we point you to it during the tutorial:

1. (API Gateway) Invoke URL:

2. S3 Endpoint URL:

3. Google Client ID:

4. Google ID Token:



Step 1: Setting up the Scene

In this step, you will setup the environment for building an AWS Lambda authorizer. We mainly need an API at the Amazon API Gateway and a Lambda function that the API invokes. We additionally need a website with a Google Sign-in button, which we host in an S3 bucket.

Amazon API Gateway and AWS Lambda

We explain how to set up an API at the Amazon API Gateway without access restrictions and an easy AWS Lambda function in our preceding tutorial. Please complete step one of the other tutorial.

After completion of step 1, you should have created the following services:

a Lambda function called simple-hello-world

an API called simple-hello-api

and you should be able to successfully call the invoke URL of your API in the browser. Additionally, you should have noted down the Invoke URL of your API.

Google Sign-In Page

We explain how to set up a static website with a Google Sign-in button in our preceding tutorial. Please complete these two parts of step 3: Configuring the Google Login Button and Putting It Together. You can skip the other parts of step 3 as we do not need them for this tutorial.

After completion of the two parts of step 3, you should have created the following service:

an S3 bucket with a static website that displays a Google Sign-in button

and you should be able to successfully access this website in your browser and sign in to Google. Additionally, you should have noted down the S3 Endpoint URL , the Google Client ID , and your Google ID Token .

Step 2: Creating an AWS Lambda Authorizer

In this step, we explain how to create an AWS Lambda authorizer and connect it to your API. You will also modify your index.html to create a fully working example where you call your API on your Google Sign-in page.

What is an AWS Lambda Authorizer?

An AWS Lambda authorizer is a Lambda function that is registered at the Amazon API Gateway as an authorizer for your API. A Lambda function that serves as an authorizer expects a specific JSON input, which is automatically passed from the API Gateway:

{ "authorizationToken": <token>, "methodArn": <methodArn>, "type": "TOKEN" } { "authorizationToken": <token>, "methodArn": <methodArn>, "type": "TOKEN" }

This information can be accessed via the event variable.

As output, the API Gateway expects an authentication response from the Lambda function. The authentication response should contain the principal ID of the user and a policy document. In this policy document, you can set the permissions to allow or deny access to the API and other AWS services.

In the API Gateway, you can create an authorizer at the Authorizers section. You however need to enter the name of a Lambda function when specifying the options. Therefore, you first need to write the Lambda function.

Writing the Lambda Function

Navigate to AWS Lambda and start creating a function. There are blueprints for authorizers that you might want to use in the future, but for this tutorial they are way too complex. Therefore, select “Author from scratch”.

Call your Lambda function simple-lambda-authorizer and select “Python 2.7” as runtime. As a role, you can select the role you created for your simple-hello-lambda function which is the simple-lambda-role. If you are ready to proceed, click on “Create function”. Your function will be created containing example code.

Writing the Python Code

Now you need to write the code for your AWS lambda authorizer. As you will need external Python libraries to verify and decode the Google ID token, you cannot use the inline editor. Therefore open an editor of your choice, create a file called simple-lambda-authorizer.py, and save it in a project directory of your choice. Put the following code into your file:

from __future__ import print_function from google. auth . transport import requests from google. oauth2 import id_token def generatePolicy ( principalId , effect , methodArn ) : authResponse = { } authResponse [ 'principalId' ] = principalId if effect and methodArn: policyDocument = { 'Version' : '2012-10-17' , 'Statement' : [ { 'Sid' : 'FirstStatement' , 'Action' : 'execute-api:Invoke' , 'Effect' : effect , 'Resource' : methodArn } ] } authResponse [ 'policyDocument' ] = policyDocument return authResponse def lambda_handler ( event , context ) : try : # Verify and get information from id_token idInformation = id_token. verify_oauth2_token ( event [ 'authorizationToken' ] , requests. Request ( ) , 'YOUR_CLIENT_ID.apps.googleusercontent.com' ) print ( idInformation ) # Deny access if the account is not a Google account if idInformation [ 'iss' ] not in [ 'accounts.google.com' , 'https://accounts.google.com' ] : return generatePolicy ( None , 'Deny' , event [ 'methodArn' ] ) # Get principalId from idInformation principalId = idInformation [ 'sub' ] except ValueError as err: # Deny access if the token is invalid print ( err ) return generatePolicy ( None , 'Deny' , event [ 'methodArn' ] ) return generatePolicy ( principalId , 'Allow' , event [ 'methodArn' ] ) from __future__ import print_function from google.auth.transport import requests from google.oauth2 import id_token def generatePolicy(principalId, effect, methodArn): authResponse = {} authResponse['principalId'] = principalId if effect and methodArn: policyDocument = { 'Version': '2012-10-17', 'Statement': [ { 'Sid': 'FirstStatement', 'Action': 'execute-api:Invoke', 'Effect': effect, 'Resource': methodArn } ] } authResponse['policyDocument'] = policyDocument return authResponse def lambda_handler(event, context): try: # Verify and get information from id_token idInformation = id_token.verify_oauth2_token( event['authorizationToken'], requests.Request(), 'YOUR_CLIENT_ID.apps.googleusercontent.com') print(idInformation) # Deny access if the account is not a Google account if idInformation['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: return generatePolicy(None, 'Deny', event['methodArn']) # Get principalId from idInformation principalId = idInformation['sub'] except ValueError as err: # Deny access if the token is invalid print(err) return generatePolicy(None, 'Deny', event['methodArn']) return generatePolicy(principalId, 'Allow', event['methodArn'])

Make sure to replace the ‘YOUR_CLIENT_ID.apps.googleusercontent.com’ placeholder with your Google Client ID . The code uses the google-auth library to verify and decode the ID token given as authorizationToken and then generates a policy for the user. If the ID token is not a Google ID token or is invalid, the authorizer returns a policy denying access to the API. In the other case, the authorizer will return a policy allowing the user to invoke the API. Additionally, the code either prints the error in case of an invalid token or the extracted ID information in case of a valid token.

Creating and Uploading a Deployment Package

To create a deployment package for your Lambda function, you have to install all necessary libraries directly into your project directory. For this tutorial, you need the two libraries google-auth and requests. Then zip the content of your project directory (not the directory itself) and name it simple-lambda-authorizer.zip. If you need further information on how to install Python, install libraries or create the zip file, follow the detailed description provided in the AWS documentation.

In the AWS console, scroll down to the function box and change the dropdown menu from “Edit code inline” to “Upload a .ZIP file”. Click on upload to select your simple-lambda-authorizer.zip. Additionally, you need to adapt the Handler information. Make sure it is named <python-file-name>.<lambda-handler-function-name>. For this tutorial, the name should be simple-lambda-authorizer.lambda_handler. If this information is misconfigured, you will get an error message during the next step. Click on the “Save” button in the top right corner to load your code into the inline editor.

Testing your Lambda Function

You can now test your Lambda function to make sure it works. First, let’s test your Lambda function with an invalid token. Click on the dropdown menu next to the “Test” button and click on “Configure test event”. Make sure that “Create new test event” is selected. Then search for the Event template “API Gateway Authorizer” in the dropdown menu and select it. Finally, enter the name invalidTokenTest. For this test, you can just use the input created automatically by the template.

Click on “Create” at the bottom of the dialog and then press “Test” in the top right corner to test your function. You should now see a successful execution result. If not, make sure that your deployment package contains all necessary libraries and that you correctly specified the handler. Click on “Details” to see the logs of your execution.

As expected, the function returns a policy document with the deny effect as you passed an invalid token and the log output contains the printed error message that the token contained the wrong number of segments.

Let’s now configure a second test event with a valid token. Click again on the dropdown menu next to the “Test” button and select “Configure test event”. In the dialog, select “Create new test event”. You will now automatically use your first test event as event template. As name for the new event, enter validTokenTest. Copy your Google ID Token from your text file and pass it as authorizationToken.

Click “Create” and “Test”. As a result of your test, the execution should not only succeed, but the created policy should allow the API usage and your account information will be printed in the log output. If not, your ID token might be expired, so just refresh your Sign-In page to get a new ID token and change your test event.

Creating the Authorizer

As you finished creating your Lambda function for the authorizer, navigate back to the API Gateway and your simple-hello-api. Navigate to the “Authorizers” sub menu, click “Create New Authorizer” and fill in the necessary information. As name pick simple-lambda-authorizer. When you start entering the name of your Lambda function in the corresponding text field, you should then be able to select your simple-lambda-authorizer. The only other property you have to specify is the Token Source. The token source is the name of the request header expected from your API Gateway to contain the token to authorize the user. Name it Authorization.

Press “Create” and in the following dialog click “Grant & Create” as you have to grant your API Gateway the permissions to execute your Lambda function. You can now test your AWS Lambda authorizer by clicking on “Test” providing different values for the Authorization header.

Although you created the authorizer, it is not yet hooked up to your simple-hello-api. To hook up your authorizer to your API, navigate to “Resources” -> “GET” and click on “Method Request”. Edit the Authorization settings and select your simple-lambda-authorizer. Press the checkmark to confirm. If you cannot select your authorizer in the dropdown menu, just reload the page and it should appear.

To reflect the changes in your API, you need to deploy your API again. To do this, select “Actions” -> “Deploy API”. Wait a few second to make sure that your API was deployed again. If you now try to access your Invoke URL , you will get a message that you are unauthorized.

Putting Things Together

You will now put together a working example. Thus, locally change your index.html to an updated version:

< html lang = "en" > < head > < meta name = "google-signin-scope" content = "profile email" > < meta name = "google-signin-client_id" content = "YOUR_CLIENT_ID.apps.googleusercontent.com" > < script src = "https://apis.google.com/js/platform.js" async defer>< / script > < / head > < body > < div class = "g-signin2" data-onsuccess = "onSignIn" data-theme = "dark" >< / div > < p id = "content" >< / p > < script > function onSignIn(googleUser) { // Useful data for your client-side scripts: var profile = googleUser.getBasicProfile(); console.log("ID: " + profile.getId()); // Don't send this directly to your server! console.log("Full Name: " + profile.getName()); console.log("Given Name: " + profile.getGivenName()); console.log("Family Name: " + profile.getFamilyName()); console.log("Image URL: " + profile.getImageUrl()); console.log("Email: " + profile.getEmail()); // The ID token you need to pass to your backend: var id_token = googleUser.getAuthResponse().id_token; console.log("ID Token: " + id_token); var request = new XMLHttpRequest(); request.open("GET","INVOKE_URL", true); request.setRequestHeader("Authorization", id_token); request.onload = function() { var text = request.responseText; document.getElementById("content").innerHTML = text; }; request.send(); } < / script > < / body > < / html > <html lang="en"> <head> <meta name="google-signin-scope" content="profile email"> <meta name="google-signin-client_id" content="YOUR_CLIENT_ID.apps.googleusercontent.com"> <script src="https://apis.google.com/js/platform.js" async defer></script> </head> <body> <div class="g-signin2" data-onsuccess="onSignIn" data-theme="dark"></div> <p id="content"></p> <script> function onSignIn(googleUser) { // Useful data for your client-side scripts: var profile = googleUser.getBasicProfile(); console.log("ID: " + profile.getId()); // Don't send this directly to your server! console.log("Full Name: " + profile.getName()); console.log("Given Name: " + profile.getGivenName()); console.log("Family Name: " + profile.getFamilyName()); console.log("Image URL: " + profile.getImageUrl()); console.log("Email: " + profile.getEmail()); // The ID token you need to pass to your backend: var id_token = googleUser.getAuthResponse().id_token; console.log("ID Token: " + id_token); var request = new XMLHttpRequest(); request.open("GET","INVOKE_URL", true); request.setRequestHeader("Authorization", id_token); request.onload = function() { var text = request.responseText; document.getElementById("content").innerHTML = text; }; request.send(); } </script> </body> </html>

The updated version makes a simple HTTP request calling your API with the request header “Authorization”. This is the name of the header that we specified as token source when creating the authorizer at the API Gateway. Make sure to replace the “YOUR_CLIENT_ID.apps.googleusercontent.com” placeholder, with your Google Client ID and the “INVOKE_URL” placeholder with your Invoke URL . In the AWS console, navigate to your S3 bucket and upload the new version of your index.html. Afterwards make it public again, otherwise you will get an error when later reloading your sign-in page.

As last step to make your API call work, you have to enable CORS (Cross-Origin Resource Sharing) for your API. Navigate back to the API Gateway and your simple-hello-api. Click on “Resources” and then “Actions” and select “Enable CORS” from the drowndown menu. As “Access-Control-Allow-Header” enter ‘Autorization’ and as Access-Control-Allow-Origin, enter your S3 Endpoint URL in single quotes. Make sure that it does not have a slash at its end.

Click on “Enable CORS and replace existing CORS headers” and then “Yes, replace existing values”. To reflect your changes, redeploy your API via “Actions” -> “Deploy API”.

If you now reload your sign-in page, you should see the message “Hello from Lambda!”.

Step 3: Passing on Information Between Services

You can now show the result of your simple-hello-world Lambda function in your client application, but what we were really interested in was how to show content related to our users. In this step, you will therefore modify your current API and Lambda to personally greet the user with their name. At the moment, you only have the information about who the user is in your simple-lambda-authorizer, but you need this information in your simple-hello-world Lambda function to return a user-specific greeting.

From AWS Lambda Authorizer to API Gateway

First, you need to adapt your AWS Lambda authorizer to make the user-specific information available in your API Gateway. To do this, you can attach a context variable to your authentication response that can contain any key value pairs you specify. In our tutorial, we aim to know the name of the user for the personal greeting and therefore add a context variable with a key called “name”.

In the inline editor of your simple-lambda-authorizer, change your code to the following code:

from __future__ import print_function from google. auth . transport import requests from google. oauth2 import id_token def generatePolicy ( principalId , name , effect , methodArn ) : authResponse = { } authResponse [ 'principalId' ] = principalId if effect and methodArn: policyDocument = { 'Version' : '2012-10-17' , 'Statement' : [ { 'Sid' : 'FirstStatement' , 'Action' : 'execute-api:Invoke' , 'Effect' : effect , 'Resource' : methodArn } ] } authResponse [ 'policyDocument' ] = policyDocument if name is not None : context = { 'name' : name } authResponse [ 'context' ] = context return authResponse def lambda_handler ( event , context ) : try : # Verify and get information from id_token idInformation = id_token. verify_oauth2_token ( event [ 'authorizationToken' ] , requests. Request ( ) , 'YOUR_CLIENT_ID.apps.googleusercontent.com' ) print ( idInformation ) # Deny access if the account is not a Google account if idInformation [ 'iss' ] not in [ 'accounts.google.com' , 'https://accounts.google.com' ] : return generatePolicy ( None , None , 'Deny' , event [ 'methodArn' ] ) # Get principalId and name from idInformation principalId = idInformation [ 'sub' ] name = idInformation [ 'name' ] except ValueError as err: # Deny access if the token is invalid print ( err ) return generatePolicy ( None , None , 'Deny' , event [ 'methodArn' ] ) return generatePolicy ( principalId , name , 'Allow' , event [ 'methodArn' ] ) from __future__ import print_function from google.auth.transport import requests from google.oauth2 import id_token def generatePolicy(principalId, name, effect, methodArn): authResponse = {} authResponse['principalId'] = principalId if effect and methodArn: policyDocument = { 'Version': '2012-10-17', 'Statement': [ { 'Sid': 'FirstStatement', 'Action': 'execute-api:Invoke', 'Effect': effect, 'Resource': methodArn } ] } authResponse['policyDocument'] = policyDocument if name is not None: context = { 'name': name } authResponse['context'] = context return authResponse def lambda_handler(event, context): try: # Verify and get information from id_token idInformation = id_token.verify_oauth2_token( event['authorizationToken'], requests.Request(), 'YOUR_CLIENT_ID.apps.googleusercontent.com') print(idInformation) # Deny access if the account is not a Google account if idInformation['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: return generatePolicy(None, None, 'Deny', event['methodArn']) # Get principalId and name from idInformation principalId = idInformation['sub'] name = idInformation['name'] except ValueError as err: # Deny access if the token is invalid print(err) return generatePolicy(None, None, 'Deny', event['methodArn']) return generatePolicy(principalId, name, 'Allow', event['methodArn'])

Remember to replace the ‘YOUR_CLIENT_ID.apps.googleusercontent.com’ placeholder with your Google Client ID . Do not forget to press “Save” to reflect your changes.

From API Gateway to Lambda Function

Switch to the API Gateway of your simple-hello-api to define a mapping template. Navigate to “Resources” -> “GET” and then click on “Integration Request”. Here you can now specify a body mapping template. Select “When there are no templates defined (recommended)” and add a mapping template called application/json. Then scroll down to add the actual content of the template.

Your template should pass the name from the context variable as “name”:

{ "name": "$context.authorizer.name" } { "name": "$context.authorizer.name" }

Click on “Save” and then on “Actions” -> “Deploy API” to reflect your changes.

If you are interested in what other information the $context variable holds, check out the API Gateway Mapping Template Reference.

Access Information in your Lambda Function

Finally, you have to modify your simple-hello-world Lambda function to access the name you passed on and return a personal greeting. Navigate to the inline editor of your simple-hello-world function and change the code to include the name of the user from the event variable:

def lambda_handler ( event , context ) : return 'Hello ' + event [ 'name' ] + '!' def lambda_handler(event, context): return 'Hello ' + event['name'] + '!'

Press “Save” in the top right corner to reflect your change. Afterwards reload your sign-in page. Your sign-in page should now display a personal greeting.

Summary

In this tutorial, we showed you how to implement an AWS Lambda authorizer and pass on information between the authorizer, the API Gateway and further Lambda functions. This is just one way to authorize users at your API Gateway, so make sure to check other options before deciding which is the best option for your use case.

We hope that you enjoyed our tutorial and learned some new AWS skills. If you have any questions or comments, feel free to let us know.