На WWDC 2019 Apple представила новую систему авторизации пользователей — Sign in with Apple. Возникла задача интегрировать её в наш back-end и синхронизировать её с уже существующими методами авторизации при помощи email, Google и Facebook. За задачу взялся наш коллега kurenkoff, он и является автором данной статьи. Заинтересовавшихся просим под кат.

Процедура достаточно примитивная и происходит точно так, как указано на схеме от Apple.
Помимо этого, Apple предоставляет возможность обновления токена:

Схема тоже достаточно простая, есть возможность делать верификацию токена, но мы не используем эту возможность, т.к. у нас нет в этом необходимости.
Для реализации авторизации через AppleID мы используем пакет appleLogin. Автором данного пакета допущены некоторые ошибки, но они не являются критическими (а некоторые были совместно исправлены). В первую очередь необходимо инициализировать конфиг при помощи данных полученных через портал разработчика Apple.
Затем получить токен:
Важно заметить, какой запрос отправляется на сервера Apple. В документации сказано, что для авторизации необходимо отправить поля client_id, client_secret, code, grant_type, redirect_uri. Все эти поля описаны как обязательные, но redirect_uri можно опустить. Основную сложность представляет client_secret — это JWT, подписанный ключом сгенерированным на портале WWDR:
API Apple ответит либо ошибкой, либо вернет структуру. Нас интересуют поле IDToken:
IDToken является JWT токеном содержащим данные пользователя:
Стоит обратить внимание на то, что email можно получить только при первой авторизации. При попытках повторной авторизации можно получить только ID (уникальный идентификатор пользователя в Sign in with Apple). Для регистрации пользователя нам достаточно этих данных.
Процедура регистрации и авторизации пользователя через Apple

Процедура достаточно примитивная и происходит точно так, как указано на схеме от Apple.
Помимо этого, Apple предоставляет возможность обновления токена:

Схема тоже достаточно простая, есть возможность делать верификацию токена, но мы не используем эту возможность, т.к. у нас нет в этом необходимости.
Реализация авторизации через AppleID
Для реализации авторизации через AppleID мы используем пакет appleLogin. Автором данного пакета допущены некоторые ошибки, но они не являются критическими (а некоторые были совместно исправлены). В первую очередь необходимо инициализировать конфиг при помощи данных полученных через портал разработчика Apple.
config := appleLogin.InitAppleConfig(
TeamID, // ID с сайта developer.apple.com, попросить у iOS разработчиков
ClientID, // Bundle вашего iOS приложения
callbackURI, // ссылка для редиректа при успешной авторизации
KeyID, // Сгенерированный ключ с сайта developer.apple.com, попросить у iOS разработчиков
)
privateKey := os.Getenv("PRIVATE_KEY")
err := config.LoadP8CertByByte([]byte(privateKey))
if err != nil {
return nil, err
}
Затем получить токен:
token, err := config.GetAppleToken(clientToken, tokenExpireTime)
if err != nil {
return nil, err
}
Важно заметить, какой запрос отправляется на сервера Apple. В документации сказано, что для авторизации необходимо отправить поля client_id, client_secret, code, grant_type, redirect_uri. Все эти поля описаны как обязательные, но redirect_uri можно опустить. Основную сложность представляет client_secret — это JWT, подписанный ключом сгенерированным на портале WWDR:
token := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{
"iss": a.TeamID,
"iat": time.Now().Unix(),
"exp": time.Now().Unix() + expireTime,
"aud": "https://appleid.apple.com",
"sub": a.ClientID,
})
token.Header = map[string]interface{}{
"kid": a.KeyID,
"alg": "ES256",
}
tokenString, _ := token.SignedString(a.AESCert)
API Apple ответит либо ошибкой, либо вернет структуру. Нас интересуют поле IDToken:
type AppleAuthToken struct {
AccessToken string json:"access_token" //AccessToken
ExpiresIn int64 json:"expires_in" //Expires in
IDToken string json:"id_token" //ID token
RefreshToken string json:"refresh_token" //RF token
TokenType string json:"token_type" //Token Type
}
IDToken является JWT токеном содержащим данные пользователя:
type AppleUser struct {
ID string json:"sub,omitempty"
Email string json:"email,omitempty"
EmailVerified bool json:"email_verified,string,omitempty"
}
Стоит обратить внимание на то, что email можно получить только при первой авторизации. При попытках повторной авторизации можно получить только ID (уникальный идентификатор пользователя в Sign in with Apple). Для регистрации пользователя нам достаточно этих данных.