Front-End Web & Mobile

Understanding Amazon Cognito Authentication Part 3: Roles and Policies

Amazon Cognito helps you create unique identifiers for your end users that are kept consistent across devices and platforms. Cognito also delivers temporary, limited-privilege credentials to your application to access AWS resources. In previous posts (Part 1, Part 2), I covered the basics of Cognito’s authentication flow. In this post, I want to focus on the last step of the Cognito Authflow and how you can leverage this to create more dynamic policies for your users.

When you create your identity pool via the Cognito console, it will create two roles for you, one for authenticated users and one for unauthenticated users, but you can visit the IAM Console and create more roles than just these. You can also modify the existing roles to add support for additional services such as Amazon S3 or Amazon DynamoDB, depending on your use case.

Role Trust

Cognito leverages IAM Roles to generate temporary credentials for your applications users. As I showed in Part 1, the access to these permissions is controlled by that role’s trust relationships:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:aud": "us-east-1:12345678-dead-beef-cafe-123456790ab"
        },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "unauthenticated"
        }
      }
    }
  ]
}

Reusing Roles Across Identity Pools

If you want to reuse your role with multiple identity pools because they share a common permission set, you can simply include multiple identity pools like so:

"StringEquals": {
    "cognito-identity.amazonaws.com:aud": [
        "us-east-1:12345678-dead-beef-cafe-123456790ab",
        "us-east-1:98765432-face-cafe-beef-123456790ab"
    ]
}

Doing this can allow you to have a single role to update as your needs for access policy change.

Limiting Access to Specific Identities

If you want to create a policy limited to only a specific set of users of your application, you can also do that by checking the value of the cognito-identity.amazonaws.com:sub value. This value is the identity ID for the individual user, not the identifier of their login account (such as an email address):

"StringEquals": {
    "cognito-identity.amazonaws.com:aud": "us-east-1:12345678-dead-beef-cafe-123456790ab",
    "cognito-identity.amazonaws.com:sub": [
            "us-east-1:12345678-1234-1234-1234-123456790ab",
            "us-east-1:98765432-1234-1234-1243-123456790ab"
    ]
}

If you are using developer authenticated identities, Amazon Cognito includes an API for looking up Cognito identity IDs for your users to make this process easier.

Note: You may want to consider using conditional access policies instead of limited access roles. More on conditional access policies later.

Limiting Access to Specific Providers

If you want to create a policy limited to only users who have logged in with a specific provider (perhaps your own login provider), as mentioned in Part 1, you can check the cognito-identity.amazonaws.com:amr value. This field will be filled with the domains of all providers used during the last authentication:

"ForAnyValue:StringLike": {
    "cognito-identity.amazonaws.com:amr": "login.myprovider.myapp"
}

Access Policies

Once a user has been included in a trust relationship, when your application calls AssumeRoleWithWebIdentity (perhaps via an AWSCognitoCredentialsProvider), they will receive the permissions attached to that role. These permissions will be effective across all users that assume that role. If you want to partition your users’ access, you can do so via policy variables. Be careful when using your users’ identity IDs in your access policies, particularly for unauthenticated identities, as these may change if the user chooses to login.

S3 Prefix

You can very easily give a user a specific prefix “folder” in an S3 bucket by mapping this prefix to the ${cognito-identity.amazonaws.com:sub} variable:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": ["s3:ListBucket"],
      "Effect": "Allow",
      "Resource": ["arn:aws:s3:::mybucket"],
      "Condition": {"StringLike": {"s3:prefix": ["${cognito-identity.amazonaws.com:sub}/*"]}}
    },
    {
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Effect": "Allow",
      "Resource": ["arn:aws:s3:::mybucket/${cognito-identity.amazonaws.com:sub}/*"]
    }
  ]
}

DynamoDB Fine-Grained Access

Amazon Cognito variables work with Amazon DynamoDB and fined-grained access control. You can grant access to items in DynamoDB keyed by their identity ID:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:BatchGetItem",
                "dynamodb:Query",
                "dynamodb:PutItem",
                "dynamodb:UpdateItem",
                "dynamodb:DeleteItem",
                "dynamodb:BatchWriteItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:us-west-2:123456789012:table/MyTable"
            ],
            "Condition": {
                "ForAllValues:StringEquals": {
                   "dynamodb:LeadingKeys":  ["${cognito-identity.amazonaws.com:sub}"]
                }
            }
        }
    ]
}

Conditional Access Policy

If you want to have a different set of permissions for a set of users, for instance a set of admins, you can use the limited access approach I mentioned earlier, or you can use a conditional access policy. Using a conditional access policy just means extending the Condition block to look for values in our identity:

{
   "Version":"2012-10-17",
   "Statement":[
      {
         "Effect":"Allow",
         "Action": ["s3:GetObject"],
         "Resource":["arn:aws:s3:::MYBUCKET*"]
      }
   ],
   "Statement":[
      {
         "Effect":"Allow",
         "Action":["s3:PutObject"],
         "Resource":["arn:aws:s3:::MYBUCKET*"],
         "Condition":{
            "StringEquals":{
               "cognito-identity.amazonaws.com:sub":[
                    "us-east-1:12345678-1234-1234-1234-123456790ab",
                    "us-east-1:98765432-1234-1234-1243-123456790ab"
               ]
            }
         }
      }
   ]
}

This policy would give everyone read access to MYBUCKET, but only the users listed in the second statement would be allowed to upload files to the bucket.

Conclusion

I hope this clarifies how you can use Cognito and IAM to generate more dynamic access policies for your applications. If you have any comments or questions, please free to leave a comment here or visit our forums and we will try to assist you.