Karhin’s Blog
About technologies and life

"Вход через Apple" был представлен в 2019 году и стал обязательным для всех приложений, которые используют авторизацию через социальные сети или внешние сервисы.

Я реализовывал функцию несколько раз и набил пару шишек на ней. Перед тем, как её реализовывать, хорошо подумайте, потому что самый главный вывод, который я сделал: не связывайтесь с авторизацией через Apple.

iOS приложение

Здесь нет ничего сложного, но есть некоторые моменты, на которые стоит обратить внимание. Для начала добавьте в настройках проекта одноименное разрешение.

В 2022 году при авторизации можно попросить полное имя и электронную почту.

Первый важный момент: запрашиваемые данные передаются лишь однажды при первом входе, поэтому обязательно сохраните их в Keychain. Если ваш сервер или сеть отвалится и вы не сможете с первого раза отправить эти данные, то боюсь, что ваш пользователь больше не сможет войти.

Для работы с Keychain я использую KeychainWrapper. Репозиторий давно не обновлялся, но и в API ничего не менялось.

Если вы всё-таки запрашивали данные, но потеряли их, то вход можно сбросить через настройки: Settings → Apple ID → Password & Security → Apps Using Apple ID → Stop using Apple ID.

Второй важный момент: это не работает в симуляторе. В iOS 16 может починили, но я не проверял.

Третий важный момент: в Catalyst кнопка из SwiftUI не работает и приложение просто падает. Багу сто лет. Может быть его уже исправили, но я не проверял.

В SwiftUI добавить кнопку очень просто. Не забудьте импортировать AuthenticationServices.

SignInWithAppleButton(.continue, onRequest: { request in request.requestedScopes = [.email, .fullName] }, onCompletion: { result in switch result { case .success(let auth): // handle it case .failure(let error): // ¯\_(ツ)_/¯ print(error) } })

В UIKit тоже ничего сложного нет, но придётся написать чуть больше кода. Создать фирменную кнопку можно примерно вот так.

private lazy var signInButton: ASAuthorizationAppleIDButton = { let button = ASAuthorizationAppleIDButton( authorizationButtonType: .continue, authorizationButtonStyle: traitCollection.userInterfaceStyle == .dark ? .white : .black ) button.cornerRadius = 13.0 button.translatesAutoresizingMaskIntoConstraints = false button.addTarget(self, action: #selector(didTapSignInButton), for: .touchUpInside) return button }()

При нажатии создаём запрос на авторизацию и ASAuthorizationController, которому нужно указать делегата.

@objc private func didTapSignInButton() { let request = ASAuthorizationAppleIDProvider().createRequest() request.requestedScopes = [.email] let controller = ASAuthorizationController(authorizationRequests: [ request ]) controller.delegate = self controller.presentationContextProvider = self controller.performRequests() } extension ViewController: ASAuthorizationControllerDelegate { func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { print(error) } func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else { return } // handle it } }

Что передавать на сервер?

Из объекта credential вам обязательно нужно достать identityToken и authorizationCode. Они необходимы для проверки токена на стороне сервера.

Эти объекты можно просто перевести в строки и передать как есть.

guard let identityTokenData = credential.identityToken else { return } guard let authorizationCodeData = credential.authorizationCode else { return } guard let identityToken = String(data: identityTokenData, encoding: .utf8) else { return } guard let authorizationCode = String(data: authorizationCodeData, encoding: .utf8) else { return }

Identity Token содержит всю самую важную информацию: уникальный идентификатор пользователя, email и fullName.

Серверная часть

Если для вашего языка программирования есть хорошая библиотека для валидации токенов от Apple, то поздравляю, можете её взять и использовать. Скорее всего, её нет.

Четвёртый важный момент: c 30 июня 2022 года все приложения должны предоставлять функцию удаления аккаунта в приложении. Это требование напрямую касается всех, кто использует вход через Apple, так как необходимо отозвать токены, а нормального способа это сделать не существует.

Перед тем, как вы сможете валидировать токены, необходимо получить ваш приватный ключ в Apple Developer. Если вы отправляете пуши, то у вас он скорее всего есть.

Apple Developer → Certificates, Identifiers & Profiles → Keys. Не забудьте включить сервис Sign In with Apple.

Сохраните ключ в безопасное место и не делайте хардкод. Вместе с ключом вам нужно держать рядом Key ID, Team ID и Bundle ID вашего приложения.

Генерируем токен для сервера

Перед этим шагом поищите библиотеку для JWT, если у вас её ещё нет.

Он нужен для того, чтобы отправлять запросы на сервера Apple. Представляет из себя обычный JSON Web Token (JWT) в котором указаны Team ID и Bundle ID. Подписать его нужно вашим приватным ключом, который вы скачали у Apple.

В документации более подробно расписано про все поля. Ниже пример пейлоуда на Go, а вот пример генерации на питоне.

Для подписи используйте ES256.

type SecretKeyPayload struct { Iss string `json:"iss"` Iat int64 `json:"iat"` Exp int64 `json:"exp"` Aud string `json:"aud"` Sub string `json:"sub"` }

Проверяем Identity Token

Сначала нужно скачать JSON Web Key (JWK) от Apple, которыми подписываются токены для авторизации. Адрес с ключами ниже.

https://appleid.apple.com/auth/keys

Не все библиотеки для JWT поддерживают JWK, но в этом нет ничего страшного. JWK представляет из себя просто массив ключей. Достаточно разобрать заголовок из JWT и найти публичный ключ у которого такой же kid.

Учитывайте, что ключи очень часто обновляются. Количество ключей и они сами постоянно меняются.

identityToken, который прислал нам iOS клиент, представляет из себя как раз таки JWT, который подписан одним из этих ключей.

Не забудьте дополнительно проверить следующие поля:

Генерируем Refresh Token

На этом шаге нам пригодится приватный ключ сервера, который мы уже сгенерировали и подписали ранее. Более подробная документация доступна на сайте Apple.

https://appleid.apple.com/auth/token

На этот адрес отправляем запрос с следующей формдатой:

В ответ должен прилететь JSON с следующей структурой. Не забудьте проверить HTTP статус, он должен быть 200.

type GeneratedTokenResponse struct { AccessToken string `json:"access_token"` TokenType string `json:"token_type"` ExpiresIn int64 `json:"expires_in"` RefreshToken string `json:"refresh_token"` IdToken string `json:"id_token"` }

В ответе можно найти id_token, это новый железобетонный identityToken, который уже точно можно использовать внутри приложения.

А что дальше?

Можно добавить нового пользователя в систему и сгенерировать свой собственный токен, чтобы отдать его клиенту. Вы же не хотите такой ерундой заниматься каждый раз на эппловых токенах?

Пятый важный момент: обязательно сохраните в базу данных refresh_token и access_token. Желательно с привязкой к вашим внутренним сессиям.

Зачем это нужно? Я не знаю, спросите у Тима Кука или напишите петицию, что у лучших инженеров какое-то абсолютно дебильное API с ненужными действиями, потому что вы ничего не сможете сделать с этими токенами, кроме как отозвать или провалидировать.

Обновляем Access Token

В документации написано, что делать это нужно раз в сутки.

Адрес совпадает с генерацией, но немного меняются данные:

Отзываем токены

Видимо, их стоит отзывать, когда пользователь разлогинивается в приложении. А ещё нужно отозвать все токены, которые нам выдал Apple, если пользователь решил удалить аккаунт в приложении. Сделать это можно только по одному за раз.

https://appleid.apple.com/auth/revoke

На этот адрес отправляем запрос с следующей формдатой:

В случае успеха возвращается 200-ый код с пустым телом. В общем, на ответ можно просто забить.

Заключение

Я бы хотел написать, что авторизация через Apple – это круто, но нет.

Самый главные подводные камни:

Digital Apple iOS Swift SwiftUI UIKit Программирование

To post a comment, please log in or create an account.

Sign In

DALL-E: я снова верю в технологии
DALL-E: я снова верю в технологии

Попробовал новую систему для превращения текста в картинки. Показываю, что получилось.

312
Первый iPhone
Первый iPhone

Возле первого айфона часто вижу приписки «Революционный», «Прорывной» и так далее. У меня совсем другое мнение по этому поводу.

818
Как слушать YouTube видео в фоне и смотреть картинку-в-картинке без подписки на iOS
Как слушать YouTube видео в фоне и смотреть картинку-в-картинке без подписки на iOS

YouTube предлагает оформить для доступа к этим функциям подписку за несколько баксов, но есть секретный и даже легальный способ делать это бесплатно.

895
Красивый Email за десять минут
Красивый Email за десять минут

Собственный домен для почты – это не привилегия бизнеса или программистов. Сделать его проще простого, но нужно потратить десять минут.

847