How To Integrate Amazon Cognito with Ethereum Blockchain: A Step-by-Step Guide

Written by yi | Published 2021/07/01
Tech Story Tags: blockchain | aws | serverless | aws-cognito | user-authentication | ethereum | dapp | hackernoon-top-story

TLDRvia the TL;DR App

When you build a blockchain DApp, you can use AWS services with custom logic, such as monitoring and troubleshooting your contract event logs using AWS CloudWatch. With Amazon Cognito, you can deliver temporary, limited-privilege credentials to your application to access AWS resources.
In this article, We will introduce a cryptographically secure authentication flow using the Amazon Cognito enhanced flow with the MetaMask extension and Web3.

Solution overview

By the end of this article, we will have a website that allows users to log in using MetaMask and having access to our Amazon API Gateway APIs with IAM authorization.
here is a demo we are going to build:

The auth flow includes the following steps:

  1. User sign-in with MetaMask.
  2. Get nonce from DynamoDB. Generate one if nonce doesn’t exist.
  3. Sign messages off-chain with the private key of the current account.
  4. Verify signature with Web3.
  5. Get developer authenticated identities.
  6. Get credentials for the returned developer authenticated identity ID.
  7. Signing AWS requests with signature version 4.
  8. Control access to AWS API Gateway APIs with IAM authorization.
The following diagram illustrates the auth flow.

Prerequisites

Let’s begin!

React frontend DApp

I created a react frontend DApp for this article. The project can be seen in my GitHub repository.
When users click the login button on the login page, they need to connect to their MetaMask Wallet and get the current MetaMask account’s public address. The MetaMask connection function would look like the following:
Once we have the public address, we will use it to get the nonce by the public address. If no nonce is found in DynamoDB, we will call signup API to create a cryptographically strong pseudo-random data as the nonce and save it to the DynamoDB table.
Next, we use 
web3.eth.personal.sign
 to sign string message with nonce:
Running the above code snippet will prompt a Signature Request popup for signing the message in MetaMask.
When the user clicks the sign button in the popup window, the callback function of 
web3.eth.personal.sign
then returns a signature.
After this, the frontend makes an authentication API call, passing signature, and public address. if the tokens and signature are verified in the backend process, the frontend will be given the AWS STS credentials (
accessKeyId
,
secretAccessKey
,
sessionToken
).
After the authentication process is completed, we can use AWS STS credentials to sign our requests using Signature Version 4, then connect to the API Gateway endpoints secured using AWS IAM permissions.
The sample code of the 
login
 page would look like the following:
Now we have created our frontend DApp, let’s move on to the backend.

Handling authentication and authorization in serverless application

As a heads up, I will assume you already have Serverless Framework installed and are familiar with it. If you are not, take a look at the Get started with Serverless Framework Open Source & AWS guide.

Creating nonce and Geting nonce

Step2: Get nonce from DynamoDB; generate one if nonce doesn’t exist.
First of all, we need to store nonce and user’s public address in DynamoDB; we will create a 
user
 table where each item is uniquely identified by 
address
.
Let’s navigate into the 
serverless.yml
file inside your serverless application folder. First, add the following lines to the 
Resources
 section:
Now we have the user table defined, let’s build an API Gateway REST API with Lambda functions to get/set nonce by user’s public address.
Add the following config to the functions section,
The code snippets from the 
functions
 section defines 2 API endpoints 
getNonce
 & signup (create nonce), the frontend can send requests to Lambda functions via the 2 API Gateway HTTPS endpoints.
Let's create related Lambda functions: 
getNonce
 and 
signup
 .
getNonce
 lambda would look like the following:
and 
signup
 Lambda function would look like the following:
Next, We will create an IAM policy that allows 
Get/Put/update
 access to the 
user
 DynamoDB table. Add the following lines to the 
provider
 section of 
serverless.yml
:

Building authentication flow

First, we need to create a Cognito Identity Pool associated with the developer provider 
my.ether.login 
.
With developer authenticated identities, you can register and authenticate users via your own existing authentication process, while still using Amazon Cognito to synchronize user data and access AWS resources.
Now, let’s create resources: (
CognitoIdentityPool
CognitoAuthorizedRole
unAuthorizedRole
CognitoIdentityPoolRolesMapping
), copy the following lines to 
Resources
 the section looks like this:
Next, it’s time to build our serverless authentication flow. The flow covers the following steps:
  1. Verify signature with Web3.
  2. Get developer authenticated identities.
  3. Get credentials for the returned developer authenticated identity ID.
Signature verification
When the user logins to the site by sending a POST request, the first step is to verify that the user has correctly signed the nonce. we can use 
web3.personal.ecRecover
 function to verify a signed message, 
ecRecover
 outputs the 
signing_address
 used to sign the same message. If the 
signing_address
 matches our address from the request body, then the user who made the request successfully proved their ownership of the public address.
Developer authenticated identities
Once the signature is verified, we will implement our own identity provider in the Lambda function, and the identity provider function should return a response object containing 
identityId
 and 
token
 as attributes.
Getting credentials for the authenticated identity ID
After we establish identity ID and token, we can then call 
getCredentialsForIdentity
 to return STS credentials for the provided identity ID.
To prevent the user from logging in with the same signature every time, we will change nonce (
updateNonce
) at the bottom of 
login
 Lambda function. The full sample code of auth flow would look like below:
login
 API endpoint in 
functions
 section of 
serverless.yml
 :
login
 Lambda function:
CONGRATULATIONS! We have just completed building our blockchain authentication flow.
Now, We can create HTTP Endpoints 
/hello
 with
AWS_IAM
Authorizers. After login successfully, the user can submit the IAM user’s access keys to be authenticated to invoke our Lambda Function.
Copy following lines to 
functions
 section of 
serverless.yml
:
and
hello
Lambda function would look like below:
Then use 
aws4fetch
 in React frontend to sign the hello request with signature version 4 and 
fetch()
 .
Deploy the serverless application and 
npm start
 react app, log in with MetaMask. We should see the “Welcome your IAM role is authorized” message on the dashboard page.

Conclusion

This article introduced the approach to authenticating users with MetaMask and Cognito Identity Pool developer provider. I showed you how to invoke AWS API gateway endpoints with AWS_IAM authorizer. I hope you have found this article useful. You can find the complete project in my GitHub repository.

Published by HackerNoon on 2021/07/01