Розробка авторизації через Apple ID (Sign in with Apple)
З 2019 року Apple вимагає: якщо в iOS-додатку є авторизація через сторонні identity-провайдери (Google, Facebook, VK), — Sign in with Apple обов'язковий. Rejection по 4.8.0 у App Store Review — частою історія у команд, які інтегрували Google Sign-In, але проігнорували Apple. Це не просто вимога ревю — це хороша авторизація з privacy-центричним дизайном.
Що робить Sign in with Apple особливим
Apple не вертає реальний email користувача додатку — якщо користувач вибрав скривання email, додаток отримує relay-адреса вида [email protected]. Листи на цю адресу Apple перенаправляє на реальну. Якщо користувач відкличе доступ додатку — relay припиняє працювати.
Ім'я та email передаються тільки один раз — при першій авторизації. Якщо ви не зберегли їх у своїй базі — при повторному вході Apple їх не вертане. Це не баг, це намірне рішення Apple. Команди, які не знали про це, виявляли проблему в продакшені: користувачі не можуть повторно увійти тому що backend не зберіг email при першій авторизації.
Реалізація на iOS
import AuthenticationServices
// Запит авторизації
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
// Обробка результату
func authorizationController(controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization) {
guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else { return }
let userID = credential.user // Стійний ідентифікатор користувача
let identityToken = credential.identityToken // JWT для верифікації на сервері
let authorizationCode = credential.authorizationCode // Для обміну на refresh token
// email та fullName доступні ТІЛЬКИ при першій авторизації
let email = credential.email
let fullName = credential.fullName
}
credential.user — стійкий ідентифікатор, унікальний для пари (користувач, додаток). Не використовується для ідентифікації користувача між додатками однієї команди (для цього — identityToken з sub claim).
Верифікація на сервері
Клієнт передає identityToken (JWT) на backend. Сервер верифікує:
- Підпис токена публічним ключем Apple (ключи за
https://appleid.apple.com/auth/keys) -
audclaim співпадає з Bundle ID додатку -
iss=https://appleid.apple.com -
expне істекло
Після першої авторизації authorizationCode обмінюється на refresh_token через Apple's token endpoint. refresh_token зберігається на сервері й використовується для перевірки, чи користувач не відкликав доступ додатку.
Перевірка стану авторизації при кожному запуску додатку:
let appleIDProvider = ASAuthorizationAppleIDProvider()
appleIDProvider.getCredentialState(forUserID: savedUserID) { state, error in
switch state {
case .authorized: break // Все добре
case .revoked: // Користувач відкликав доступ — розлогиніиуємо
case .notFound: // Перший вхід або дані видалені
}
}
Інтеграція на Android та Web
Apple вимагає підтримки Sign in with Apple на всіх платформах, де є додаток. На Android — через OIDC flow у браузері (нема нативного SDK): відкриваємо ASWebAuthenticationSession аналог через Custom Tab, авторизація через Apple's OAuth endpoint, отримуємо code у redirect URI.
Retrofit / OkHttp на Android: звичайний OAuth2 PKCE flow з Apple's authorization endpoint. Apple не надає Android SDK — тільки веб-браузерний flow.
Типові помилки
- Не зберегли email при першій авторизації — при спробі «відновити аккаунт» користувач не може, тому що email невідомий
- Не реалізували перевірку revocation — користувач відкликав доступ, але в додатку залишається залогінений
- Не налаштували Service ID для веб/Android flow —
invalid_clientпомилка при OAuth2
Терміни: від 1 до 2 тижнів. Включає нативну iOS реалізацію, серверну верифікацію JWT, зберігання relay email, перевірку статусу при запуску, опціонально — Android OAuth2 flow.







