我尝试在 Laravel 项目中验证 Apple 登录,但遇到错误。有人可以帮助我吗?我什至还提供了密钥 ID 和服务 ID 的屏幕截图(已修改敏感信息)。
{成功:错误,消息:无法与Apple验证,错误:{错误:invalid_client}}
我的 .env 文件
APPLE_CLIENT_ID=com.example.apple
APPLE_TEAM_ID=NR88888888
APPLE_KEY_ID=RJ85222222
这是我的函数(我必须修剪一些部分,因为 StackOverflow 不允许发布整个函数)。
函数loginWithApple(请求$请求) { 尝试 { $authCode = $request->input('apple_authorization_code'); 如果(空($authCode)) { 返回响应()->json([ '成功' => 假, 'message' => '需要授权码' ], 400); }
// Load and validate private key file
$path = storage_path('appleKeys/applePrivateKey/AuthKey_RJ85222222.p8');
if (!file_exists($path))
{
Log::error('Apple Login: Private key file not found', ['path' => $path]);
throw new \Exception('Apple private key file not found at: ' . $path);
}
$privateKeyContent = file_get_contents($path);
if (!$privateKeyContent)
{
Log::error('Apple Login: Failed to read private key file');
throw new \Exception('Failed to read private key file');
}
// Ensure private key format
$privateKey = openssl_pkey_get_private($privateKeyContent);
if (!$privateKey)
{
Log::error('Apple Login: Invalid private key format', [
'openssl_error' => openssl_error_string()
]);
throw new \Exception('Invalid private key format: ' . openssl_error_string());
}
// Generate client secret (JWT)
$timestamp = time();
$header = [
'alg' => 'ES256',
'kid' => env('APPLE_KEY_ID')
];
$payload = [
'iss' => env('APPLE_TEAM_ID'),
'iat' => $timestamp,
'exp' => $timestamp + 86400 * 180, // 180 days
'aud' => 'https://appleid.apple.com',
'sub' => env('APPLE_CLIENT_ID'),
];
// Base64Url encode header and payload
$base64Header = rtrim(strtr(base64_encode(json_encode($header)), '+/', '-_'), '=');
$base64Payload = rtrim(strtr(base64_encode(json_encode($payload)), '+/', '-_'), '=');
// Create signature
$signature = '';
if (!openssl_sign(
$base64Header . '.' . $base64Payload,
$signature,
$privateKey,
OPENSSL_ALGO_SHA256
)) {
Log::error('Apple Login: Failed to create signature', [
'openssl_error' => openssl_error_string()
]);
throw new \Exception('Failed to create signature: ' . openssl_error_string());
}
$base64Signature = rtrim(strtr(base64_encode($signature), '+/', '-_'), '=');
$clientSecret = $base64Header . '.' . $base64Payload . '.' . $base64Signature;
// Send request to Apple for token
$requestData = [
'client_id' => env('APPLE_CLIENT_ID'),
'client_secret' => $clientSecret,
'code' => $authCode,
'grant_type' => 'authorization_code'
];
Log::debug('Apple Login: Sending request to Apple', [
'url' => 'https://appleid.apple.com/auth/token',
'client_id' => env('APPLE_CLIENT_ID'),
'code' => $authCode,
'code_length' => strlen($authCode),
]);
// $response = Http::asForm()->post('https://appleid.apple.com/auth/token', $requestData);
$response = Http::withHeaders([
'Content-Type' => 'application/x-www-form-urlencoded'
])->post('https://appleid.apple.com/auth/token', $requestData);
if (!$response->successful())
{
Log::error('Apple Login: Failed response from Apple', [
'status' => $response->status(),
'error' => $response->json(),
]);
return response()->json([
'success' => false,
'message' => 'Failed to verify with Apple',
'error' => $response->json()
], 400);
}
$data = $response->json();
$idToken = $data['id_token'];
$tokenParts = explode('.', $idToken);
if (count($tokenParts) != 3) {
return response()->json([
'success' => false,
'message' => 'Invalid token format'
], 400);
}
}
catch (\Exception $e)
{
Log::error('Apple Sign In Error', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
}
}
这个问题很棘手,正如here和here所示,问题实际上出在openSSL生成的签名上。您可以在那里阅读更多相关信息,但就我而言,我只使用了 Firebase JWT 库。
因此,对于 Apple Sign In,我们需要使用 ES256(带有 SHA-256 的 ECDSA)进行 JWT 签名。这是使用 firebase/php-jwt 库的更简单、更可靠的实现,它支持 ES256:
确保安装 Firebase JWT 库
作曲家需要 firebase/php-jwt
然后更新
use Firebase\JWT\JWT;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
// YOUR PROBLEM CAN BE SOLVED HERE
// Create client secret JWT
$clientSecret = JWT::encode([
'iss' => env('APPLE_TEAM_ID'),
'iat' => time(),
'exp' => time() + 86400 * 180, // 180 days
'aud' => 'https://appleid.apple.com',
'sub' => env('APPLE_CLIENT_ID'),
], $privateKey, 'ES256', env('APPLE_KEY_ID'));
// Exchange auth code for tokens
$response = Http::asForm()->post('https://appleid.apple.com/auth/token', [
'client_id' => env('APPLE_CLIENT_ID'),
'client_secret' => $clientSecret,
'code' => $authCode,
'grant_type' => 'authorization_code'
]);
这是一个完整的重构函数
use Firebase\JWT\JWT;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
function loginWithApple(Request $request) {
try {
// Validate request
$authCode = $request->input('apple_authorization_code');
if (empty($authCode)) {
return response()->json([
'success' => false,
'message' => 'Authorization code is required'
], 400);
}
// Load private key from Laravel storage location
$privateKeyPath = storage_path('appleKeys/applePrivateKey/AuthKey_' . env('APPLE_KEY_ID') . '.p8');
$privateKey = file_get_contents($privateKeyPath);
if (!$privateKey) {
throw new Exception('Could not read private key');
}
// Create client secret JWT
$clientSecret = JWT::encode([
'iss' => env('APPLE_TEAM_ID'),
'iat' => time(),
'exp' => time() + 86400 * 180, // 180 days
'aud' => 'https://appleid.apple.com',
'sub' => env('APPLE_CLIENT_ID'),
], $privateKey, 'ES256', env('APPLE_KEY_ID'));
// Exchange auth code for tokens
$response = Http::asForm()->post('https://appleid.apple.com/auth/token', [
'client_id' => env('APPLE_CLIENT_ID'),
'client_secret' => $clientSecret,
'code' => $authCode,
'grant_type' => 'authorization_code'
]);
if (!$response->successful()) {
Log::error('Apple Token Exchange Error', [
'status' => $response->status(),
'error' => $response->json()
]);
return response()->json([
'success' => false,
'message' => 'Failed to verify with Apple',
'error' => $response->json()
], 400);
}
// Get user data from ID token
$data = $response->json();
$idToken = $data['id_token'];
$tokenParts = explode('.', $idToken);
if (count($tokenParts) != 3) {
throw new Exception('Invalid token format');
}
// Decode payload
$payload = base64_decode(str_pad(strtr($tokenParts[1], '-_', '+/'), strlen($tokenParts[1]) % 4, '=', STR_PAD_RIGHT));
$userData = json_decode($payload, true);
if (!$userData) {
throw new Exception('Failed to decode token payload');
}
// Verify token expiry
if (time() > $userData['exp']) {
throw new Exception('Token has expired');
}
// Return user data
$user = $request->input('user') ? json_decode($request->input('user'), true) : null;
return response()->json([
'success' => true,
'message' => 'Apple Sign In successful',
'user_data' => [
'apple_user_id' => $userData['sub'],
'email' => $userData['email'] ?? null,
'email_verified' => $userData['email_verified'] ?? false,
'name' => $user['name'] ?? null
]
]);
} catch (Exception $e) {
Log::error('Apple Sign In Error', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return response()->json([
'success' => false,
'message' => 'Authentication failed',
'error' => $e->getMessage()
], 500);
}
}