Serverless Push Notification With AWS IoT
Tags: aws, serverless, iot, mobile
Published: 2021-06-29 14:35:41


AWS IoT is an option to create IoT devices without back-end servers as it provides MQTT as a service for us. Having a mobile application to interact with the devices is also possible without servers thanks to AWS Cognito that enables us to access many AWS services securely from the mobile application.

With this setup, serverless push notification from IoT devices to the mobile application can be achieved with only a few more configuration. And since I rarely find any example on this topic so I put it here for anyone exploring the option.

## TL;DR

Use IoT Rule SQL like this to transform the data published into a topic to a valid GCM/FCM payload and forward it to Platform Application Endpoints.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h4nn2mxyzk5z7xeac47t.png)
 
## Prerequisites

This post focuses only on how to set up the push notification with AWS IoT. Readers are assumed to be familiar with following topics.

- How to connect microcontrollers, such as ESP32, to AWS IoT.
- How to set up Firebase Cloud Messaging (FCM) to work with an Android application in your framework of choice, though in this example, I use Ionic and its [Capacitor plugin](https://capacitorjs.com/docs/apis/push-notifications).
- How to set up AWS Cognito, and possiblity Amplify Library, to work with a mobiile application.

## Runtime Architecture

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ef9q54gjtbgah45i5erj.png)
 

1. IoT devices publish to an MQTT topic with a JSON data.
2. An IoT Rule is configured to transform the published data to a valid FCM notification payload and forward the payload to an SNS Topic.
3. The SNS Topic forwards the payload to all subscribed Platform Application Endpoints.
4. AWS sends requests to FCM with the payload, and the push notifications are delivered to the mobile application

## Setup

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zl00iy52ak38b62rtgxg.png)
 

1\. Aquire FCM ServerKey from Firebase Console and use it to create SNS Platform Application.

```bash
aws sns create-platform-application \
    --name dev.foo-project.foo-mobile-devices.sns-app \
    --platform GCM \
    --attributes PlatformCredential=<Your FCM ServerKey>
```

2\. Create the SNS Topic.

```bash
aws sns create-topic --name dev__foo-project__push-notification__sns-topic
```

3\. The IoT rule we are about to create will need an IAM role that grants a permission to trigger the above SNS Topic.

```bash
echo '{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "iot.amazonaws.com"
            },
            "Action": [
                "sts:AssumeRole"
            ]
        }
    ]
}' > trust-policy.json

echo '{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sns:Publish",
            "Resource": "arn:aws:sns:<your_region>:<your_account_id>:dev__foo-project__push-notification__sns-topic"
        }
    ]
}' > policy.json

aws iam create-role --role-name dev.foo-project.iot-rule-action.role --assume-role-policy-document file://trust-policy.json
aws iam put-role-policy --role-name dev.foo-project.iot-rule-action.role --policy-name dev.foo-project.iot-rule-action.policy --policy-document file://policy.json
```

4\. And finally, the main piece of today recipe, the IoT Rule.

```bash
echo '{
    "Sql": "SELECT concat('{\"priority\":\"high\",\"notification\":{\"title\":\"', title ,'\",\"body\":\"', body ,'\",\"mutable_content\":true,\"sound\":\"Tri-tone\",\"badge\":1,\"click_action\":\"foo\"},\"data\":{}}') as GCM, '' as default FROM 'dev.foo-project/push-notification'",
    "Actions": [
        {
            "Sns": {
                "MessageFormat": "JSON",
                "TargetArn": "arn:aws:sns:<your_region>:<your_account_id>:dev__foo-project__push-notification__sns-topic",
                "RoleArn": "arn:aws:iam::<your_account_id>:role/dev.foo-project.iot-rule-action.role"
            }
        }
    ]
}' > rule-payload.json

aws iot create-topic-rule \
    --rule-name dev__foo_project__push_notification__iot_rule \
    --topic-rule-payload file://rule-payload.json
```

It can transform a payload of the form `{ "title": "...", "body": "..." }` to a valid FCM/GCM payload.

## User Notification Registration Flow

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ia2epuqfcmqrdytdu86k.png) 

1\. The mobile application is set up to use AWS Cognito + Amplify Authentication. Here is an example as a React Component.

```Tsx
import Amplify, { Auth } from "aws-amplify";
import { Authenticator, SignIn } from "aws-amplify-react";

Amplify.configure({
    aws_project_region: 'your_region',
    aws_cognito_identity_pool_id: 'your_cognito_identity_pool_id',
    aws_cognito_region: 'your_region',
    aws_user_pools_id: 'your_user_pool_id',
    aws_user_pools_web_client_id: 'your_user_pool_client_id',
    aws_appsync_authenticationType: "AMAZON_COGNITO_USER_POOLS",
    oauth: {},
});
Auth.configure({
    authenticationFlowType: "USER_PASSWORD_AUTH",
});
Amplify.Logger.LOG_LEVEL = "VERBOSE";

const AppWithAuth: React.FC = () => {
    const [isSignedIn, setSignedIn] = useState(false);
    return (
        <Authenticator           
            onStateChange={(state) => {
                if (state === "signedIn") {
                    setSignedIn(true);
                }
            }}>
            <SignIn />
            <App ready={isSignedIn} />
        </Authenticator>
    )
}
```

2\. After the user signin, we can use the credentials from Cognito to create any AWS SDK Clients

```Typescript
import { SNSClient } from "@aws-sdk/client-sns";
import { IoTClient } from "@aws-sdk/client-iot";

const amplifyCredentialProvider = async () => {
    const awsCredentials = await Auth.currentCredentials();
    return Auth.essentialCredentials(awsCredentials);
};

export const iotClient = new IoTClient({
    region: config.region,
    credentials: amplifyCredentialProvider,
});

export const snsClient = new SNSClient({
    region: config.region,
    credentials: amplifyCredentialProvider,
});
```

3\. The application retrieves FCM token from Firebase and use SNSClient to create the platform application endpoint and subscribe to the SNS topic

```Tsx
import { ActionPerformed, PushNotifications, PushNotificationSchema, Token } from "@capacitor/push-notifications";
import { CreatePlatformEndpointCommand, SubscribeCommand } from "@aws-sdk/client-sns";

const App: Rect.FC<{ready: boolean}> = ({ ready }) => {
    useEffect(() => {
        if (ready) {
            // Retrieve FCM Token
            PushNotifications.addListener("registration", async (token: Token) => {
                const endpoint = await snsClient.send(
                    new CreatePlatformEndpointCommand({
                        PlatformApplicationArn: config.snsPlatformApplicationArn,
                        Token: token.value,
                    })
                );
                const subscriptionResult = await snsClient.send(
                    new SubscribeCommand({
                        Protocol: "application",
                        TopicArn: config.snsTopicArn,
                        Endpoint: endpoint.EndpointArn,
                    })
                );
            });
            // FCM Registration Error
            PushNotifications.addListener("registrationError", (error: any) => {
            });
            // Notification received when the app is in foreground
            PushNotifications.addListener("pushNotificationReceived", (notification: PushNotificationSchema) => {
            });
            // Notification data when the user tab the notification in system tray
            PushNotifications.addListener("pushNotificationActionPerformed", (notification: ActionPerformed) => {
            });

            PushNotifications.register();
        }
    }, [ready])
    return (
        <BlaBlaBla />
    )
}
```

## Testing

1\. We will use AWS Console' MQTT Test Client tool to simulate a device publishing to the MQTT topic.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c1qka9pmzc0dq06b5ex1.png)
 
2\. Yey!, the notification arrives at my phone.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t7jeia66k0gp3fs6kgn1.png)