Skip to content

План реализации

План реализации:

  • 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). Всегда Никогда не меняется. Основной ключ для поиска/создания пользователя в БД.
email 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 при каждом запросе.

Примерная схема взаимодействия#

scheme.png

Поля которые можно получить с 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 Нет