iOS Push Notifications Registration Using AWS

I spent some spare time over 2-3 days trying to figure out how to get AWS (Amazon Web Services) working with APNS using SNS (Simple Notification Service). Since the majority of my AWS experience is using Fog, my hands-on experience was a little lacking. The docs are full of hard-to-understand and hard-to-keep-track-of (for me, at least) names for services and permissions and all kinds of fun stuff. I wanted to post what I did to get it to work in hopes that it’ll help someone in the future. I’ll use a few external links/references for common things like generating a development certificate with Apple. I’m more focused on the AWS stuff here.

First thing, make sure you have an AWS account.

1. Create a new app

Once you’re logged into the AWS, navigate to the SNS management console (Services > Mobile Services > SNS). This is where we’ll create our new application. You’ll need an Apple Developer Account in order to generate an APNS-ready certificate. There’s a nice tutorial here on generating push-ready certificates and exporting them as a .p12 file, which is necessary to complete the app setup for AWS. You only need the instructions preceding the “Upload certificate to Admin panel” section. I’m not going into detail over that process in this post.

After you’ve named your app (we’ll say MyApp for the sake of this post) and successfully uploaded the .p12 file, go ahead and save the new app. Note: make sure you select the correct Push Platform – APNS for production or APNS_SANDBOX for dev certificates, otherwise you’ll get an error when trying to save the new app.

2. Add Cognito Access

Next we’re going to head over to the Cognito console. After clicking “Get Started” you’ll be presented with a form for a new Identity Pool. The only 2 things we need to do here is give it a name and enable access to unauthenticated entities. Click “Create Pool” and you’ll be presented with another form to either create new IAM Roles or choose existing ones. These are essentially the rules which allow access to certain AWS services when using the newly created Identity Pool. Just allow that form to create new IAM Roles for both authenticated and unauthenticated identities. Click “Update Roles” to continue.

3. Integrate with iOS

Next you’ll be presented with some starter code for Android, iOS, and .Net. Under the iOS tab you’ll see import options for linking the AWS iOS SDK. I’m using CocoaPods, but you can also include the SDK manually. I actually had to use

#import "AWSCore.h"
#import "CognitoIdentity.h"

to get it to work using v2.0.9 of the AWS iOS SDK. I’m assuming the imports listed in the example code are for an older version.

We’ll need the starter code to initialize the Cognito client. It should like similar to

AWSCognitoCredentialsProvider *credentialsProvider = [AWSCognitoCredentialsProvider 
credentialsWithRegionType:AWSRegionUSEast1
accountId:@"ACCOUNT ID"
identityPoolId:@"IDENTITY POOL ID"
unauthRoleArn:@"AUTHENTICATED ROLE ARN"
authRoleArn:@"UNAUTHENTICATED ROLE ARN"];

AWSServiceConfiguration *configuration = [AWSServiceConfiguration 
configurationWithRegion:AWSRegionUSEast1
credentialsProvider:credentialsProvider];

[AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration;

This code will use ID/ARNs from our newly created Identity Pool and IAM Roles, as well as your AWS account ID.

4. Create a Platform Endpoint

In SNS a device that is registered for push notifications is called an endpoint. You can manually enter device tokens via the AWS console, but we’re focused on device support. So I won’t spend time on the manual process here. Next we’ll need to register the device with SNS so that we can actually receive push notifications. You can refer to this StackOverflow answer to see how to initialize the remote notification registration process in iOS.

Once you’ve done that, iOS should call back to the application:didRegisterForRemoteNotificationsWithDeviceToken: method in your AppDelegate.m file. Here is what I’m using to convert the deviceToken from NSData to NSString (taken from here):

const char* data = [deviceToken bytes];
NSMutableString* token = [NSMutableString string];

for (int i = 0; i < [deviceToken length]; i++) {
    [token appendFormat:@"%02.2hhX", data[i]];
}

Now that we have the deviceToken as an NSString, we’ll be able to send it up to AWS for registration.

AWSSNSCreatePlatformEndpointInput *endPointInput = [[AWSSNSCreatePlatformEndpointInput alloc] init];
endPointInput.platformApplicationArn = @"APPLICATION ARN";
endPointInput.token = token;

AWSSNS *sns = [AWSSNS defaultSNS];
[[sns createPlatformEndpoint:endPointInput] continueWithBlock:^id(BFTask *task) {
    if(task.error != nil)
    {
        NSLog(@"%@", task.error);
    }
    else
    {
        NSLog(@"success!");
    }

    return nil;
}];

What we’re doing here is creating an SNS endpoint input object. Make sure you use the app ARN from where we create a new SNS application earlier. We’ll use the newly created token as well. The createPlatformEndpoint:continueWithBlock: method is what actually makes the request to create the new endpoint (register the device for push notifications).

At this point we want to go ahead and run this on an actual device to test it out. So build to a device from Xcode. If your experience is similar to mine, once the code runs to create the platform endpoint you’ll see an error about not being authorized to . I did this a few times before I got it right so I got a few different errors at times, but if you’ve followed everything else up to here, the error you should be seeing will say something like “Provider is not authorized to perform: SNS:CreatePlatformEndpoint” in the console. We need to navigate to the IAM management console to tweak the roles that were auto-created with our Cognito identity pool earlier. Up to this point we haven’t talked about authenticating via Cognito yet, so for now we’ll stay with unauthenticated access. Once you get to the IAM console, click “Roles,” then the unauthenticated role – should be named something like “Cognito_MyAppUnauth_DefaultRole.” Once you get to the details view for that role you’ll have the option to manage the role policy. Click “Manage Policy” and update the “Action” array to add “SNS:CreatePlatformEndpoint”. At this point this is what that array looks like

"Action": [
  "mobileanalytics:PutEvents",
  "cognito-sync:*",
  "SNS:CreatePlatformEndpoint"
]

Now click “Apply Policy” to save and then build the app to your device again. This time you should get the “success!” message in the console. You can verify the successful device registration by opening the SNS management console, choosing Apps > MyApp, then viewing the list of registered endpoints. At this point you can test push notifications by checking your newly registered endpoint then selecting Endpoint Actions > Publish. Enter a message, “testing” perhaps, and then send it using “Publish Message.” If everything is working correctly you should receive the “testing” push notification on your device!

I’m still learning some of the ins and outs of AWS as a whole, but this process got device registration and push notifications working for me. If you have a better way of doing it or noticed an error in how I did something, please feel free to comment. I may update this post occasionally as I learn new things with AWS.

Thanks for reading!

8 Comments

  1. Ben

    Thanks for you post, that’s very useful. Think about update your code, it seems like part of AWS methods were updated and/or deprecated.

  2. Henrik

    Thanks a lot for this. It’s amazing how unclear documentation can be on things like policies. I followed several different tutorials for setting up a device endpoint with SNS through GCM on Android, and no-one mentioned the need to tweak the auto-generated cognito IAM role.

    Worth mentioning is perhaps that at time of writing, the IAM console lists two separate policy sets: managed policies and inline policies. If anyone finds this blog post but gets confused about how exactly to get the role tweaked, it’s the inline policy (in my case named oneClick_Cognito_Unauth_Role_ that should be modified.

    Also, after fixing the missing policy, I got one additional error when calling createPlatformEndpoint from my device:

    com.amazonaws.services.sns.model.InvalidParameterException: Invalid parameter: PlatformApplicationArn Reason: Region from ARN (eu-west-1) is different than SNS endpoint region (us-east-1). (Service: AmazonSNS; Status Code: 400; Error Code: InvalidParameter)

    This surprised me because I had created my SNS client using the Cognito credentials provider, explicitly setting the region to eu-west-1 when creating the credentials. But it turns out I had to super-explicitly set the region as well on the SNS client after creating it and passing the credentials to it. On Android this was done through:

    AmazonSNSClient sns = new AmazonSNSClient(credentialsProvider);
    sns.setRegion( Region.getRegion(Regions.EU_WEST_1) );

    I suppose there’s very similar code for iOS to accomplish this.

    I’m guessing that if you didn’t encounter this, then maybe you were already running everything in the default us-east-1 region, while I am running in eu-west-1, but I’m thinking this could be helpful to someone else. After fixing these two problems I have now managed to complete the endpoint registration successfully.

    Again, thanks for this.

Leave a Reply

%d bloggers like this: