Proper credentials management in AWS Cognito

A couple of days ago, I wrote how to combine Cognito, Angular and Node.js, and it was primarily focused on how to authenticate users using those technologies. However, a real application has way more use cases and features than just a simple authentication process. 

AWS provides many services such as databases, file storages, lambda expressions, among others. To connect to those services, Amazon provides at our disposal an SDK compressed in an NPM package called AWS-SDK (sounds logic, right?)

Today, I wanted to write how to use Cognito services to authorize users to use these services, instead of creating an IAM user and using the access keys and secret directly in the code; which is a bad practice, since you are actually uploading to your code the credentials to connect to your AWS account, so let's say that if another person finds them, it can do some pretty bad things to your account.

In order to apply security to an application using Cognito, we will need to configure the following resources:

  1. Cognito User pool
  2. Cognito Federated Identities
  3. Code in your Node.js service

The first step, is actually something I wrote two weeks ago, configuring the user pool was something we had to do in order to authenticate users, so for this post, I will skip that step but you can read all about it here. So, let's start from step 2 now.

Cognito Ferederated Identity

Objective: create a federated identity that will provide credentials to users to connect to the Amazon Web Services

On the Amazon console, go to the Security section and click on Cognito. On the screen that appears, click on "Manage Federated Identities" and then "Create new identity pool". Right after, the first step is to type the name of the identity pool. Also, mark the checkbox to allow unauthenticated identities.

Federated Identity - Step 1

Next, open the Authentication providers section and in the Cognito tab, type the user pool id and app client id created in the previous section.

Federated Identity - Step 2

After that, click on create pool and it will prompt you to create some roles to be used for the authenticated and unauthenticated users. Type the role name that suits best your needs and click on allow. We will update this roles later to match the policies that we need.

Click on Allow, and it will create the roles and redirect you to the dashboard screen for the identity pool.

Next, we will go IAM and modify the roles created to have the proper credentials to access our resources. So, on the Amazon console, go to the Security section and click on IAM. On the screen that appears, click Roles in the left pane and search for the roles that you want to update.

For the unatuhenticated users, the policy could reflect the following configuration:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:Scan",
                "dynamodb:Query"
            ],
            "Resource": "arn:aws:dynamodb:us-east-1:ACCOUNTID:table/TABLENAME"
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:PutItem",
                "dynamodb:UpdateItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:us-east-1:ACCOUNTID:table/TABLENAME",
            ]
        }
    ]
}

The policy above, is an example of how it should look like, remember that it will differ depending on how you need each role to behave, so it can have more or less privileges than this example.

For the authenticated users, the policy could reflect the following configuration:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "cognito-idp:ListUsersInGroup",
                "cognito-idp:DescribeUserPool",
                "cognito-idp:AdminEnableUser",
                "cognito-idp:SignUp",
                "cognito-idp:AdminDisableUser",
                "cognito-idp:ChangePassword",
                "cognito-idp:AdminAddUserToGroup",
                "cognito-idp:AdminUpdateUserAttributes",
                "cognito-idp:AdminConfirmSignUp"
            ],
            "Resource": "arn:aws:cognito-idp:us-east-2:ACCOUNTID:userpool/USERPOOLID"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "cognito-identity:*",
            "Resource": "arn:aws:cognito-identity:us-east-2:ACCOUNTID:identitypool/us-east-2:IDENTITYPOOLID"
        },
        {
            "Sid": "VisualEditor2",
            "Effect": "Allow",
            "Action": "cognito-sync:*",
            "Resource": [
                "arn:aws:cognito-sync:us-east-2:ACCOUNTID:identitypool/IDENTITYPOOLID",
                "arn:aws:cognito-sync:us-east-2:ACCOUNTID:identitypool/IDENTITYPOOLID/identity/*",
                "arn:aws:cognito-sync:*:*:identitypool/*/identity/*/dataset/*"
            ]
        },
        {
            "Sid": "VisualEditor3",
            "Effect": "Allow",
            "Action": [
                "dynamodb:PutItem",
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:Scan",
                "dynamodb:Query",
                "dynamodb:UpdateItem"
            ],
            "Resource": "arn:aws:dynamodb:us-east-1:ACCOUNTID:table/TABLENAME"
        },
        {
            "Sid": "VisualEditor4",
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:Scan",
                "dynamodb:Query"
            ],
            "Resource": "arn:aws:dynamodb:us-east-1:ACCOUNTID:table/TABLENAME"
        }
    ]
}

The authenticated users will have the ability to manage cognito users and perform CRUD operations over the dynamo tables. I have created some custom pages to manage the cognito users, that's why I'm allowing authenticated users to have this policy. I encountered many problems letting only administrators to manage this users, so that's why I ended up creating those screens. For example, one problem is that when an admin creates a user, it stays in a status named FORCE_CHANGE_PASSWORD, and it means that the user should, somehow, change the password by using an AWS-SDK command, so I had to create a screen to do that. Also, resetting a password needed an AWS-SDK and other use cases that at the end, it makes so much sense just to create the pages, and stop fighting Amazon, :)

Code in Node.JS service

Objective: modify node.js service to use authentication based on Cognito tokens.

Lastly, this will the fastest step, the trainer application will already have this applied, but just to ensure, validate that the AWS SDK configuration is being done in the following way:

function awsDynamo(configuration) {
  var docClient;
  var credentials = {
    IdentityPoolId: configuration.identityPoolId,
    Logins: {}
  };
  if (configuration.cognitoJwt) {
    credentials.Logins[`cognito-idp.${configuration.cognitoRegion}.amazonaws.com/${configuration.userPoolId}`] = configuration.cognitoJwt;
  }
  AWS.config.update({
    credentials: new AWS.CognitoIdentityCredentials(credentials, {
      region: configuration.cognitoRegion
    }),
    region: configuration.dynamoRegion
  });
  docClient = new AWS.DynamoDB.DocumentClient();
  /* More code */
}

In this block of code, we are sending a configuration object that have the identity pool id, user pool id, the jwt token generated from cognito (when the user has been authenticated) and the region for the cognito identity pool and the dynamo tables.

After all this steps, the service should connect correctly, however, in 1 of 2 ocassions I had to do one more configuration. Apparently, in IAM we need to allow trusted connectivity between the user pool and the identity pool, this is something that I never found in any documentation provided by Amazon, so I had to dig in other places.

And to be honest, this is something that I need to give credits to a guy called Alex Hague. I found the solution here. In case the link is not working, this was his solution, and when applied, it worked perfectly.

To check that the role you have assigned in Cognito Identity Pools (Federated Identities), has a trust relationship with the identity pool. Get the identity pool ID + the name of the role that isn't working. To do this, follow the next steps:

  1. Go to Cognito
  2. Select Manage Federated Identities
  3. Select the identity pool
  4. Click Edit identity pool (top right)
  5. Make a note of the identity pool ID
  6. Make a note of the name of the role that isn't working (e.g. Cognito_blahUnauth_Role

In IAM, check the trust relationship for the role. Ensure that the StringEquals condition value matches the identity pool ID. To do this:

  1. Go to IAM
  2. Click Roles
  3. Click the name of the role that you noted previously
  4. Click Trust relationships
  5. On the right under Conditions, check the StringEquals condition contains the identity pool Id that you noted previously .

Finally, when you fixed the relationship of trust, the service should authenticate and authorize any user that works with the application.

Thanks for reading up to here, hope it works for you and don't hesitate on leaving any comment!