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?
Reading and Writing AppSync Data From Swift
Running GraphQL mutations in the Playground is fun — but not as fun as running them from your app!
Switch back to Xcode and open AppDelegate.swift. Before the call to Amplify.configure()
, add the following line:
try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels()))
This tells the Amplify library to add support for AppSync via the API Plug-in and register the models created from your GraphQL schema. Currently, this is just your User
model.
Reading Data From AppSync
At this point, the UserSession
object is just a string representing the user’s name. In this next section, you’ll update the app to retrieve user data from your User
model. You’ll use AppSync to read from your DynamoDB database. This will take quite a bit of refactoring, so don’t worry if you see Xcode errors as you work through this section.
Open UserSession.swift and update the two type declarations for loggedInUser
. Change them from String?
to User?
:
public final class UserSession: ObservableObject {
@Published var loaded = false
// Here
@Published var loggedInUser: User? {
didSet {
loaded = true
}
}
init() {}
// Here
init(loggedInUser: User?) {
self.loggedInUser = loggedInUser
}
}
Next, open AuthenticationService.swift. Add the following method after setUserSessionData(_:)
:
private func fetchUserModel(id: String) -> Future<User, Error> {
// 1
return Future { promise in
// 2
_ = Amplify.API.query(request: .get(User.self, byId: id)) { [self] event in
// 3
switch event {
case .failure(let error):
logger.logError(error.localizedDescription)
promise(.failure(error))
return
case .success(let result):
// 4
switch result {
case .failure(let resultError):
logger.logError(resultError.localizedDescription)
promise(.failure(resultError))
return
case .success(let user):
// 5
guard let user = user else {
let error = IsolationNationError.unexpectedGraphQLData
logger.logError(error.localizedDescription)
promise(.failure(error))
return
}
promise(.success(user))
}
}
}
}
}
This might look a bit scary at first glance, but there’s really not much to it:
- First, this function returns a
Future
, which promises aUser
on successful completion. - You use the Amplify API to run a query. The query will retrieve a
User
object by its ID. - The API takes an event listener closure as its final argument. You issue the call with the result of the network request, which can either succeed or fail. On failure, you log the error before returning the failure.
- If the network request succeeds, you check the underlying GraphQL result type. This could still result in a failure, such as an invalid request, so again you must check for errors.
- If everything succeeds, you confirm that you received a valid user for your ID. If so, you return it.
Now, update setUserSessionData(_:)
to take a User
rather than a String
:
private func setUserSessionData(_ user: User?) {
DispatchQueue.main.async {
if let user = user {
self.userSession.loggedInUser = user
} else {
self.userSession.loggedInUser = nil
}
}
}
Then, in checkAuthSession()
, replace the call to setUserSessionData(authUser.username)
with the following:
let sub = authUser.userId
cancellable = fetchUserModel(id: sub)
.sink(receiveCompletion: { completion in
switch completion {
case .failure(let error):
logger.logError(error)
signOut()
case .finished: ()
}
}, receiveValue: { user in
setUserSessionData(user)
})
This code calls the fetchUserModel(id:)
method you just wrote. On success, it sets the user session with the user.
Similarly, in signIn(as:identifiedBy:)
, replace the call to setUserSessionData(_:)
with the following:
cancellable = self.fetchUserModel(id: authUser.userId)
.sink(receiveCompletion: { completion in
switch completion {
case .failure(let error):
signOut()
promise(.failure(error))
case .finished: ()
}
}, receiveValue: { user in
setUserSessionData(user)
promise(.success(.signedIn))
})
Finally, open RootView.swift. Update the HomeScreenViewModel
initializer in line 62 to use the new UserModel
:
model: HomeScreenViewModel(
userID: loggedInUser.sub,
username: loggedInUser.username)
Build and run. If you’re not already logged in, log in now. Confirm that the app still takes you to the Locations screen.
Nothing has changed in the UI. But your app is now using AppSync to query for the correct User model from the DynamoDB database!
Creating Data in DynamoDB
Earlier, you created a new User
record in DynamoDB by running a mutation in the GraphQL playground. You hard-coded the information for your one user. Obviously, that isn’t a good long-term solution! Instead, you should use Amplify.API
. You’ll make that change now.
Open AuthenticationService.swift and locate the success handler in confirmSignUp(for:with:confirmedBy:)
. Remove the call to checkAuthSession()
, and replace it with the following:
// 1
guard let authUser = Amplify.Auth.getCurrentUser() else {
let authError = IsolationNationError.unexpctedAuthResponse
logger.logError(authError)
promise(.failure(IsolationNationError.unexpctedAuthResponse))
signOut()
return
}
// 2
let sub = authUser.userId
let user = User(
id: sub,
username: username,
sub: sub,
postcode: nil,
createdAt: Temporal.DateTime.now()
)
// 3
_ = Amplify.API.mutate(request: .create(user)) { event in
switch event {
// 4
case .failure(let error):
signOut()
promise(.failure(error))
case .success(let result):
switch result {
case .failure(let error):
signOut()
promise(.failure(error))
case .success(let user):
// 5
setUserSessionData(user)
promise(.success(.signedIn))
}
}
}
This is what your code does:
- First, you get the current user from the Amplify Auth API. If no user is logged in, you return an error and sign out.
- You create a new
User
model object, setting theusername
for your user. You set bothid
andsub
to theuserId
from Cognito. - Then you write this user model record to DynamoDB by calling the
Amplify.API.mutate
API with acreate
request type. - You handle failures from the network layer and then the GraphQL layer, as in previous examples.
- Finally, you set the user session to your newly-created user and return a successful sign-in.
Build and run the app on a different simulator. Sign up as a new user. Confirm that the new user record appears in DynamoDB by refreshing the table in the DynamoDB tab in your browser.
Where to Go From Here?
Congratulations! You’ve used AWS Cognito to add sign-up and sign-in to your app. And you’ve used AWS AppSync to read and write data between your app and a database stored in the cloud, via GraphQL.
You can download the finished project using the Download Material buttons. Remember that you’ll need to perform the Amplify setup for this project, just as you did for the starter project.
You can refer to the Amplify Framework Documentation to find out more about the AWS services available via Amplify. Or check out Part 2 of this tutorial, Using AWS as a Back End: The Data Store & Analytics. In it, you’ll learn how to use the DataStore API to build the rest of the Isolation Nation chat app with real-time updates and user analytics.