消息:与 Apple 验证失败,错误:Laravel PHP 中的 `{error: invalid_client}`

问题描述 投票:0回答:1

我尝试在 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()
    ]);       
}

}

key id service id

laravel digital-signature
1个回答
1
投票

这个问题很棘手,正如herehere所示,问题实际上出在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);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.