Apple
인증된 제출자socialiteproviders/apple
Laravel Socialite용 Apple OAuth2 프로바이더
Apple
composer require socialiteproviders/apple설치 및 기본 사용법
기본 설치 가이드를 먼저 읽고, 아래의 Apple 프로바이더 설정을 진행하세요.
config/services.php에 설정 추가
'apple' => [
'client_id' => env('APPLE_CLIENT_ID'),
'client_secret' => env('APPLE_CLIENT_SECRET'),
'redirect' => env('APPLE_REDIRECT_URI')
],Apple ID 인증 설정을 참고하세요.
참고: "Sign In with Apple"에서 사용하는 클라이언트 시크릿은 유효 기간이 최대 6개월인 JWT 토큰입니다. 위 글에서 필요할 때 클라이언트 시크릿을 생성하는 방법을 설명하며, 6개월마다 갱신해야 합니다. 요청마다 생성하려면 요청마다 Sign In With Apple 클라이언트 시크릿 생성하기를 참고하세요.
시크릿 토큰이 없거나 수동으로 생성하고 싶지 않다면 개인 키를 사용할 수 있습니다(공식 문서). 다음과 같이 설정을 추가하세요.
'apple' => [
'client_id' => env('APPLE_CLIENT_ID'), // Required. Bundle ID from Identifier in Apple Developer.
'client_secret' => env('APPLE_CLIENT_SECRET'), // Empty. We create it from private key.
'key_id' => env('APPLE_KEY_ID'), // Required. Key ID from Keys in Apple Developer.
'team_id' => env('APPLE_TEAM_ID'), // Required. App ID Prefix from Identifier in Apple Developer.
'private_key' => env('APPLE_PRIVATE_KEY'), // Required. Must be absolute path, e.g. /var/www/cert/AuthKey_XYZ.p8
'passphrase' => env('APPLE_PASSPHRASE'), // Optional. Set if your private key have a passphrase.
'signer' => env('APPLE_SIGNER'), // Optional. Signer used for Configuration::forSymmetricSigner(). Default: \Lcobucci\JWT\Signer\Ecdsa\Sha256
'redirect' => env('APPLE_REDIRECT_URI'), // Required.
'jwt_issued_time_leeway' => env('APPLE_JWT_ISSUED_TIME_LEEWAY'), // Optional. Set this to add a leeway to your JWT issued_time value. See section below
],400 Bad Request {"error":"invalid_client"} 오류가 발생하면 다른 서명자(비대칭 알고리즘)를 사용하는 방법을 검토할 수 있습니다. 비대칭 알고리즘을 참고하세요.
프로바이더 이벤트 리스너 추가
Laravel 11+
Laravel 11에서는 기본 EventServiceProvider가 제거되었습니다. 대신 AppServiceProvider의 boot 메서드에서 Event 파사드의 listen 메서드로 리스너를 등록합니다.
- 참고: Socialite의 기본 제공 프로바이더는 직접 만든 프로바이더로 재정의하는 경우가 아니라면 별도로 등록할 필요가 없습니다.
Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) {
$event->extendSocialite('apple', \SocialiteProviders\Apple\Provider::class);
});Laravel 10 이하
패키지의 리스너가 SocialiteWasCalled 이벤트를 수신하도록 설정합니다.
app/Providers/EventServiceProvider의 listen[] 배열에 이벤트를 추가합니다. 자세한 방법은 기본 설치 가이드를 참고하세요.
protected $listen = [
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
// ... other providers
\SocialiteProviders\Apple\AppleExtendSocialite::class.'@handle',
],
];사용법
파사드가 설치되어 있다면 이제 일반적인 Socialite 사용 방식으로 프로바이더를 사용할 수 있습니다.
return Socialite::driver('apple')->redirect();콜백의 state와 nonce
Apple은 리다이렉트 URL로 사이트 간 POST 콜백을 보냅니다. 범위를 요청할 때 Apple이 요구하는 response_mode=form_post 방식입니다. 콜백에서 프로바이더는 state를 세션과 대조하고, 신원 토큰의 nonce를 리다이렉트 시 발급한 값과 대조합니다. 따라서 앱에서 시작하지 않은 콜백은 InvalidStateException으로 거부됩니다.
두 검증 모두 해당 POST 요청에 세션 쿠키가 함께 전달되어야 합니다. Laravel의 기본 SameSite=lax 쿠키는 사이트 간 POST에 전송되지 않으므로, 기본 설정에서는 세션이 비어 있는 것으로 인식해 모든 콜백을 거부합니다. 쿠키가 사이트 경계를 넘어 전달되도록 설정해야 합니다.
SESSION_SAME_SITE=none
SESSION_SECURE_COOKIE=trueSameSite=none 설정은 Apple용 쿠키뿐 아니라 앱에서 설정하는 모든 쿠키의 제한을 완화합니다. 다른 부분에서 lax를 유지하고 싶다면 아래의 cookieNonce()를 사용하는 무상태 흐름을 이용하세요.
세션 없이 사용하기(stateless)
콜백에서 세션을 유지할 수 없다면 cookieNonce()와 함께 무상태 흐름을 사용하세요. 프로바이더가 nonce를 처리합니다.
// Redirect. The provider generates the nonce, sends it to Apple, and sets it
// on the browser in an encrypted cookie.
return Socialite::driver('apple')->stateless()->cookieNonce()->redirect();
// Callback. The provider reads the nonce back from the cookie and verifies it
// against the identity token.
$user = Socialite::driver('apple')->stateless()->cookieNonce()->user();nonce는 Secure, HttpOnly, SameSite=none 속성을 가진 socialite_apple_nonce 쿠키로 전달됩니다. APP_KEY로 암호화되므로 클라이언트가 위조할 수 없습니다. 로그인을 시작한 브라우저만 이 쿠키를 가지므로, RFC 9700의 2.1절에서 요구하는 대로 인증 흐름이 해당 브라우저에 연결됩니다. 다른 브라우저에서 가로챈 콜백을 재전송하면 쿠키가 없어 거부되고, 변조된 쿠키 역시 거부됩니다. 세션, 캐시, 서버 측 상태는 사용하지 않습니다.
이 쿠키는 SameSite=none이므로 HTTPS가 필요합니다. 콜백을 TLS로 제공하지 않으면 브라우저가 쿠키를 버립니다. 모든 세션 쿠키의 제한을 완화하는 대신 전용 쿠키 하나를 사용하므로 앱의 나머지 부분은 lax를 유지할 수 있습니다.
쿠키와 로그인 시도의 유효 기간은 nonce_ttl로 변경합니다. 단위는 초이며 기본값은 600입니다.
'apple' => [
// ...
'nonce_ttl' => env('APPLE_NONCE_TTL', 600),
],nonce를 직접 관리하려면 생성한 값을 setNonce()에 전달하고, Apple에 보낸 뒤 콜백에서도 같은 값을 전달하세요. 이 두 방식 중 어느 것도 사용하지 않고 stateless()를 호출하면 보호되지 않은 콜백을 수락하는 대신 InvalidStateException이 발생합니다.
이미 신원 토큰을 전달하는 네이티브 iOS·Android 클라이언트는 아래의 userByIdentityToken()을 사용하세요.
네이티브 앱(신원 토큰)
네이티브 iOS·Android 클라이언트는 서버에 신원 토큰을 직접 전달합니다. Apple의 요구에 따라 검증할 수 있도록 인증 요청의 nonce도 전달하세요.
$user = Socialite::driver('apple')->userByIdentityToken($identityToken, $nonce);하위 호환성을 위해 nonce는 선택 사항이지만, 생략하면 토큰이 만료될 때까지 재사용 공격이 가능해집니다.
Apple은 요청한 클라이언트를 대상으로 신원 토큰을 발급합니다. 따라서 하나의 API가 여러 클라이언트를 지원하면 대상 audience도 여러 개가 됩니다.
| 클라이언트 | aud |
|---|---|
| 웹, Android(Sign in with Apple JS / REST) | Services ID |
| 네이티브 iOS, macOS | 해당 앱의 App ID(bundle ID) |
웹 흐름에 필요한 client_id는 Services ID로 유지하고, 나머지는 audiences에 나열하세요.
'apple' => [
// ...
'audiences' => ['com.example.app', 'com.example.app.macos'],
],userByIdentityToken()과 userFromToken()은 client_id 또는 audiences 중 하나를 대상으로 발급된 토큰을 수락합니다. 웹 콜백은 client_id만 수락합니다.
반환되는 사용자 필드
idnameemail
name은 서명된 신원 토큰이 아니라 콜백 POST의 user 필드에서 가져오며, Apple은 최초 인증 시에만 이 값을 보냅니다. 사용자 입력으로 취급하여 저장 전에 검증하고 정제하세요.
알려진 문제
JWT Issued_at
시간 차이로 인해 플러그인에서 예외가 발생할 수 있습니다(#1354 참고). config('services.apple.jwt_issued_time_leeway')로 시간을 앞당겨 차이를 허용할 수 있습니다. 기본값은 3초(PT3S)입니다.
예를 들어 PT3S는 3초, PT1M은 1분을 나타냅니다.
발생하는 예외는 다음과 같은 형태입니다.
[object] (Laravel\\Socialite\\Two\\InvalidStateException(code: 0): The token violates some mandatory constraints, details: - The token was issued in the future at /vendor/socialiteproviders/apple/Provider.php:207) [stacktrace]
유효하지 않은 audience
신원 토큰의 aud 클레임은 config('services.apple.client_id') 또는 config('services.apple.audiences')의 항목 중 하나와 일치해야 합니다. 네이티브 앱은 웹 흐름의 Services ID 대신 bundle ID를 audience로 전송합니다. 네이티브 앱 설명을 참고하세요.
발생하는 예외는 다음과 같습니다.
Laravel\Socialite\Two\InvalidStateException: The token violates some mandatory constraints, details:
- The token is not allowed to be used by this audience네이티브 앱의 audience 설정 요약
앞 구간의 네이티브 앱 설명대로 웹 흐름에 필요한 client_id는 Services ID로 유지하고, 네이티브 앱의 bundle ID를 audiences에 추가합니다. userByIdentityToken()과 userFromToken()은 이 대상들을 수락하지만 웹 콜백은 client_id만 수락합니다.