When a user signs in with Apple on the web, they authenticate with Apple servers.
When this finishes, Apple will redirect the browser back to your app using the redirect URL you set up in the developer portal.
If the redirect URL does not match the redirect URL set up in the portal and the redirect URL passed to the Sign in with Apple JavaScript,
You’ll get an error. The redirect makes things a little complicated however because of SameSite protections in cookies. In modern browsers, cookies have a flag that dictates if the browser can load them.
By default, cookies use the ‘Lax’ SameSite setting, which means that cookies will be loaded when the user took an action to navigate to a site (such as clicking on a link). However, redirects using a POST request are not part of this.
This is great because it stops people tricking you into redirecting to a page that submits a form to make a bank transfer!
However, it presents a problem for the Sign in with Apple flow, because when the redirect occurs, the default Vapor cookies, such as whether a user is logged in aren’t available. In order for cookies to be loaded on a POST request originating from another domain you need to use the .none SameSite setting.
The trick to workaround this is to split it up into two stages. The first is the callback handler. That loads the state cookie you created in the previous video using the none SameSite setting. You can decode the JSON sent in the callback and do some basic checks.
If they pass, you can load a page that redirects to a new page. Because this redirect comes from the SameSite domain and it is triggered by code on the page on your domain, all cookies will be loaded. You can then get the signed in user and complete the registration or log in.
Time to implement the handler for the callback! In the Vapor app, open WebsiteController.swift and create a new route handler called appleAuthCallbackHandler(_:) that returns a View:
func appleAuthCallbackHandler(_ req: Request) async throws -> View {
}
First, decode the incoming data to AppleAuthorizationResponse:
let siwaData = try req.content.decode(AppleAuthorizationResponse.self)
Then, get the state from the cookie and check it matches the state returned from Apple:
guard let sessionState = req.cookies["SIWA_STATE"]?.string, !sessionState.isEmpty, sessionState == siwaData.state else {
req.logger.warning("SIWA does not exist or does not match")
throw Abort(.unauthorized)
}
Next, define a context at the bottom of the page to pass to the View to redirect to the next page. This will be a form submission with all of the data you need for registering or logging in:
struct SIWAHandleContext: Encodable {
let token: String
let email: String?
let firstName: String?
let lastName: String?
}
Then, back in the callback handler appleAuthCallbackHandler, create the context using the data returned from Apple and load a template called siwaHandler:
let context = SIWAHandleContext(token: siwaData.idToken, email: siwaData.user?.email, firstName: siwaData.user?.name?.firstName, lastName: siwaData.user?.name?.lastName)
return try await req.view.render("siwaHandler", context)
Register the route in boot(_:):
authSessionsRoutes.post("login", "siwa", "callback", use: appleAuthCallbackHandler)
Finally, create the template siwaHandler.leaf in Resources/Views. Create a basic HTML page:
<!doctype html>
<html lang="en" class="h-100">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sign in With Apple | Southdown Marketplace</title>
</head>
<body class="d-flex flex-column h-100">
</body>
</html>
You can add some styling to the body in case the automatic submission of the form doesn’t work. In the <head> create a script to submit the form to the Vapor backend:
<script>
function handleCallback() {
const form = document.getElementById("siwaRedirectForm")
form.style.display = 'none';
form.submit();
}
window.onload = handleCallback;
</script>
This gets the form using the ID, hides it and submits it. It also tells the browser to call the function as soon as the page loads.
Then in the body, create the form using the data passed in. Set the action to /login/siwa/handle - you’ll define this in the next video:
<form action="/login/siwa/handle" method="POST" id="siwaRedirectForm">
</form>
Define a hidden input for each piece of data needed, using the data from the context passed in:
<input type="hidden" name="token" value="#(token)">
<input type="hidden" name="email" value="#(email)">
<input type="hidden" name="firstName" value="#(firstName)">
<input type="hidden" name="lastName" value="#(lastName)">
Finally, create a submit button:
<input type="submit" value="If nothing happens click here">
This is a fallback in case the JavaScript doesn’t trigger.