Polkadot + AWS Cognito User Authentication
Our goal is to enable users to sign up and log in to our website using their Polkadot or Kusama credentials via the Polkadot.js extension.
You might find this guide useful if you are building a website or app for Polkadot users. Since Polkadot is built on Substrate, a popular blockchain framework, you can easily modify the code to enable login for other Substrate-based blockchains or para-chains.
We will be using Polkadot API libraries and AWS Cognito for user authentication and session management. We will use Javascript for both the frontend and backend. You can test a live version of this solution at Diversified Validator Network, a website for validators to find more nominators. Let’s dive right in.
Objective
Users should be able to sign in to our website by selecting a Polkadot account and entering their password inside the Polkadot.js extension. Users do not have to enter their email or any other password to register or sign in.
Setup AWS Cognito pool
First, you will need to set up an AWS Cognito user pool. You can choose the default settings but think about whether the user’s email should be a “required attribute”. In this tutorial, we will assume that the email is not required and the user can sign up with their public Polkadot address.
Create an APP client for your new pool. Enable ALLOW_CUSTOM_AUTH and ALLOW_REFRESH_TOKEN_AUTH and disable all other options. If you are making a website, make sure you do not create an App client secret key.
We also need to create two custom attributes. Go to Attributes, and click on Add another attribute. Create two attributes with names keyToSign and signedKey. Finally, go to App Clients, Show details, and Set attribute read and write permissions, and select custom:keyToSign and custom:signedKey attributes under Readable Attributes and Writable Attributes.
We will only use these custom attributes during registration to verify that the user was able to sign the key. Subsequent logins will create their own temporary key and will not use these attributes.
We will return to Cognito one more time to update the triggers in a later step.
Create Lambda functions
Now it’s time to create our Lambda functions that will enable the custom login functionality with Cognito. Our goal is to replace Cognito default password authentication with Polkadot’s signature verification. To do this we will need 4 Lambda functions.
Presignup Lambda
The function will ensure that only users that submit a valid signature can create an account. The function will also auto-confirm users in Cognito.
AuthChallengeDefine Lambda
This function will orchestrate the authentication steps.
The function will issue a PASSWORD challenge as a first step. Although we don’t require a password, I found that the Cognito library expects to see this challenge first to proceed with CUSTOM. We will use a dummy password and we will not check for its validity so it’s all good.
In the second step, the function will issue a CUSTOM challenge, and in the final step, it will issue tokens if the custom challenge was successful.
AuthChallengeCreate Lambda
This function will create a random string named keyToSign that we want the user to sign to prove that they own the Polkadot account. The string must be stored in the private challenge parameters to pass in the next Lambda function. The string must also be stored in the public parameters so that it is returned to the user via the Cognito HTTP call.
AuthChallengeVerify Lambda
Finally, we need a function to verify that the signed string is valid. This function will take the keyToSign (passed from the previous function’s private parameters), the signed key that is the challenge answer from the user, and the username which is the Polkadot public address. We can then ask Polkadot’s crypto library to verify that the key was signed by that address.
Back to Cognito
The next step is to connect your Lambdas with Cognito’s triggers so that each function is accessed by Cognito during the user authentication flow.
Go to the Cognito pool you created and go to Triggers. Now, assign the functions:
Pre sign-up -> Presignup
Create Auth Challenge -> AuthChallengeCreate
Verify Auth Challenge Response -> Auth ChallengeVerify
Define Auth Challenge -> AuthChallengeDefine
That was easy!
Coding the Client
Our client will need one process for signing up and one for logging in. From the user’s perspective, signing up will be the same as logging in, i.e. we will not ask for any extra information during the sign-up process.
When a first-time user tries to log in, the front-end will first try to log them to the system and fail (since they are not registered). We will then catch the exception, register them through the registration endpoint, and log them in again. Therefore, first-time users will have to sign two challenges (one for registration and one for logging in) which is hardly an inconvenience.
You can set defaultPassword to any password string that satisfied the Cognito password requirements. The password will not be checked and it does not matter.
Our functions will pass around an account which is a Polkadot injected account. You can get those accounts from…
web3AccountsSubscribe(async (injectedAccounts) => … )
Now, you need login and register functions. These are your typical Cognito login functions with some modifications.
In login, we have to add a customChallenge callback in authenticateUser. This function will be called after the dummy password authentication step and must provide Cognito with a valid signed key. Note that the function takes the key from the public challenge parameters provided by the AuthChallengeCreate lambda.
In the register function, we generate our own random key and sign it. We then send the key and the signed key as custom attributes to Cognito. These attributes are checked by the Presignup lambda to ensure that the user is the account owner.
Note that, during registration, we are generating a random key (UUID) and we are signing that key. In contrast, during log-in, we must sign the keyToSign that is returned by Cognito. If somebody has obtained a signature of any key by intercepting our communications they will be able to register their account but they will not be able to access it. This is ok for our use case but it may not be ok for yours.
The last step is to implement the signKey function that will use the Polkadot library to generate a signed key from the key and the user’s Polkadot public address.
Congrats!
Anybody with a Polkadot address can now sign in to your website. Since you know that the user is the owner of that address, you can go ahead and offer them services that interact with that address.