DEV Community

Matt Martz for AWS Community Builders

Posted on • Originally published at matt.martz.codes on

Amplifying AWS Tutorials: Building a Social Notes App with "Sign in with Apple" and AWS Pinpoint Analytics

For the 2023 AWS Amplify + Hashnode Hackathon, I wanted to take a closer look into iOS development and take the introductory iOS notes app from AWS Amplify's tutorial to the next level by incorporating powerful features like federated login via Apple's "Sign in with Apple" Then we'll add some easy Analytics to our app with AWS Pinpoint.

While AWS's initial tutorial covered basic username/password authentication... users appreciate convenience and security. To support this, we'll add "Sign in with Apple" support, which offers users a seamless and privacy-focused login option. With "Sign in with Apple," users can authenticate with their Apple ID and stay in control of their personal information (including giving them the ability to hide their email).

A successful app requires understanding user behavior and optimizing user experiences. That's where AWS Pinpoint Analytics comes into play. I'll demonstrate how to integrate AWS Pinpoint into the notes app, enabling us to collect critical user engagement data. With this newfound insight, we can analyze user interactions, monitor feature adoption, and make data-driven decisions to enhance the app's performance.

AWS Amplify is a comprehensive development platform offered by Amazon Web Services (AWS) that simplifies the process of building web and mobile applications. It provides developers with a set of tools, services, and libraries to accelerate the development of cloud-powered applications.

This tutorial assumes you are familiar with the basics of AWS Amplify, including authentication setup and working with the Amplify Storage component. If you're new to these concepts, I recommend checking out the original AWS Amplify tutorial to get up to speed.

The code for this project is located here: https://github.com/martzcodes/blog-amplifyhackathon-ios-2023

Updating the App to use "Sign in with Apple"

Amplify and Apple make it really easy to incorporate "Sign in with Apple" (SIWA) into your applications. SIWA allows you to federate users into an Amazon Cognito identity pool using the AWS Amplify Libraries for Swift. By federating a user into a Cognito identity pool, it allows us to use temporary AWS IAM credentials for the users based on the identity token that Apple provides us. In doing so, we can use those AWS IAM credentials to access other services like Amazon S3, AppSync and Pinpoint.

This article will show you how to use Sign in with Apple (SIWA) to retrieve an identity token and federate the user in an Amazon Cognito identity pool using the AWS Amplify Libraries for Swift. Federating a user in an identity pool provides them with credentials that allow them to access other services like Amazon S3 and Amazon DynamoDB.

First, we'll update amplify's auth to use Apple's provider. Below is the full list of answers when running amplify update auth:

$ amplify update auth
 What do you want to do? Walkthrough all the auth configurations
 Select the authentication/authorization services that you want to use: User Sign-Up, Sign-In, connected with AWS IAM controls (Enables per-user Storage features for images or other content, Analytics, and more)
 Allow unauthenticated logins? (Provides scoped down permissions that you can control via AWS IAM) Yes
 Do you want to enable 3rd party authentication providers in your identity pool? Yes
 Select the third party identity providers you want to configure for your identity pool: Apple

 You've opted to allow users to authenticate via Sign in with Apple. If you haven't already, you'll need to go to https://developer.apple.com/account/#/welcome and configure Sign in with Apple.

 Enter your Bundle Identifier for your identity pool: <your app bundle id from apple>
 Do you want to add User Pool Groups? No
 Do you want to add an admin queries API? No
 Multifactor authentication (MFA) user login options: OFF
 Email based user registration/forgot password: Enabled (Requires per-user email entry at registration)
 Specify an email verification subject: Your verification code
 Specify an email verification message: Your verification code is {####}
 Do you want to override the default password policy for this User Pool? No
 Specify the app's refresh token expiration period (in days): 30
 Do you want to specify the user attributes this app can read and write? No
 Do you want to enable any of the following capabilities?
 Do you want to use an OAuth flow? No
? Do you want to configure Lambda Triggers for Cognito? Yes
? Which triggers do you want to enable for Cognito

Enter fullscreen mode Exit fullscreen mode

Identity pools do NOT use Cognito User Groups... they use AWS IAM-based access. In order to make use of these for our app, we need to switch AWS AppSync to use IAM auth instead of Cognito User Groups. Let's run amplify update api. Below are the full answers for this section:

amplify update api
? Select from one of the below mentioned services: GraphQL
...
? Select a setting to edit Authorization modes
? Choose the default authorization type for the API IAM
? Configure additional auth types? No

Enter fullscreen mode Exit fullscreen mode

Before we can push these changes, we need to update our schema.graphql. It was using auth based on Cognito Pools, but we need to switch it to IAM-based auth. For demo sake, we also want to enable guests (unauthenticated users) to also read notes. We'll update the schema.graphql file to:

type NoteData
@model
@auth(
    rules: [
      { allow: public, provider: iam, operations: [read] }
      { allow: private, provider: iam, operations: [read, create, update, delete] }
    ]
  ) {
    id: ID!
    name: String!
    description: String
    image: String
}

Enter fullscreen mode Exit fullscreen mode
  • { allow: public, provider: iam, operations: [read] } gives guests read access

  • { allow: private, provider: iam, operations: [read, create, update, delete] } gives logged in users full CRUD access

With the schema.graphql updated, we can run amplify codegen models to regenerate the APIs.

With these changes, we can run amplify push and our backend will update to reflect these new changes. Next, we'll need to modify some of the swift code.

Instead of using Amplify.Auth.signInWithWebUI we need to use the SignInWithApple button and then make a call to federateToIdentityPool. SignInWithApple is a capability provided by Apple. Once signed in, it provides an identity token that is used by federateToIdentityPool

In ContentView.swift:

// add this to the top of the file
import AuthenticationServices

// In the ContentView view:
func configureRequest(_ request: ASAuthorizationAppleIDRequest) {
    request.requestedScopes = [.email]
}
func handleResult(_ result: Result<ASAuthorization, Error>) {
    switch result {
    case .success(let authorization):
        guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential,
                let identityToken = credential.identityToken else {
                    return
                }
        guard let tokenString = String(data: identityToken, encoding: .utf8) else {
            return
        }
        Backend.shared.federateToIdentityPools(with: tokenString)
        self.userData.isSignedIn = true;
    case .failure(let error):
        print(error)
    }
}

// replace the original sign in button with:
SignInWithAppleButton(
    onRequest: configureRequest,
    onCompletion: handleResult
)
.frame(maxWidth: 300, maxHeight: 45)

Enter fullscreen mode Exit fullscreen mode

SignInWithAppleButton triggers the SIWA capability by Apple. configureRequest ensures that the email is returned as part of the scope of the identityToken. On completion then parses the identityToken and sends it to our Backend.swift service.

In Backend.swift the Amplify.Auth plugin calls federateToIdentityPool:

func federateToIdentityPools(with tokenString: String) {
    guard
        let plugin = try? Amplify.Auth.getPlugin(for: "awsCognitoAuthPlugin") as? AWSCognitoAuthPlugin
    else { return }

    Task {
        do {
            let result = try await plugin.federateToIdentityPool(
                withProviderToken: tokenString,
                for: .apple
            )
            print("Successfully federated user to identity pool with result:", result)
        } catch {
            print("Failed to federate to identity pool with error:", error)
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

In this version of our code, we also have an auth listener that triggers UI updates based on session events. The auth listener in our Backend.swift file looks like this now:

public func listenAuthUpdate() async -> AsyncStream<AuthStatus> { 
    return AsyncStream { continuation in
        continuation.onTermination = { @Sendable status in
                   print("[BACKEND] streaming auth status terminated with status : \(status)")
        }

        // listen to auth events.
        // see https://github.com/aws-amplify/amplify-ios/blob/master/Amplify/Categories/Auth/Models/AuthEventName.swift
        let _ = Amplify.Hub.listen(to: .auth) { payload in            
            print(payload.eventName)
            switch payload.eventName {
            case "Auth.federatedToIdentityPool":
                print("User federated, update UI")
                continuation.yield(AuthStatus.signedIn)
                Task {
                    await self.updateUserData(withSignInStatus: true)
                }
            case "Auth.federationToIdentityPoolCleared":
                print("User unfederated, update UI")
                continuation.yield(AuthStatus.signedOut)
                Task {
                    await self.updateUserData(withSignInStatus: false)
                }
            case HubPayload.EventName.Auth.sessionExpired:
                print("Session expired, show sign in aui")
                continuation.yield(AuthStatus.sessionExpired)
                Task {
                    await self.updateUserData(withSignInStatus: false)
                }
            default:
                print("\(payload)")
                break
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

For federation events, the events end up being Auth.federatedToIdentityPool and Auth.federationToIdentityPoolCleared.

Adding AWS Pinpoint for App Analytics

Next, we want to see what our users are doing in our Notes app and see if they're really making use of the "new" image upload feature.

First, we'll need to update amplify by running amplify add analytics:

amplify add analytics
? Select an Analytics provider Amazon Pinpoint
 Provide your pinpoint resource name: amplifypushup

Enter fullscreen mode Exit fullscreen mode

Then we'll run amplify push.

We'll need to add the Amplify Analytics libraries to our app by making sure the AWSPinpointAnalyticsPlugin is installed. And then we'll add the initialization in our Backend.swift file:

private init() {
  // initialize amplify
  do {
      try Amplify.add(plugin: AWSCognitoAuthPlugin())
      try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels()))
      try Amplify.add(plugin: AWSS3StoragePlugin())
      try Amplify.add(plugin: AWSPinpointAnalyticsPlugin()) // <-- add this
      try Amplify.configure()
      print("Initialized Amplify");
  } catch {
    print("Could not initialize Amplify: \(error)")
  }

Enter fullscreen mode Exit fullscreen mode

If we want to track how popular our image upload feature is, we can add an event:

let properties: AnalyticsProperties = [
    "eventPropertyStringKey": "eventPropertyStringValue",
    "eventPropertyIntKey": 123,
    "eventPropertyDoubleKey": 12.34,
    "eventPropertyBoolKey": true
]

let event = BasicAnalyticsEvent(
    name: "imageUploaded",
    properties: properties
)

try Amplify.Analytics.record(event: event)

Enter fullscreen mode Exit fullscreen mode

You can also emit events related to authentication that Pinpoint will automatically track:

  • _userauth.sign_in

  • _userauth.sign_up

  • _userauth.auth_fail

Conclusion

In my quest to elevate the capabilities of AWS Amplify tutorials, I ventured into iOS development for the 2023 AWS Amplify + Hashnode Hackathon. Building upon the introductory iOS notes app tutorial provided by AWS Amplify, I took the app to the next level by incorporating sought-after features like "Sign in with Apple" and AWS Pinpoint Analytics.

The inclusion of "Sign in with Apple" as a federated login option significantly enhances the app's authentication experience. Users can now seamlessly log in with their Apple ID, ensuring both convenience and privacy. This advanced authentication option empowers users to stay in control of their personal information, including the ability to hide their email.

In addition to empowering users with a better way to sign-in, I integrated AWS Pinpoint Analytics into the notes app. By leveraging AWS Pinpoint's analytical capabilities, we gained valuable insights into user engagement and behavior. This data-driven approach allows us to analyze user interactions, monitor feature adoption, and make informed decisions to optimize the app's performance and user experience.

As you explore the code and implement these advanced features, I encourage you to further experiment and customize the app to suit your specific use case and preferences. Remember to refer to the code repository for detailed implementations.

Thank you for joining me on this amplified journey to enhance AWS tutorials. With "Sign in with Apple" and AWS Pinpoint Analytics at your fingertips, you're equipped to build robust and engaging mobile apps that connect users with seamless authentication and actionable insights.

Top comments (0)