Впровадження Sign in with Apple для веб-сайтів
Sign in with Apple — вимога Apple для нативних iOS/macOS додатків: якщо додаток пропонує будь-яку соціальну авторизацію, Apple ID має бути у списку. Для веба ця вимога формально не обов'язкова, але реалізація потрібна там, де аудиторія — користувачі Apple-пристроїв, а також для веб-частини додатків з iOS-компонентом.
Apple ID має ряд особливостей, що відрізняють його від Google OAuth та інших провайдерів:
- Користувач може приховати справжню адресу електронної пошти — 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 днів.







