Реализация авторизации через Apple ID на сайте
Sign in with Apple — требование Apple для нативных iOS/macOS приложений: если приложение предлагает любую социальную авторизацию, Apple ID должен присутствовать в списке. Для веба это требование формально не обязательно, но реализация нужна там, где аудитория — пользователи Apple-устройств, а также для веб-части приложений с iOS-компонентом.
Apple ID имеет ряд особенностей, отличающих его от Google OAuth и прочих провайдеров:
- Пользователь может скрыть настоящий email — Apple выдаёт relay-адрес вида
[email protected] -
id_tokenвозвращается только при первой авторизации вместе с именем пользователя - Последующие входы не возвращают имя — его нужно сохранить при первом входе
- Нет refresh token в стандартном OAuth2-смысле
Регистрация приложения в Apple Developer
- Certificates, Identifiers & Profiles → Identifiers → создать App ID с включённым Sign In with Apple
- Создать Services ID (web-компонент) — указать домен и Redirect URL
- Создать Key с включённым Sign In with Apple — скачать
.p8файл (хранить безопасно, скачать можно только один раз) - Зафиксировать: Team ID, Client ID (= Services ID), Key ID
Генерация client_secret
Apple не использует статический секрет. client_secret — JWT, подписанный приватным ключом .p8:
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Ecdsa\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
function generateAppleClientSecret(): string
{
$config = Configuration::forAsymmetricSigner(
new Sha256(),
InMemory::file(storage_path('keys/apple_auth.p8')),
InMemory::empty()
);
return $config->builder()
->issuedBy(config('services.apple.team_id')) // iss: Team ID
->permittedFor('https://appleid.apple.com') // aud
->relatedTo(config('services.apple.client_id')) // sub: Services ID
->issuedAt(new \DateTimeImmutable())
->expiresAt(new \DateTimeImmutable('+6 months'))
->withHeader('kid', config('services.apple.key_id'))
->getToken($config->signer(), $config->signingKey())
->toString();
}
Срок действия до 6 месяцев. Токен пересоздаётся заранее через cron.
OAuth2 флоу
1. Редирект пользователя:
GET https://appleid.apple.com/auth/authorize
?client_id=com.example.web
&redirect_uri=https://example.com/auth/apple/callback
&response_type=code id_token
&response_mode=form_post
&scope=name email
&state=<random_string>
&nonce=<random_nonce>
2. Apple делает POST на redirect_uri с:
- code
- id_token
- state
- user (JSON с именем — только при первом входе!)
Важно: response_mode=form_post — Apple делает POST, не GET. Redirect URI должен принимать POST.
Обработка callback
public function handleCallback(Request $request): RedirectResponse
{
// Верификация state
abort_unless($request->state === session('apple_state'), 422);
// Декодирование id_token (без верификации подписи пока)
$idToken = $this->decodeIdToken($request->id_token);
// user приходит только при первом входе
$appleUser = $request->has('user')
? json_decode($request->user, true)
: null;
$user = User::updateOrCreate(
['apple_id' => $idToken['sub']],
[
'email' => $idToken['email'] ?? null,
'email_verified_at' => $idToken['email_verified'] ? now() : null,
// Имя сохраняем только если пришло (первый вход)
'name' => $appleUser
? trim(($appleUser['name']['firstName'] ?? '') . ' ' . ($appleUser['name']['lastName'] ?? ''))
: null,
]
);
// Обновляем имя только если оно не было установлено ранее
if ($appleUser && !$user->name) {
$user->update(['name' => ...]);
}
Auth::login($user);
return redirect()->intended('/dashboard');
}
Верификация id_token
Apple публикует публичные ключи по адресу https://appleid.apple.com/auth/keys. Верификация через JWT:
// composer require firebase/php-jwt
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
$keys = Cache::remember('apple_public_keys', 3600, function () {
return Http::get('https://appleid.apple.com/auth/keys')->json();
});
$payload = JWT::decode($idToken, JWK::parseKeySet($keys));
// Проверить: iss = appleid.apple.com, aud = client_id, exp, nonce
Relay email и ограничения
Если пользователь скрыл email, Apple выдаёт relay-адрес @privaterelay.appleid.com. Письма на него доходят только если домен зарегистрирован в Apple Developer Console → More → Configure Sign in with Apple for Email Communication.
Laravel Socialite
composer require laravel/socialite socialiteproviders/apple
// config/services.php
'apple' => [
'client_id' => env('APPLE_CLIENT_ID'),
'client_secret' => env('APPLE_CLIENT_SECRET'), // сгенерированный JWT
'redirect' => env('APPLE_REDIRECT_URI'),
],
Socialite Apple провайдер обрабатывает большинство деталей, но client_secret нужно периодически обновлять.
Сроки работ
| Этап | Время |
|---|---|
| Регистрация в Apple Developer | 0.5 дня |
| Генератор client_secret + cron | 1 день |
| OAuth callback + id_token верификация | 1.5 дня |
| Хранение relay email, обработка имени | 0.5 дня |
| Тесты + проверка на реальных устройствах | 1 день |
Итого: 4–5 рабочих дней.







