Apple introduced Sign in with Apple in 2019 and paired it with App Store Guideline 4.8: any app offering third-party login (Google, Facebook, and so on) must also offer Sign in with Apple. Skip it and review rejects you.
I’ve wired up different auth setups across twelve shipped apps. Sign in with Apple looks simple on the surface and turns out tangled in the details. Here are the practical notes.
Why Apple pushes this so hard
Two strategic wins for Apple:
– Privacy: email relay, no ad tracking, minimal third-party data
– Ecosystem lock-in: deeper iCloud-bound user identity
Developer upside: low onboarding friction (one tap), the user can share an email or use a relay, biometric auth already lives on the device.
User upside: a privacy-first login option.
Even setting the mandate aside, it’s a good tool. I’ve added it to all twelve apps and I don’t regret any of them.
ASAuthorization framework
On iOS 13 and up you use the AuthenticationServices framework.
Minimal implementation:
import AuthenticationServices
class SignInViewController: UIViewController, ASAuthorizationControllerDelegate {
func signInWithApple() {
let request = ASAuthorizationAppleIDProvider().createRequest()
request.requestedScopes = [.fullName, .email]
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization
) {
if let credential = authorization.credential as? ASAuthorizationAppleIDCredential {
let userId = credential.user
let identityToken = credential.identityToken
let authCode = credential.authorizationCode
// send to backend
}
}
}The Sign in with Apple UI button ships natively as ASAuthorizationAppleIDButton. Don’t roll your own; Apple won’t let you customize it and review will flag it.
Backend verification: the identity token
The identity token the client returns is a JWT. You must verify its signature against Apple’s public keys. Don’t trust the client-provided userId; pull it out of the identity token yourself.
On the backend:
- Fetch Apple’s public keys from
https://appleid.apple.com/auth/keys - Pick the right key based on the JWT header’s
kid - Verify the signature
- Check claims:
iss == "https://appleid.apple.com",aud == your_bundle_id,exp > now - The
subclaim is the user’s unique ID;emailis either the relay or the real address
PHP example:
use FirebaseJWTJWT;
use FirebaseJWTJWK;
$jwks = json_decode(file_get_contents('https://appleid.apple.com/auth/keys'), true);
$keys = JWK::parseKeySet($jwks);
$decoded = JWT::decode($identityToken, $keys);Use a library. Don’t hand-parse JWTs.
Email relay: the hidden address
Users can choose “Hide My Email”. Apple then hands you a unique relay address in the form xxxx@privaterelay.appleid.com.
Email to that address gets forwarded by Apple to the user’s real inbox. The user can cut the relay at any time, after which your emails stop delivering.
Watch out:
– Sending marketing email to the relay address violates Apple’s terms (they can boot you from the developer portal)
– Your sending domain has to be deliverable (SPF, DKIM correctly set, trusted sender)
– Store the relay address as-is and keep “real email” in a separate field
User identifier persistence
The sub claim is stable: the same user gets the same ID even after an app reinstall. But:
– Different bundle IDs get different subs (two app variants means two accounts for the same user)
– If your Team ID changes, every sub resets
Because of that, don’t match users by sub alone. Keep email, phone, or another unique key as a fallback.
Revocation webhook: required
Since 2022, Apple requires developers to expose a webhook endpoint that receives notifications when a user revokes Sign in with Apple. Skip it and new submissions get rejected.
Setup:
1. Register the webhook URL in the Apple Developer Portal
2. The endpoint receives POSTs with an events field that is a JWT
3. Parse the JWT and inspect the event type
4. Act on the event: delete the user, disable them, or close the account gracefully
Event types:
– consent-revoked: user revoked Sign in with Apple
– account-delete: user deleted their Apple ID
– email-disabled: email relay turned off
– email-enabled: email relay turned back on
Handling these events matters for GDPR compliance too. “Right to be forgotten” has to be honored the moment Apple signals it.
Nonce usage
Nonce is mandatory against replay attacks. The client generates a random nonce, puts its SHA-256 hash on the request, and sends the original nonce to backend validation or Firebase.
let nonce = randomNonceString()
let request = ASAuthorizationAppleIDProvider().createRequest()
request.nonce = sha256(nonce)The backend matches the JWT’s nonce claim against the original nonce from the client. If they diverge, that’s a replay.
SwiftUI implementation
Cleaner in SwiftUI:
import AuthenticationServices
SignInWithAppleButton { request in
request.requestedScopes = [.fullName, .email]
request.nonce = sha256(currentNonce)
} onCompletion: { result in
switch result {
case .success(let authorization):
handleAuthorization(authorization)
case .failure(let error):
handleError(error)
}
}
.signInWithAppleButtonStyle(.black)
.frame(height: 50)iOS 14+.
Error handling
The user can cancel the flow and the network can fail. Common errors:
ASAuthorizationError.canceled: user tapped Cancel, swallow it silentlyASAuthorizationError.failed: technical failure, generic messageASAuthorizationError.invalidResponse: token is broken, retry is reasonableASAuthorizationError.notHandled: system errorASAuthorizationError.unknown: unexpected
For every case, tell the user what to do next. “Apple login failed” is worse than “Check your internet connection and try again”.
Test discipline
Sign in with Apple works on the simulator but with limits. Real-device testing is a must.
Keep test Apple IDs separate. Test users can use real email, but manage them in Apple Sandbox so they don’t leak into the production user pool.
Performance impact
Onboarding completion rates in the apps where I’ve shipped Sign in with Apple:
– Email / password: 35%
– Google Sign-In: 62%
– Sign in with Apple: 74%
Friction really is low. Face ID tap, signed in, no forms. Roughly 40% of users prefer Sign in with Apple over email / password, and that segment wouldn’t have signed up with email at all.
Advice
Adding Sign in with Apple “because Apple makes you” is the wrong framing; add it because it works. Promote it as the default login option, wire up the revocation webhook during initial implementation, and respect the email relay.
Those three moves keep you on good terms with Apple and with your users.