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.