When you authenticate with a web or mobile application, you typically do so with a username and password where you’re authenticated against a user database such as Amazon Cognito. You’re expected to secure your password and rotate it periodically or when it has been compromised.
When you’re building a user-facing application that is running on a Hyperledger Fabric blockchain network, there are no usernames and passwords native to the blockchain. Instead, user credentials consist of a private key and one or more public certificates. The private key is used to validate ownership of your account (similar to a password), whereas the public certificate contains publicly consumable information such as your name, role, organization.
Every read or write transaction against a blockchain network requires the transaction to be signed with the private key to prove user authenticity. This is similar to requiring you to enter your password for every action you performed on a web application, which would be a very poor user experience.
In this post, we look at how we can continue providing a familiar username and password authentication experience to users, while securely managing private keys with AWS Secrets Manager and using AWS Lambda to sign blockchain transactions on the user’s behalf. This post shows a step-by-step approach to implement this, but you can automate many of these steps to provide a simple mechanism for administering users across Amazon Cognito and the blockchain.
Certificate Authorities, private keys, and public certificates
A blockchain network consists of multiple members, each representing a different organization participating in the network. Each member manages their own users’ credentials through a Certificate Authority (CA), which serves two primary purposes:
Registration – The first step is to create accounts for new users, which get created within the CA.
Enrollment – The second step is to generate cryptographic material (private keys and public certificates) for the user. Private keys should be managed with the same level of security as a password, whereas public certificates are designed to be shared.
A user’s private key is used to sign a blockchain transaction before it’s submitted to the blockchain network. When a user has signed a transaction, their identity and associated attributes (such as name, role, and organization) are available to the network through their public certificate. This enables role-based access control within a smart contract, such as ensuring only administrators can perform a particular function. You can also use this to include the identity of the user in the smart contract data, or to assign them ownership of a digital asset.
Blockchain credentials as part of web application authentication
This post builds on the blockchain application deployed in the following GitHub repo. The application lets users donate to their favorite NGOs, while having transparency into how their donations are being spent. Application data and functionality are contained within a smart contract deployed on the blockchain.
We continue to build out this application by creating users and defining their roles within our CA. We also upgrade our smart contract to restrict access based on the user’s role using Hyperledger Fabric’s native attribute-based access control.
To build this, we start by registering and enrolling two users within our CA with the usernames ngoDonor and ngoManager. Next, we create an Amazon Cognito user pool with two users: bobdonor and alicemanager. Each user is tied to their respective CA user (ngoDonor or ngoManager) by setting Amazon Cognito custom attributes on their user profile.
We then deploy an Amazon API Gateway and secure access to it by requiring authentication against the user pool. For more details on how API Gateway uses Amazon Cognito user pools for authorization, see Control access to a REST API using Amazon Cognito user pools as authorizer.
The following diagram illustrates the solution architecture.
The blockchain user is registered (created) in the Certificate Authority, and the application takes custody of the user’s private key and public certificate in AWS Secrets Manager. The user never needs to know or supply their private key; instead a Lambda function signs their blockchain transactions for them after they have successfully authenticated.
A corresponding user account is created within an Amazon Cognito user pool, with a custom attribute, fabricUsername, that identifies this user within the Certificate Authority, and also identifies their credentials within Secrets Manager.
A user attempting to access an API Gateway endpoint is challenged to authenticate via username and password against the Amazon Cognito user pool. When a user successfully authenticates, Amazon Cognito returns an identity token, which is a JSON Web Token (JWT). A JWT is a standards-based method for transferring credentials between two parties in a trusted manner. The client application includes this JWT in requests sent to the API Gateway, which authorizes the user to invoke the API route.
API Gateway retrieves the fabricUsername custom attribute from the JWT, and sends this to the Lambda function that runs the blockchain transaction. The Lambda function retrieves the blockchain user’s private key from Secrets Manager and retrieves the connection profile for connecting to the Amazon Managed Blockchain network from AWS Systems Manager (Parameter Store). We use AWS Identity and Access Management (IAM) policies to restrict access to Secrets Manager and Systems Manager to only the Lambda function.
Up to this point, we have a solution for issuing blockchain transactions on behalf of a user that has authenticated using a username and password. The last step is to enforce attribute-based access within our Fabric chaincode. We upgrade our chaincode to add new methods that add restrictions as to who can run them. The following is a snippet of a Node.js function that retrieves information about the invoking user.
The following diagram shows the sequence of events that transpire to authenticate a user and invoke blockchain transactions on their behalf.
Prior to completing the step-by-step walkthrough, you must complete Part 1, Part 2, and Part 6 in the GitHub repo. These parts include creating the Managed Blockchain network, deploying the NGO chaincode, and deploying an API Gateway with an associated Lambda function to query the blockchain.
In our solution, we create two blockchain users, ngoDonor and ngoManager, each with a corresponding Amazon Cognito user we use to authenticate with. Next, we define API routes in API Gateway that require authorization via Amazon Cognito. One of these routes allows access only to ngoManager users. We then test our setup using the AWS Command Line Interface (AWS CLI) and curl.
For more information about each step in this process, see the GitHub repo. Each step listed in this post has a matching step in the repo in which you run the script or command that completes that step. As you read each step, you can refer to the corresponding step in the repo to gain a more in-depth understanding.
The walkthrough includes the following steps:
Create Fabric users in the Certificate Authority.
Deploy an Amazon Cognito user pool.
Deploy API Gateway routes that require Amazon Cognito authentication.
Create users in the Amazon Cognito user pool.
Upgrade the chaincode.
We conclude by testing the solution.
Create Fabric users in the Certificate Authority
On our Certificate Authority, we create two users, one named ngoDonor that represents a donor, and one named ngoManager that represents a manager. We define various attributes on the users’ certificates, including the users’ fullname and role. These attributes are available within the smart contract and can be used to enforce attribute-based access. For more information about creating Fabric users, see Fabric CA client.
These credentials are stored in Secrets Manager, where they’re retrieved when processing an API request.
Deploy an Amazon Cognito user pool
Next, we create an Amazon Cognito user pool. We enable user password authentication to allow users to authenticate just like they do with a typical web application. We also define a custom attribute called fabricUsername that we populate with the user’s identity within the Certificate Authority.
Deploy API Gateway routes that require Amazon Cognito authentication
In this step, we deploy an API Gateway with the following routes:
/donors – Returns information about all donors. This route is available to all authenticated users.
/donorsmanager – Same as the preceding route, but restricted to authenticated users with the necessary attributes.
/user – Returns information about the calling user. Useful for debugging.
API Gateway fulfills these routes using the Lambda function that was deployed in Part 6. Each route requires authentication with the Amazon Cognito user pool we created in the previous step. When the user is authenticated, API Gateway parses the user’s fabricUsername from the provided identity token. This value is sent to the Lambda function with each invocation so it can retrieve the corresponding user’s CA credentials stored in Secrets Manager.
Create users in the Amazon Cognito user pool
Next, we create two users within the Amazon Cognito user pool. User bobdonor represents a donor, and we set a corresponding fabricUsername of ngoDonor. User alicemanager represents a manager and has a fabricUsername of ngoManager.
Upgrade the chaincode
The final step before we test is to upgrade the NGO chaincode with the new methods needed to support our application. We deploy this on our peer node and upgrade the chaincode on the channel. In a high availability deployment, blockchain members have multiple peer nodes, and you should first install the chaincode on all peer nodes before upgrading the chaincode version on the channel.
Test the solution
You’ve now provisioned all the components and are ready to test.
First, we verify that an unauthenticated user can’t access the APIs. We issue the following command:
We get the following expected response:
Next, we access that same URL, but first we authenticate as the bobdonor We do this using the aws cognito-idp initiate-auth AWS CLI command.The command returns a JWT that contains various information about the authenticated user. The following code is an example of a parsed JWT; note the fabricUsername attribute that identifies this user in the Certificate Authority.
We call the API again, but this time we send an Authorization header with the JWT token.
This returns a successful response containing a list of all the donors.
We try using this same JWT to access the API route that is restricted to only users with the ngoManager role.
As expected, this fails because our user is not a manager, and the returned response tells us the following.
To complete our testing, we authenticate as user alicemanager, who maps to our ngoManager user in our CA. We use the returned JWT to query the restricted API route, and we can now view the list of donors.
View user attributes within the smart contract
It can be helpful during debugging to know what attributes are available within the smart contract. When we upgraded our chaincode, we included a method that returns the attributes associated with the caller. We can see these attributes by calling the following route:
This returns the following result:
In this post, I walked you through how to authenticate blockchain users with API Gateway and Amazon Cognito, while securing their blockchain credentials in Secrets Manager. Developers can use this approach to easily integrate blockchain capabilities into their web and mobile applications.
Although this post explored implementing this integration step by step, you can automate user administration behind a user API. For example, with a single request, the API can atomically create a user within the CA, within the Amazon Cognito pool, and store their credentials on Secrets Manager.
You can also extend this solution beyond Amazon Cognito user pools. For example, you can implement a similar solution that uses other identity providers such as Facebook or Google for authentication. For more information, see Amazon Cognito Identity Pools (Federated Identities).
About the author
Emile Baizel is a Senior Blockchain Architect on the AWS Professional Services team. He has been working with blockchain technology since 2018 and is excited by its potential across a wide range of industries and use cases. In his free time he enjoys trail running, and spending tech-free time with his wife and two young children.
Read MoreAWS Database Blog