Error 403 when accessing AWS IoT device shadow with Cognito authenticated user Identity

Error 403 when accessing AWS IoT device shadow with Cognito authenticated user Identity
Page content

AWS IoT is useful. You can manage and control the status of devices even remotely. On the other hand, mastering AWS IoT requires an understanding of the entire AWS IoT service, a familiarity with the not-so-friendly service console, and some tricky configuration. In this article, I will introduce some small AWS IoT tips.

Goals

  • Getting AWS IoT Device Shadows in Mobile Apps
  • Only authenticated users can access device shadows
  • Use Cognito's UserPool and Identity Pool created by AWS Amplify

Environment

  • Android
    • aws-android-sdk-iot: 2.23.0

Error 403 occurs when getting device shadow

Get the device shadow in Android. The following is a simple snippet using the AWS IoT-Data client.

1val dataClient = AWSIotDataClient(AWSMobileClient.getInstance().credentials)
2dataClient.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1))
3
4val deviceShadowsResult = dataClient.getThingShadow(
5  GetThingShadowRequest().withThingName(thingName)
6)

Then, the following error was printed.

E/AndroidRuntime(15973): Caused by: com.amazonaws.AmazonServiceException: null (Service: AWSIotData; Status Code: 403; Error Code: ForbiddenException

Is the client lacking permissions because of the 403 error?

The AWS Developer Guide says the following

When you use authenticated identities, in addition to the IAM policy attached to the identity pool, you must attach an AWS IoT policy to an Amazon Cognito Identity by using the AttachPolicy API and give permissions to an individual user of your AWS IoT application. You can use the AWS IoT policy to assign fine-grained permissions for specific customers and their devices.

Attach an IAM Policy to a Cognito-authenticated IAM Role

First, attach an IAM policy to the IAM Role that will be assigned to the authenticated user in Cognito.

AWS Amplify Auth should have created an IAM Role named xxxxx-authRole. Attach the AWSIotDataAccess policy and AWSIoTConfigAccess to this IAM Role.

The following actions are the ones that are required this time.

"iot:Connect",
"iot:Publish",
"iot:Subscribe",
"iot:Receive",
"iot:GetThingShadow",
"iot:UpdateThingShadow",
"iot:DeleteThingShadow",
"iot:ListNamedShadowsForThing",
"iot:AttachPolicy",
"iot:AttachPrincipalPolicy",
"iot:AttachThingPrincipal",

Creating an AWS IoT Policy

Nest, create AWS IoT Policy. This is required to access the AWS IoT data plane. The format is the same as the familiar AWS IAM policy document.

In this time, create IoT Policy named HogeIoTPolicy.

 1{
 2  "Version": "2012-10-17",
 3  "Statement": [
 4    {
 5      "Action": [
 6        "iot:Connect",
 7        "iot:Publish",
 8        "iot:Subscribe",
 9        "iot:Receive",
10        "iot:GetThingShadow",
11        "iot:UpdateThingShadow",
12        "iot:DeleteThingShadow",
13        "iot:ListNamedShadowsForThing"
14      ],
15      "Resource": "*",
16      "Effect": "Allow",
17      "Sid": "HogeIoTPolicy"
18    }
19  ]
20}

Attach IoT Policy

In addition, attach the IoT Policy to the authenticated user. It is important to note that IoT Policy attachments must be applied to each Identity ID.

If you are considering increasing or decreasing the number of users, it would be better to attach them within the mobile app as follows.

1val identityId = AWSMobileClient.getInstance().identityId
2val iotClient = AWSIotClient(AWSMobileClient.getInstance().credentials)
3iotClient.attachPolicy(AttachPolicyRequest().withPolicyName("HogeIoTPolicy").withTarget(identityId))
1val thingName = "HogeThing"
2val identityId = AWSMobileClient.getInstance().identityId
3val iotClient = AWSIotClient(AWSMobileClient.getInstance().credentials)
4iotClient.attachThingPrincipal(AttachThingPrincipalRequest().withPrincipal(identityId).withThingName(thingName))

Get the device shadow

After all these settings, you can finally access the device shadow.

Once again, the GetThingShadowRequest at the beginning will succeed.

1val dataClient = AWSIotDataClient(AWSMobileClient.getInstance().credentials)
2dataClient.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1))
3
4val deviceShadowsResult = dataClient.getThingShadow(
5  GetThingShadowRequest().withThingName(thingName)
6)

References