Using AWS as a Back End: Authentication & API

Learn how to use Amazon Web Services (AWS) to build a back end for your iOS apps with AWS Amplify and Cognito, using GraphQL. By Tom Elliott.

4.3 (3) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Adding Amplify to Your App

In Xcode, open Podfile. Between the # Pods for IsolationHelp comment and the end at the end of the file, add the following new dependencies:

pod 'Amplify'
pod 'Amplify/Tools'

Next, in your terminal, navigate to the root of the project directory. Type the following to install the dependencies in your project:

pod install --repo-update

Amplify is the main dependency. It provides your app with access to all the Amplify APIs. Amplify Tools adds various automation to Xcode’s build process to make working with Amplify easier.

Next, click the IsolationNation project in the project workspace, then the IsolationNation target.

In the Build Phases tab, click the plus button to add another phase. Choose New Run Script Phase.

Adding a Run Phase for Amplify Tools

Name the phase Amplify Tools by clicking the Run script title. Click and drag it above the Compile Sources phase.

Update the shell script to the following:

"${PODS_ROOT}/AmplifyTools/amplify-tools.sh"

Configuring the Amplify Tools Run Phase

To ensure all of the Amplify CLI tools operate correctly, enter the following command in Terminal:

npm i --package-lock-only
Note: If you’re using nvm to manage your node versions, the preceding command may be unnecessary.

Build your project. After the build completes, the Project navigator will have a new group called AmplifyConfig. This folder houses files containing configuration and resource identifiers for Amplify.

Amplify Config Group in Xcode

Next, in your terminal, type the following:

amplify init

Press Enter to accept the default project name, and select None as your default editor. When asked if you would like to use a profile, type Y and then choose the default profile.

Initializing Amplify

This will take some time to complete, as the CLI creates AWS resources for you.

Next, type the following into your terminal:

amplify console

This will open the Amplify Console in your browser. If you get an error that your project doesn’t exist, make sure you’ve selected the N. Virginia region.

The Amplify Console

At this point, you may want to look around the console to become familiar with it. For now, though, there’s not much to see, since you haven’t added any services to your app yet.

However, you’re about to turn Isolation Nation into a bona fide app! The first step is to add support for users to create accounts and log in. Amazon provides a service for this called Cognito. Cognito has a User Pool, which serves as a directory of all your users. You can configure your User Pool to allow users to log in with username and password, social identity providers like Google or Facebook, or enterprise security systems like SAML.

Configuring AWS Cognito

To start, open Podfile in Xcode and add the following dependency after the two existing dependencies:

pod 'AmplifyPlugins/AWSCognitoAuthPlugin'

Next, install the dependency by running this command in your terminal:

pod install --repo-update

Finally, use the Amplify CLI to configure Cognito for your project. Type the following command into the terminal window at the root of your project:

amplify add auth 

When the CLI prompts you, select Default configurationUsernameNo, I am done (the default option in each case), and wait for the Amplify CLI to complete.

Adding Cognito Auth to your Project

It’s important to note that the Amplify CLI has now configured Cognito for your project locally, but it has not saved that configuration in the cloud. You can confirm this by typing the following in the terminal:

amplify status

Checking Amplify Status

This tells you that you need to create a resource in the Auth category with the given name. Type the following into the terminal and confirm in the affirmative when asked:

amplify push

When you are asked if you would like to generate code for your new API, enter N.

This will likely take several minutes to complete as Amplify creates AWS resources for you.

Once it’s finished, head back to the Amplify Console in your browser. Select your app and then the Backend environments tab.

The Amplify console

An Authentication category now appears in your back end.

Click the Authentication link. Then, in the Users section, click the View in Cognito button to view the Cognito User Pool.

Note: If AWS presents a screen saying “Region not supported”, click Switch to US East (Virginia).

The Amplify Console - Authentication

Viewing User Pools in Cognito

Next, select App client settings in the left-hand menu. Copy the client ID and save it somewhere. You’ll need this later.

Selecting your ClientID

Phew! All your setup is now out of the way. Next, it’s time to add the code to your app to handle authentication.

Adding Authentication With Amazon Cognito

Open AppDelegate.swift. At the top of the file, after the UIKit import, add imports for Amplify:

import Amplify
import AmplifyPlugins

Remove the line that sets userSession.loggedInUser to “Lizzie”.

Immediately after initializing authenticationService, add the following:

do {
  try Amplify.add(plugin: AWSCognitoAuthPlugin())
  try Amplify.configure()

  #if DEBUG
  Amplify.Logging.logLevel = .debug
  #else
  Amplify.Logging.logLevel = .error
  #endif
} catch {
  print("Error initializing Amplify. \(error)")
}

This code configures the Amplify library with a Cognito authentication plug-in. Then it sets an appropriate log level for Amplify.

Build and run.

Oh no! Now the app displays a never-ending spinner! Clearly you’re not finished yet.

Selecting your ClientID

Completing the Authentication Service

Open AuthenticationService.swift. In the section identified by // MARK: Public API, you’ll see stub functions with names like signIn(as:identifiedBy:) and checkAuthSession(). Now it’s time to write some code that uses your Cognito back end.

First, add a new import at the top of the file:

import Amplify

Next, locate the empty checkAuthSession() and add the following implementation:

// 1
_ = Amplify.Auth.fetchAuthSession { [self] result in
  switch result {
  // 2
  case .failure(let error):
    logger.logError(error)
    signOut()

  // 3
  case .success(let session):
    if !session.isSignedIn {
      setUserSessionData(nil)
      return
    }

    // 4
    guard let authUser = Amplify.Auth.getCurrentUser() else {
      let authError = IsolationNationError.unexpctedAuthResponse
      logger.logError(authError)
      signOut()
      return
    }
    setUserSessionData(authUser.username)
  }
}

Here’s what this code does:

  1. Request the current auth session from Amplify.
  2. If there’s an error, sign the user out.
  3. On success, confirm the user is signed in.
  4. If the user is signed in, fetch the current user and set the details on the user session.

Build and run. The spinner is now replaced with a sign-in screen. :]

The Sign In Screen

Next, add the sign-in code. Remove all the code inside signIn(as:identifiedBy:), and replace it with the following:

return Future { promise in
  // 1
  _ = Amplify.Auth
    .signIn(username: username, password: password) { [self] result in
    switch result {
    // 2
    case .failure(let error):
      logger.logError(error.localizedDescription)
      promise(.failure(error))
    // 3
    case .success:
      guard let authUser = Amplify.Auth.getCurrentUser() else {
        let authError = IsolationNationError.unexpctedAuthResponse
        logger.logError(authError)
        signOut()
        promise(.failure(authError))
        return
      }
      // 4
      setUserSessionData(authUser.username)
    }
  }
}

This is what you’re doing:

  1. Call the Amplify sign-in API, passing the username and password.
  2. Check and handle failures.
  3. On success, fetch the current logged-in user.
  4. Set the user’s details on the user session, as before.

With this set up, users can sign in to your app!

But there’s just one problem: You don’t have any existing users, and there’s still no way to sign up. Time to fix that.

Replace the body of signUp(as:identifiedBy:with:) with the following:

return Future { promise in
  // 1
  let userAttributes = [AuthUserAttribute(.email, value: email)]
  let options = AuthSignUpRequest.Options(userAttributes: userAttributes)
  // 2
  _ = Amplify.Auth.signUp(
    username: username, 
    password: password, 
    options: options
  ) { [self] result in
    DispatchQueue.main.async {
      switch result {
      case .failure(let error):
        logger.logError(error.localizedDescription)
        promise(.failure(error))
      case .success(let amplifyResult):
        // 3
        if case .confirmUser = amplifyResult.nextStep {
          promise(.success(.awaitingConfirmation(username, password)))
        } else {
          let error = IsolationNationError.unexpctedAuthResponse
          logger.logError(error.localizedDescription)
          promise(.failure(error))
        }
      }
    }
  }
}

In this code, you do the following:

  1. Configure a sign-up request to expect sign-up via email.
  2. Perform the sign-up using Amplify. You handle the result as you did in the previous examples.
  3. If sign-up is a success, return the awaitingConfirmation state. Amplify will send the user a code via email to confirm ownership of the address provided.

Next, you need to allow users to confirm their email address. Replace the contents of confirmSignUp(for:with:confirmedBy:) with this:

return Future { promise in
  // 1
  _ = Amplify.Auth.confirmSignUp(
    for: username, 
    confirmationCode: confirmationCode
  ) { [self] result in
    switch result {
    case .failure(let error):
      logger.logError(error.localizedDescription)
      promise(.failure(error))
    case .success:
      // 2
      _ = Amplify.Auth.signIn(
        username: username, 
        password: password
      ) { result in
        switch result {
        case .failure(let error):
          logger.logError(error.localizedDescription)
          promise(.failure(error))
        case .success:
          // 3
          checkAuthSession()
        }
      }
    }
  }
}

In this code, you verify the user:

  1. Confirm the sign-up with Amplify and handle the response in the usual fashion.
  2. On success, sign the user in.
  3. Call checkAuthSession(), which sets the user session.

Update signOut() to sign the user out of Cognito and clear their user session. Add the following code after setting the user session to nil:

_ = Amplify.Auth.signOut { [self] result in
  switch result {
  case .failure(let error):
    logger.logError(error)
  default:
    break
  }
}

Finally, open AppDelegate.swift. Add the following to the bottom of application(_:didFinishLaunchingWithOptions:), just before return true:

// Listen to auth changes
_ = Amplify.Hub.listen(to: .auth) { payload in
  switch payload.eventName {
  case HubPayload.EventName.Auth.sessionExpired:
    authenticationService.checkAuthSession()
  default:
    break
  }
}

By default, the authentication token returned from Cognito expires after an hour. But you can extend it without asking the user to log in again. That’s what’s happening here.

Build and run. This time, tap the Sign Up button and sign up with a username, email address and password.

Note: Most email providers will allow you to add an infinite number of suffixes per email address. For example, if your email address is example@gmail.com, example+lizzie@gmail.com will also route to your inbox. This is a great way to generate test email addresses.
Note: The default Cognito User Pool setup has a password policy of 8 characters or more.

The Sign Up Screen

AWS will send a confirmation code in an email to the address you provided. When that arrives, enter it and tap Confirm.

The Confirm Sign Up Screen

Congratulations! You have successfully logged into the app. :] Now, sign out and sign back in using the same user to verify that your code is working.

Next, confirm that your user now appears in the cloud. In your browser, go to your earlier Cognito tab. Click the Users and groups option in the left-hand menu. Select your user from the list (of one user!).

The cognito user dashboard

Find the sub field and save it somewhere. You’ll need this later.