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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Using AWS as a Back End: Authentication & API
35 mins
- Getting Started
- Introduction to the App
- Setting Up Your Environment
- AWS Amplify
- Installing and Configuring Amplify
- Adding Amplify to Your App
- Configuring AWS Cognito
- Adding Authentication With Amazon Cognito
- Completing the Authentication Service
- GraphQL, AppSync and DynamoDB
- Adding a GraphQL API
- Defining Your Schema
- Generating the Database
- Writing Data
- Reading and Writing AppSync Data From Swift
- Reading Data From AppSync
- Creating Data in DynamoDB
- Where to Go From Here?
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.
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"
To ensure all of the Amplify CLI tools operate correctly, enter the following command in Terminal:
npm i --package-lock-only
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.
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.
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.
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 configuration ▸ Username ▸ No, I am done (the default option in each case), and wait for the Amplify CLI to complete.
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
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.
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.
Next, select App client settings in the left-hand menu. Copy the client ID and save it somewhere. You’ll need this later.
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.
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:
- Request the current auth session from Amplify.
- If there’s an error, sign the user out.
- On success, confirm the user is signed in.
- 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. :]
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:
- Call the Amplify sign-in API, passing the username and password.
- Check and handle failures.
- On success, fetch the current logged-in user.
- 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:
- Configure a sign-up request to expect sign-up via email.
- Perform the sign-up using Amplify. You handle the result as you did in the previous examples.
- 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:
- Confirm the sign-up with Amplify and handle the response in the usual fashion.
- On success, sign the user in.
- 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.
AWS will send a confirmation code in an email to the address you provided. When that arrives, enter it and tap Confirm.
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!).
Find the sub field and save it somewhere. You’ll need this later.