Part-3, Serverless

Private API endpoints with API Gateway Authorizers and cognito.

If you don’t know how to use cognito and API gateway, I would suggest to read through first two parts of this tutorial.

Here and Here

In this tutorial we will make a private API endpoint which will only be accessed if the user is registered with AWS cognito service.

If you look at the part-1 of this tutorial we built a lambda function for user login and ran it behind an API endpoint.

We will use this same API endpoint to get the id_token for the user which will be sent in a request header to access a private API endpoint. The response of the Login API enpoint will be like this:

{'message': "success", 
"error": False,
"success": True,
"data": {
"id_token": resp["AuthenticationResult"]["IdToken"],
"refresh_token": resp["AuthenticationResult"]["RefreshToken"],
"access_token": resp["AuthenticationResult"]["AccessToken"],
"expires_in": resp["AuthenticationResult"]["ExpiresIn"]
"token_type": resp["AuthenticationResult"]["TokenType"]
}}

Here in this response we are returning three types of tokens

You only get a refresh token when you provide your username and password. The refresh token is long lived and the id and access token are only valid for 1 hour. Refresh tokens only give you a new id and access token, never a new refresh token. If your refresh token expires, you need to authenticate again.

Lets divide this tutorial into parts for better understanding:

  1. IAM role for lambda function
  2. Lambda function for getting new id_token and access_token by using refresh token.
  3. Building a test lambda function which returns private information of user like his/her email from AWS cognito service.
  4. Attach this lambda function to an API endpoint.
  5. Create a New authorizer.
  6. Attaching AWS cognito authorizer with private API endpoint.
  7. Test and results.

IAM role for lambda function

An IAM role is an IAM entity that defines a set of permissions for making AWS service requests. IAM roles are not associated with a specific user or group. Instead, trusted entities assume roles, such as IAM users, applications, or AWS services such as Lambda.

In a brief, When a AWS service has an IAM role with policies, this service can call other resources on your behalf on AWS account or other AWS accounts.

Go to your AWS account and and click on roles.

Now, click on create role and on the new page, select AWS service and then Lambda.

Click Next:Permissions and on the drop down Menu select two existing policies.

AmazonCognitoPowerUser

CloudWatchLogsFullAccess

Click finish to complete. Now any AWS service which has this role attached to it can call any cognito related function and any cloudwatch function on your behalf.

Lambda function for getting new id_token and access_token by using refresh token.

Go to your lambda console in AWS, create a new function “refresh_access_token”.

Select runtime as python3.7 atleast.

Select the IAM role created above in Permissions section.

Click on create function to create this function. Now copy the below code and paste it the console of lambda function.

import boto3
import botocore.exceptions
import hmac
import hashlib
import base64
import json
USER_POOL_ID = 'ap-south-1_AI8aBvd7q'
CLIENT_ID = ''
CLIENT_SECRET =''
def error_message(msg):
return {'message': msg, "error": True, "success": False, "data": None}
def get_secret_hash(username):
msg = username + CLIENT_ID
dig = hmac.new(str(CLIENT_SECRET).encode('utf-8'),
msg = str(msg).encode('utf-8'), digestmod=hashlib.sha256).digest()
d2 = base64.b64encode(dig).decode()
return d2
def lambda_handler(event, context):
for field in ["username", "refresh_token"]:
if event.get(field) is None:
return error_message(f"Please provide {field} to renew tokens")
client = boto3.client('cognito-idp')
username = event["username"]
refresh_token = event["refresh_token"]

secret_hash = get_secret_hash(username)
try:
resp = client.initiate_auth(
AuthParameters={
'USERNAME': username,
'SECRET_HASH': secret_hash,
'REFRESH_TOKEN': refresh_token,

},
ClientId=CLIENT_ID,
AuthFlow='REFRESH_TOKEN_AUTH',
)
res = resp.get("AuthenticationResult")
except client.exceptions.NotAuthorizedException as e:

return error_message("Invalid refresh token or username is incorrect or Refresh Token has been revoked")

except client.exceptions.UserNotConfirmedException as e:
return error_message("User is not confirmed")
except Exception as e:
return error_message(e.__str__())
if res:
return {'message': "success",
"error": False,
"success": True,
"data": {
"id_token": res["IdToken"],
"access_token": res["AccessToken"],
"expires_in": res["ExpiresIn"],
"token_type": res["TokenType"]
}}
return

This function takes two parameteres, username and refresh_token. If the refresh_token is valid it will give you renewed id_token and access_token valid for another 1 hour. You can keep calling this API to get renewed access_token and id_token.

This id_token will be used in Cognito authorizer, which is explained later in this blog.

Building a test lambda function

Follow the above mentioned steps to create a new lambda function “test_user”. Copy the below code and paste it.

import boto3
import botocore.exceptions
import json
USER_POOL_ID = 'YOUR COGNITO USERPOOL ID'
def error_message(msg):
return {'message': msg, "error": True, "success": False, "data": None}
def lambda_handler(event, context):
for field in ["username"]:
if event.get(field) is None:
return error_message(f"Please provide {field} to renew tokens")
client = boto3.client('cognito-idp')
try:
$$if you want to get user from users access_token
# response = client.get_user(
# AccessToken=event["access_token"])

response = client.admin_get_user(
UserPoolId=USER_POOL_ID,
Username=event["username"]
)
except client.exceptions.UserNotFoundException as e:
return error_message("Invalid username ")
return {
"error": False,
"success": True,
"data": response["UserAttributes"],
'message': None,

}

This lambda functions takes in just a username and returns the user information stored on cognito. In case of invalid username, it will return an error.

This is a confidential information which we want to protect from prying eyes. We will accomplish it by executing this lambda function behind a protected API end point.

Note: This function could be anything, users details stored on dynamoDB etc.

Attach this lambda function to an API endpoint.

Please follow the steps in part-2 of this Tutorial to attach API Gateway with thse lamda functions. Once created, Please dont forget to deploy these API by clicking on the Stages tab on right hand side on API gateway console.

Create a New authorizer

On the API gateway console, Click on the Authorizers Tab.

Fill in the details and click create. This will create a new API Gateway authorizer which will use incoming token in headers to filter out the authentic requests.

You can also test this authorizer by clicking on Test. Paste the id_token you have received after a successful login from /login API.

Attaching AWS cognito authorizer with private API endpoint.

In my case the two API enpoints are (POST):

https://ek14dw8.execute-api.ap-south-1.amazonaws.com/v1/user/test-user

https://ek14dw8.execute-api.ap-south-1.amazonaws.com/v1/user/renew_refresh_token

Note: v1 is the stage in stages

Now, if you ping test_user enpoint from your Machine, it will return the users detail for a particular username.

{'error': False,
'success': True,
'data': [{'Name': 'sub', 'Value': '6e8acaa9-cc0f-43f2-9e53-3299475ab6a6'},
{'Name': 'email_verified', 'Value': 'true'},
{'Name': 'name', 'Value': 'saurav verma'},
{'Name': 'email', 'Value': 'houzier.saurav@gmail.com'}],
'message': None}

which should be protected and shouldnt be publicly accessible. Please navigate back to your API gateway console and click on Resources and then this endpoint. You will see something similar to this.

click on Method request.

No click on authorization and select CognitoAuthorizer.

Click on the right button to save your preference.

Now deploy his API Endpoint.

Now try to access this same api again and you will get an error message.

{'message': 'Unauthorized'}

Now, Try accessing this api by sending id_token under the value “token” in your headers. It should work as intended.

In the next Blog, I will try to publish the process for getting temporary aws credentials through cognito and federated identities.

Feel free to send me queries on houzier.saurav@gmail.com.

Thank you.

Analytics Vidhya

Analytics Vidhya is a community of Analytics and Data Science professionals. We are building the next-gen data science ecosystem https://www.analyticsvidhya.com

Graphicaldot (Saurav verma)

Written by

Interested in Blockchain, Python, Go, Erlang and Devops. Pretendotype vs Prototype.

Analytics Vidhya

Analytics Vidhya is a community of Analytics and Data Science professionals. We are building the next-gen data science ecosystem https://www.analyticsvidhya.com

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade