Photo by Jeremy Bezanger on Unsplash
1. Introduction
In the past couple of months, I’ve been working on a REST API that we have deployed on AWS. One of the decisions made whiles defining the API was to have it managed by Amazon’s API Gateway. It was the first time I used their gateway service, so I decided to write this short guide to help others who are considering using it. Amazon’s API Gateway is a fully managed service that makes it easy for developers to create, publish, maintain, monitor, and secure APIs. It allows developers to manage both WebSocket and RESTful APIs. Some of its functions are to manage traffic, process API calls as well as provide authorization and access control.
Source: Amazon AWS
Our goal
In this blog post, we will take a look at how to set up a simple API Gateway with a custom authorizer for REST APIs. We will not be building an actual API from scratch, but rather rely on the templates that AWS offers which will give us a functional API we can work with.
Prerequisites
- Amazon web services account
- Minor knowledge of REST APIs
-
- *
2. Defining the API gateway
Let’s start off by defining our API Gateway. To do this, login into the AWS console at https://console.aws.amazon.com and run a search for “API Gateway”. Once you’ve selected the service, click the “Create API” button to get started.
Source: AWS console
Choose your API type
Since we are setting up the API gateway for a REST API, select the “Build” button on the “REST API” card to get started. There are other options for an HTTP, Websocket, and private REST API, but we will be focusing on the regular REST API set up in this post.
Source: AWS console
Choose your protocol
We will be using the Example API provided by Amazon in our example so select the “Example API” option under the “Create new API” section of the page. Make sure the protocol selected is “REST” as well.
Under the “Example API” section, you’ll notice that the API endpoints have been defined in an OpenAPI (formerly Swagger) document. You can make some changes to the endpoints defined in the document or leave as is. For the “Settings” section of the page, leave the “Endpoint Type” value to “Regional”. Once you confirm that everything looks good, go ahead and select the “Import” button at the bottom of the page to create the API.
Source: AWS console
Method Execution
Once our API has been created, you’ll be presented with an overview of our newly created API. Under the “Resources” section, you’ll find the defined API endpoints (resources) listed. Each resource is made up of the following parts:
- Method Request
- Method Response
- Integration Request
- Integration Response
With the method request and response resources, you can define required headers as well as models that outline what to expect in incoming requests to a specific endpoint or entire API.
For the purposes of our example, we will define a required header “API-version” for the GET endpoint under “/pets”. To do this, select the GET method execution under the “/pets” resource and then click on “Method Request”.
Source: AWS console
To add our required header, click on “HTTP Request Headers” to expand it and select the “Add header” option. Let’s use “API-version” as a name and then click on the checkmark button on the side to confirm the entry. Make sure to select the “Required” option for the header. This ensures that any request to the GET “/pets” resource will fail if the header “API-version” is not present. At this level, we cannot add validation for the value being passed in as the header, but we will do this later on in the next section.
Source: AWS console
3. Creating our custom authorizer
Since our API resources have been defined, our next step is to create a custom authorizer which will authorize requests being made to our API. To do this we will create a lambda function with some base code that will check if the header value for “API-version” is set to “1” before authorizing the request.
To start off let’s create our lambda function. In the search bar at the top of the page, run a search form “lambda” and select the “Lambda” service in the results drop-down. We will then select the “Author from scratch” option, with a “Node.js 14x” runtime and give it a function name of “test-pets-authorizer”. After this select the “Create function” button at the bottom of the page.
Source: AWS console
Writing our custom authorizer code
For our authorizer logic, we will define a “Deny” and “Allow” policy that follows the model below:
{
principalId: "string",
policyDocument: {
Version: "string",
Statement: \[
{
Action: "string",
Effect: "string",
Resource: "string",
}\],
},
context: {
org: "string",
role: "string",
}
}
We will add in some logic to check for the “API-version” header with a value of “1” after which we will set the relevant policy in our response. Find the sample code below:
exports.handler = async (event) => {
const authorizedResponse = {
principalId: “pets-authorizer-test”,
policyDocument: {
Version: “2012–10–17”,
Statement: \[
{
Action: “execute-api:Invoke”,
Effect: “Allow”,
Resource: “arn:aws:execute-api:us-east-1:11111111:af5q1tcwhd/\*/GET/pets”,
}\],
},
context: {
org: “pets-org”,
role: “test”,
}
};
const nonAuthorizedResponse = {
principalId: “pets-authorizer-test”,
policyDocument: {
Version: “2012–10–17”,
Statement: \[
{
Action: “execute-api:Invoke”,
Effect: “Deny”,
Resource: “arn:aws:execute-api:us-east-1:11111111:af5q1tcwhd/\*/GET/pets”,
}\],
},
context: {
org: “pets-org”,
role: “test”,
}
};
if (event.headers\[“Api-version”\] === “1”) {
return authorizedResponse
}
return nonAuthorizedResponse;
};
Parts of the code I’d like to point out are the values set for “Action”, “Effect” and “Resource” fields. The value set for the “Action” field is an IAM role that states the action we want to perform, which in our case is to execute an API that will be invoked via the API Gateway.
Action: “execute-api:Invoke”
In the “Effect” field we define the effect of the action we want to carry out which in our case is either to accept or deny the API invocation.
Effect: “Allow”
With the “Resource” field, we will provide the resource name value for the GET action of our “/pets” resource, which in our example is “arn:aws:execute-api:us-east-1:11111111:af5q1tcwhd/*/GET/pets”.
Resource: “arn:aws:execute-api:us-east-1:11111111:af5q1tcwhd_/\*/GET/pets”_
Put the complete code in the “index.js” file and then click on deploy to make our changes live.
Source: AWS console
Linking the custom authorizer to API gateway
In this final part, we will then use our custom authorizer lambda in our API gateway resource. To do that, head back to the API Gateway page select the “Authorizers” menu option and click on the “Create New Authorizer” button to get started.
In the “Lambda Function” drop-down, select the lambda we defined in the previous step “test-pets-authorizer”, Pay particular attention to the “Type” which should be “Lambda” and the event payload which should be set to “Request”. There’s also a token-based event payload that can be used but we’ll cover that in another post. Lastly, don’t forget to set the “Identity Sources” to our header “API-version”.
Source: AWS console
Next, head back to the “Resources” section of the API Gateway page and select the GET method of the “/pets” resource. Click on “Method Request”. Click on the “Authorization” drop-down and then select the “test-pets-authorizer” under the “Request Authorizer” section. Click on the checkmark next to the drop-down to save your changes.
Source: AWS console
4. Deploying the API
To deploy our API, head back to the “Resources” section of the API Gateway page. Click on the “Actions” button on the page and select the deploy API option in the drop-down.
Source: AWS console
A modal will pop up where we will define our “Deployment stage”, which in our case we’ll call “test-dev”. Click on the “Deploy” button to deploy our API. You can add in a description of the API as well before you deploy.
Source: AWS console
After this, an “Invoke URL” will be provided on the page which we will use in making our API request.
Testing our API
To test our deployed API, we’ll make a curl request in our terminal. To do that, open up your terminal paste the text below, and press enter:
curl — location — request GET ‘https://au5q1gcwdd.execute-api.us-east-1.amazonaws.com/test-dev/pets‘ \
— header ‘Api-version: 1’
Make sure to replace the URL with your deployed URL or else the request will fail.
If everything went well you should get an HTTP response of 200 OK with the data below:
[
{
“id”: 1,
“type”: “dog”,
“price”: 249.99
},
{
“id”: 2,
“type”: “cat”,
“price”: 124.99
},
{
“id”: 3,
“type”: “fish”,
“price”: 0.99
}
]
Next, let’s test if our “Deny” policy takes effect when the “API-version” value is not set to “1”. To do this run the curl command below, making sure to replace the URL with your own deployed URL.
curl — location — request GET ‘https://au5q1gcwdd.execute-api.us-east-1.amazonaws.com/test-dev/pets' \\
— header ‘Api-version: 2’
This request should fail with the HTTP status code 403 Forbidden which the response body below:
{
“Message”: “User is not authorized to access this resource with an explicit deny”
}
5. Conclusion
We’ve explored the use of Amazon’s API Gateway service to secure a REST API by using a lambda where we defined our custom policy documents and the scenarios in which they apply to a specific resource. This gives us a glimpse into the potential that the API Gateway service holds in helping us properly secure, manage and deploy our APIs all from a singular platform that promotes ease of use and accessibility.
Who am I?
I’m a senior fullstack engineer and consultant from Ghana and working for Techspire in the Netherlands 🇳🇱. I have been in the software engineering field for almost 8 years and I still enjoy it. My main expertise lies with ReactJS and I am also proficient in Python, Java, Javascript and AWS. Do you think you have what it takes to work with us? At Techspire we’re looking for people who love technology as much as we do, and are looking to push themselves to expand their knowledge. Also, we love a good story, a good laugh, and a few beers.
By Emmanuel Lartey on January 27, 2022.
Exported from Medium on January 27, 2022.