План реализации
План реализации:
- Cделать настройки в Apple Developer Settings
- Реализация фронта
- Реализация бэкенда
Настройки Apple Developer Settings#
У каждого девелопера есть TeamID - это идентификатор команды. Служит для подтверждения подлинности владения приложением.
Если отсутствует, то необходимо создать AppID - идентификатор ВСЕГО приложения. В нём необходимо включить функцию SignIn with AppleID.
Далее создаём ServicesID (client_id) - идентификатор ручки, которая будет проводить авторизацию (идентификатор сервиса). Там, точно также, как и в предыдущем пункте, необходимо включить SignIn with AppleID, в разделе Configure - явно выбрать нужный AppID из предыдущего шага.
После всего этого необходимо создать PrivateKey (.p8) и получить KeyID - идентификатор PrivateKey. Он также будет привязан к AppID. Скачать его можно всего один раз, сразу после получения, далее - его никак не получить (не скачать).
Это все ключи, которые нужно получить для реализации регистрации/авторизации.
Более наглядно можно посмотреть здесь
Реализация фронта#
Необходимо использовать фреймворк для аутентификации от Apple (конкретно для Swift) - AuthenticationServices. Там должна быть встроенная кнопка, что-то типа Войти с помощью AppleID при нажатии на неё создаётся запрос к Apple. Для Sign In with Apple JS (веб-версии) всё примерно тоже самое - существует официальный фреймворк от Apple, в котором есть всё необходимое.
Модель request - ASAuthorizationAppleIDRequest#
| Свойство | Тип данных | Что означает | Обязательно | Примечание |
|---|---|---|---|---|
| requestedScopes | [ASAuthorization.Scope]? | Массив запрашиваемых данных пользователя. Возможные значения: .fullName, .email | Нет | Если не указать — ничего кроме user и identityToken не придёт. Только первый раз возвращаются реальные значения email/fullName. |
| nonce | String? | Защита от replay-атак. Передаётся хэш (обычно SHA-256 от случайной строки). Apple возвращает его обратно в claims JWT nonce. | Рекомендуется | Проверяется на бэкенде. Если совпадает — токен свежий. |
| state | String? | Произвольная строка для защиты от CSRF / привязки ответа к сессии. Apple возвращает её обратно без изменений в credential.state | Рекомендуется | Проверяется на клиенте. |
| requestedOperation | ASAuthorization.OpenIDOperation | Тип операции OpenID. Возможные значения: .login (по умолчанию), .refresh, .logout и др. | Нет | - |
| user | String? | Идентификатор пользователя (sub), который был получен ранее. Используется при повторной авторизации / переносе аккаунта / refresh. | Нет (редко) | Если задать — Apple может предзаполнить некоторые поля. |
| authorizationCodeExpiresIn | TimeInterval? | Запрашиваемое время жизни authorization code (в секундах). Apple может игнорировать. | Нет | - |
Модель response - ASAuthorizationAppleIDCredential#
| Свойство | Тип данных | Что означает / для чего | Когда доступно | Примечание / пример |
|---|---|---|---|---|
| identityToken | Data? | JWT-токен (id_token), подписанный Apple. Содержит sub, aud, iss, exp, nonce и т.д. | Всегда при успехе | Главный элемент. Преобразовать в строку: String(data: ..., encoding: .utf8) |
| authorizationCode | Data? | Одноразовый код для обмена на токены (grant_type=authorization_code). | Почти всегда | Живёт ~5–10 минут. Преобразовать в строку аналогично. |
| user | String | Уникальный идентификатор пользователя у Apple (sub / subject). | Всегда | Никогда не меняется. Основной ключ для поиска/создания пользователя в БД. |
| String? | Email пользователя (реальный или приватный relay @privaterelay.appleid.com) | Только при первом логине + scope .email |
После первого раза → nil (даже если пользователь не скрывал). | |
| fullName | PersonNameComponents? | Имя/фамилия/отчество пользователя (структура с полями givenName, familyName и др.) | Только при первом логине + scope .fullName |
После первого раза → nil. |
| state | String? | То же, что мы передали в запросе (для проверки CSRF). | Если задавали в запросе | Проверяется на клиенте. |
| authorizedScopes | [ASAuthorization.Scope] | Список scope, которые пользователь одобрил (может быть подмножеством requestedScopes). | Всегда | Полезно, если пользователь отказался делиться именем/email. |
| realUserStatus | ASAuthorization.AppleIDRealUserStatus | Оценка, реальный ли человек (.likelyReal, .unknown, .notReal). |
Всегда | Для антифрода. |
Далее эти данные нужно отправить на бэк POST-запросом на соответствующую ручку.
Реализация backend#
Получаем данные с фронта, примерно что-то такое:
{
"identity_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkZ...",
"authorization_code": "c3f8a...9d1e",
"user": "000123.abc456def789...",
"email": "...", // только первый раз
"first_name": "...", // только первый раз
"last_name": "..." // только первый раз
}
Нужно обязательно сделать валидацию identity_token. Что нужно проверить:
- Подпись (signature) с помощью публичных ключей Apple (JWKS)
- iss == "https://appleid.apple.com"
- aud == наш ServicesID (client_id)
- exp не истёк
- iat разумный (не из будущего)
- sub присутствует и не пустой
- Если использовали nonce на клиенте → проверить совпадение с nonce в claims
Один из способов - воспользоваться этим фреймворком. После успешной валидации у нас будут проверенные Claims:
- sub — вечный уникальный ID пользователя у Apple (это ключевой идентификатор).
- email — может быть пустым после первого входа.
- is_private_email — флаг скрытого email.
- aud — должен быть равен твоему Services ID.
- nonce — если использовали.
Далее, если пользователя не существует - его необходимо его создать. apple_sub желательно сохранять как основной идентификатор для Apple-пользователей.
После этого генерируем свой токен, который пользователь будет передавать в заголовке Authorization при каждом запросе.
Примерная схема взаимодействия#

Поля которые можно получить с frontend'a#
По сути только то, что вернёт Apple Auth Service.
| Поле | Тип | Обязательно? |
|---|---|---|
identity_token |
string | Да |
authorization_code |
string | Нет |
user / sub |
string | Рекомендуется |
email |
string? | Нет |
first_name |
string? | Нет |
last_name |
string? | Нет |
full_name |
string? | Нет |
Поля которые может вернуть backend#
Любые поля, полученные при валидации токена (claims), а так же что-то опциональное, например какие-либо флаги, время жизни токена и т.д.
| Поле | Тип | Обязательно? |
|---|---|---|
access_token |
string | Да |
refresh_token |
string | Нет |