Server-Side Sign in with Apple

Nov 15 2022 · Swift 5.6, macOS 12, iOS 15, Xcode 13.3

Part 1: Add Sign in with Apple to an iOS Project

02. Add Sign in with Apple to Your Vapor App

Episode complete

Play next episode

Next
About this episode
Leave a rating/review
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 01. Learn About Sign in with Apple Next episode: 03. Add Sign in with Apple to Your iOS App
Transcript: 02. Add Sign in with Apple to Your Vapor App

In this video we’re going to set up a Vapor app to handle Sign in with Apple from an iOS device. Let’s get started!

Adding JWT

First open TILApp in the starter project for this course in Xcode and open Package.swift. In the packages dependencies array, add a new dependency for Vapor’s JWT library:

.package(url: "https://github.com/vapor/jwt.git", from: "4.0.0"),

As you learned in the previous video, Sign in with Apple generates JWTs as a way to authenticate users. Next, add the JWT library as a dependency to the App target:

.product(name: "JWT", package: "jwt"),

Save the file with CMD+S and Xcode will pull in the new dependency. Next, open UsersController.swift. First, you need to create a new type to represent the data the iOS app will send to your Vapor app:

struct SignInWithAppleToken: Content {
  let token: String
  let name: String?
}

The token is the JWT from iOS. First import JWT:

import JWT

Then, create a new route handler for signing in with Apple. This will return a Token when complete, which is a bearer token used throughout the API to authenticate users:

func signInWithApple(_ req: Request) async throws -> Token {

}

Next, decode the request body to the new type:

let data = try req.content.decode(SignInWithAppleToken.self)

Then, get the app identifier from the environment variables and if it can’t be found throw an internal server error:

guard let appIdentifier = Environment.get("IOS_APPLICATION_IDENTIFIER") else {
    throw Abort(.internalServerError)
}

This is the bundle identifier of the iOS app. Use Vapor’s JWT helpers to verify the token from iOS:

let siwaToken = try await req.jwt.apple.verify(data.token, applicationIdentifier: appIdentifier)

This takes the JWT, gets the public certificate from Apple and make sure the token is valid. Next, perform a query to see if a user using this Sign in With Apple ID already exists - I.E. this is a returning user or if it’s a new user:

let user: User
if let userFound = try await User.query(on: req.db).filter(\.$siwaIdentifier == siwaToken.subject.value).first() {
    user = userFound
} else {
    
}

If this is a new user, get the email and name and then create and save the new user:

guard let email = siwaToken.email, let name = data.name else {
    throw Abort(.badRequest)
}
let newUser = User(name: name, username: email, password: UUID().uuidString, siwaIdentifier: siwaToken.subject.value)
try await newUser.save(on: req.db)
user = newUser

Since the user is using Sign In With Apple to authenticate, create a random string using UUID for their password. Finally, create and save a token for the user and return it:

let token = try Token.generate(for: user)
try await token.save(on: req.db)
return token

The iOS app uses the token for all authenticated requests to the API. Register the new route as a POST request to /api/user/siwa:

usersRoutes.post("siwa", use: signInWithApple)

Now users can authenticate with either a username and password or Sign In With Apple and use the same authentication token type for accessing the API. Build the app to make sure everything compiles.