AWS Architecture Blog
dApp authentication with Amazon Cognito and Web3 proxy with Amazon API Gateway
If your decentralized application (dApp) must interact directly with AWS services like Amazon S3 or Amazon API Gateway, you must authorize your users by granting them temporary AWS credentials. This solution uses Amazon Cognito in combination with your users’ digital wallet to obtain valid Amazon Cognito identities and temporary AWS credentials for your users. It also demonstrates how to use Amazon API Gateway to secure and proxy API calls to third-party Web3 APIs.
In this blog, you will build a fully serverless decentralized application (dApp) called “NFT Gallery”. This dApp permits users to look up their own non-fungible token (NFTs) or any other NFT collections on the Ethereum blockchain using one of the following two Web3 providers HTTP APIs: Alchemy or Moralis. These APIs help integrate Web3 components in any web application without Blockchain technical knowledge or access.
Solution overview
The user interface (UI) of your dApp is a single-page application (SPA) written in JavaScript using ReactJS, NextJS, and Tailwind CSS.
The dApp interacts with Amazon Cognito for authentication and authorization, and with Amazon API Gateway to proxy data from the backend Web3 providers’ APIs.
Architecture diagram
Prerequisites
- Install Node.js, yarn, or npm, and the AWS Serverless Application Model Command Line Interface (AWS SAM CLI) on your computer.
- Have an AWS account and the proper AWS Identity and Access Management (IAM) permissions to deploy the resources required by this architecture.
- Install a digital wallet extension on your browser and connect to the Ethereum blockchain. Metamask is a popular digital wallet.
- Get an Alchemy account (free) and an API Key for the Ethereum blockchain. Read the Alchemy Quickstart guide for more information.
- Sign up for a Moralis account (free) and API Key. Read the Moralis Getting Started documentation for more information.
Using the AWS SAM framework
You’ll use AWS SAM as your framework to define, build, and deploy your backend resources. AWS SAM is built on top of AWS CloudFormation and enables developers to define serverless components using a simpler syntax.
Walkthrough
Clone this GitHub repository.
Build and deploy the backend
The source code has two top level folders:
- backend: contains the AWS SAM Template
template.yaml
. Examine thetemplate.yaml
file for more information about the resources deployed in this project. - dapp: contains the code for the dApp
1. Go to the backend
folder and copy the prod.parameters.example
file to a new file called prod.parameters
. Edit it to add your Alchemy and Moralis API keys.
2. Run the following command to process the SAM template (review the sam build Developer Guide).
sam build
3. You can now deploy the SAM Template by running the following command (review the sam deploy Developer Guide).
sam deploy --parameter-overrides $(cat prod.parameters) --capabilities CAPABILITY_NAMED_IAM --guided --confirm-changeset
4. SAM will ask you some questions and will generate a samconfig.toml
containing your answers.
You can edit this file afterwards as desired. Future deployments will use the .toml file and can be run using sam deploy
. Don’t commit the samconfig.toml
file to your code repository as it contains private information.
Your CloudFormation stack should be deployed after a few minutes. The Outputs
should show the resources that you must reference in your web application located in the dapp
folder.
Run the dApp
You can now run your dApp locally.
1. Go to the dapp
folder and copy the .env.example
file to a new file named .env
. Edit this file to add the backend resources values needed by the dApp. Follow the instructions in the .env.example
file.
2. Run the following command to install the JavaScript dependencies:
yarn
3. Start the development web server locally by running:
yarn dev
Your dApp should now be accessible at http://localhost:3000 (see Figure 2).
Deploy the dApp
The SAM template creates an Amazon S3 bucket and an Amazon CloudFront distribution, ready to serve your Single Page Application (SPA) on the internet.
You can access your dApp from the internet with the URL of the CloudFront distribution. It is visible in your CloudFormation stack Output
tab in the AWS Management Console, or as output of the sam deploy
command.
For now, your S3 bucket is empty. Build the dApp for production and upload the code to the S3 bucket by running these commands:
cd dapp
yarn build
cd out
aws s3 sync . s3://${BUCKET_NAME}
Replace ${BUCKET_NAME}
by the name of your S3 bucket.
Automate deployment using SAM Pipelines
SAM Pipelines automatically generates deployment pipelines for serverless applications. If changes are committed to your Git repository, it automates the deployment of your CloudFormation stack and dApp code.
With SAM Pipeline, you can choose a Git provider like AWS CodeCommit, and a build environment like AWS CodePipeline to automatically provision and manage your deployment pipeline. It also supports GitHub Actions.
Read more about the sam pipeline bootstrap command to get started.
Host your dApp using Interplanetary File System (IPFS)
IPFS is a good solution to host dApps in a decentralized way. IPFS Gateway can serve as Origin to your CloudFront distribution and serve IPFS content over HTTP.
dApps are often hosted on IPFS to increase trust and transparency. With IPFS, your web application source code and assets are not tied to a DNS name and a specific HTTP host. They will live independently on the IPFS network.
Read more about hosting a single-page website on IPFS, and how to run your own IPFS cluster on AWS.
Implementation details
In this section, we’ll demonstrate how to:
- Authenticate users via their digital wallet using Amazon Cognito user pool
- Protect your API Gateway from the public internet by authorizing access to both authenticated and unauthenticated users
- Call Alchemy and Moralis third party APIs securely using API Gateway HTTP passthrough and AWS Lambda proxy integrations
- Use the JavaScript Amplify Libraries to interact with Amazon Cognito and API Gateway from your web application
Authentication
Your dApp is usable by both authenticated and unauthenticated users. Unauthenticated users can look up NFT collections while authenticated users can also look up their own NFTs.
In your dApp, there is no login/password combination or Identity Provider (IdP) in place to authenticate your users. Instead, users connect their digital wallet to the web application.
To capture users’ wallet addresses and grant them temporary AWS credentials, you can use Amazon Cognito user pool and Amazon Cognito identity pool.
You can create a custom authentication flow by implementing an Amazon Cognito custom authentication challenge, which uses AWS Lambda triggers. This challenge requires your users to sign a generated message using their digital wallet. If the signature is valid, it confirms that the user owns this wallet address. The wallet address is then used as a user identifier in the Amazon Cognito user pool.
Figure 3 details the Amazon Cognito authentication process. Three Lambda functions are used to perform the different authentication steps.
- To define the authentication success conditions, the Amazon Cognito user pool calls the “Define auth challenge” Lambda function (
defineAuthChallenge.js
). - To generate the challenge, Amazon Cognito calls the “Create auth challenge” Lambda function (
createAuthChallenge.js
). In this case, it generates a random message for the user to sign. Amazon Cognito forwards the challenge to the dApp, which prompts the user to sign the message using their digital wallet and private key. The dApp then returns the signature to Amazon Cognito as a response. - To verify if the user’s wallet effectively signed the message, Amazon Cognito forwards the user’s response to the “Verify auth challenge response” Lambda function (
verifyAuthChallengeResponse.js
). If True, then Amazon Cognito authenticates the user and creates a new identity in the user pool with the wallet address asusername
. - Finally, Amazon Cognito returns a JWT Token to the dApp containing multiple claims, one of them being
cognito:username
, which contains the user’s wallet address. These claims will be passed to your AWS Lambda event and Amazon API Gateway mapping templates allowing your backend to securely identify the user making those API requests.
Authorization
Amazon API Gateway offers multiple ways of authorizing access to an API route. This example showcases three different authorization methods:
- AWS_IAM: Authorization with IAM Roles. IAM roles grant access to specific API routes or any other AWS resources. The IAM Role assumed by the user is granted by Amazon Cognito identity pool.
- COGNITO_USER_POOLS: Authorization with Amazon Cognito user pool. API routes are protected by validating the user’s Amazon Cognito token.
- NONE: No authorization. API routes are open to the public internet.
API Gateway backend integrations
HTTP proxy integration
The HTTP proxy integration method allows you to proxy HTTP requests to another API. The requests and responses can passthrough as-is, or you can modify them on the fly using Mapping Templates.
This method is a cost-effective way to secure access to any third-party API. This is because your third-party API keys are stored in your API Gateway and not on the frontend application.
You can also activate caching on API Gateway to reduce the amount of API calls made to the backend APIs. This will increase performance, reduce cost, and control usage.
Inspect the GetNFTsMoralisGETMethod
and GetNFTsAlchemyGETMethod
resources in the SAM template to understand how you can use Mapping Templates to modify the headers, path, or query string of your incoming requests.
Lambda proxy integration
API Gateway can use AWS Lambda as backend integration. Lambda functions enable you to implement custom code and logic before returning a response to your dApp.
In the backend/src
folder, you will find two Lambda functions:
getNFTsMoralisLambda.js
: Calls Moralis API and returns raw responsegetNFTsAlchemyLambda.js
: Calls Alchemy API and returns raw response
To access your authenticated user’s wallet address from your Lambda function code, access the cognito:username
claim as follows:
var wallet_address = event.requestContext.authorizer.claims["cognito:username"];
Using Amplify Libraries in the dApp
The dApp uses the AWS Amplify Javascript Libraries to interact with Amazon Cognito user pool, Amazon Cognito identity pool, and Amazon API Gateway.
With Amplify Libraries, you can interact with the Amazon Cognito custom authentication flow, get AWS credentials for your frontend, and make HTTP API calls to your API Gateway endpoint.
The Amplify Auth library is used to perform the authentication flow. To sign up, sign in, and respond to the Amazon Cognito custom challenge, use the Amplify Auth library. Examine the ConnectButton.js
and user.js
files in the dapp
folder.
To make API calls to your API Gateway, you can use the Amplify API library. Examine the api.js
file in the dApp to understand how you can make API calls to different API routes. Note that some are protected by AWS_IAM
authorization and others by COGNITO_USER_POOL
.
Based on the current authentication status, your users will automatically assume the CognitoAuthorizedRole
or CognitoUnAuthorizedRole
IAM Roles referenced in the Amazon Cognito identity pool. AWS Amplify will automatically use the credentials associated with your AWS IAM Role when calling an API route protected by the AWS_IAM authorization method.
Amazon Cognito identity pool allows anonymous users to assume the CognitoUnAuthorizedRole
IAM Role. This allows secure access to your API routes or any other AWS services you configured, even for your anonymous users. Your API routes will then not be publicly available to the internet.
Cleaning up
To avoid incurring future charges, delete the CloudFormation stack created by SAM. Run the sam delete
command or delete the CloudFormation stack in the AWS Management Console directly.
Conclusion
In this blog, we’ve demonstrated how to use different AWS managed services to run and deploy a decentralized web application (dApp) on AWS. We’ve also shown how to integrate securely with Web3 providers’ APIs, like Alchemy or Moralis.
You can use Amazon Cognito user pool to create a custom authentication challenge and authenticate users using a cryptographically signed message. And you can secure access to third-party APIs, using API Gateway and keep your secrets safe on the backend.
Finally, you’ve seen how to host a single-page application (SPA) using Amazon S3 and Amazon CloudFront as your content delivery network (CDN).