Skip to content

Commit 3488158

Browse files
authored
Merge pull request #75 from mikebronner/fix/49-handle-apple-server-errors
fix(auth): Handle Apple server errors (5xx) gracefully
2 parents ea7aa71 + d65b79f commit 3488158

File tree

2 files changed

+87
-25
lines changed

2 files changed

+87
-25
lines changed

src/Providers/SignInWithAppleProvider.php

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use GeneaLabs\LaravelSignInWithApple\Exceptions\InvalidRedirectUrlException;
66
use GeneaLabs\LaravelSignInWithApple\Exceptions\InvalidAppleCredentialsException;
77
use GuzzleHttp\Exception\ClientException;
8+
use GuzzleHttp\Exception\ServerException;
89
use Illuminate\Support\Arr;
910
use Laravel\Socialite\Two\AbstractProvider;
1011
use Laravel\Socialite\Two\InvalidStateException;
@@ -108,34 +109,11 @@ public function getAccessTokenResponse($code)
108109
return parent::getAccessTokenResponse($code);
109110
} catch (ClientException $e) {
110111
$this->handleTokenError($e);
112+
} catch (ServerException $e) {
113+
$this->handleServerError($e);
111114
}
112115
}
113116

114-
public function getAccessToken($code)
115-
{
116-
$response = $this->getHttpClient()
117-
->post(
118-
$this->getTokenUrl(),
119-
[
120-
'headers' => [
121-
'Authorization' => 'Basic '. base64_encode(
122-
$this->clientId . ':' . $this->clientSecret
123-
),
124-
],
125-
'body' => $this->getTokenFields($code),
126-
]
127-
);
128-
129-
return $this->parseAccessToken($response->getBody());
130-
}
131-
132-
protected function parseAccessToken($response)
133-
{
134-
$data = $response->json();
135-
136-
return $data['access_token'];
137-
}
138-
139117
protected function getTokenFields($code)
140118
{
141119
$fields = parent::getTokenFields($code);
@@ -206,6 +184,28 @@ protected function mapUserToObject(array $user)
206184
]);
207185
}
208186

187+
/**
188+
* Handle a ServerException (5xx) from the Apple token endpoint.
189+
*
190+
* Apple's servers occasionally return 500 errors. This wraps them
191+
* in a descriptive exception so apps can handle them gracefully.
192+
*
193+
* @throws \RuntimeException
194+
*/
195+
protected function handleServerError(ServerException $exception): never
196+
{
197+
$statusCode = $exception->getResponse()->getStatusCode();
198+
199+
throw new \RuntimeException(
200+
"Apple Sign In is temporarily unavailable (HTTP {$statusCode}). "
201+
. 'Apple\'s authentication servers returned a server error. '
202+
. 'This is typically a temporary issue on Apple\'s side. '
203+
. 'Please try again in a few minutes.',
204+
$statusCode,
205+
$exception,
206+
);
207+
}
208+
209209
/**
210210
* Handle a ClientException from the Apple token endpoint.
211211
*
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
namespace GeneaLabs\LaravelSignInWithApple\Tests\Unit;
4+
5+
use GeneaLabs\LaravelSignInWithApple\Providers\SignInWithAppleProvider;
6+
use GeneaLabs\LaravelSignInWithApple\Tests\UnitTestCase;
7+
use GuzzleHttp\Exception\ServerException;
8+
use GuzzleHttp\Psr7\Response;
9+
use Illuminate\Http\Request;
10+
use ReflectionMethod;
11+
12+
class ServerErrorHandlingTest extends UnitTestCase
13+
{
14+
protected function makeProvider(): SignInWithAppleProvider
15+
{
16+
return new SignInWithAppleProvider(
17+
Request::create('/'),
18+
'test-client-id',
19+
'test-client-secret',
20+
'https://example.com/callback'
21+
);
22+
}
23+
24+
public function testHandles500ServerError(): void
25+
{
26+
$provider = $this->makeProvider();
27+
28+
$response = new Response(500, [], 'Internal Server Error');
29+
$exception = new ServerException('Server error', new \GuzzleHttp\Psr7\Request('POST', '/'), $response);
30+
31+
$method = new ReflectionMethod($provider, 'handleServerError');
32+
$method->setAccessible(true);
33+
34+
try {
35+
$method->invoke($provider, $exception);
36+
$this->fail('Expected RuntimeException');
37+
} catch (\RuntimeException $e) {
38+
$this->assertStringContainsString('temporarily unavailable', $e->getMessage());
39+
$this->assertStringContainsString('500', $e->getMessage());
40+
$this->assertSame($exception, $e->getPrevious());
41+
}
42+
}
43+
44+
public function testHandles503ServiceUnavailable(): void
45+
{
46+
$provider = $this->makeProvider();
47+
48+
$response = new Response(503, [], 'Service Unavailable');
49+
$exception = new ServerException('Server error', new \GuzzleHttp\Psr7\Request('POST', '/'), $response);
50+
51+
$method = new ReflectionMethod($provider, 'handleServerError');
52+
$method->setAccessible(true);
53+
54+
try {
55+
$method->invoke($provider, $exception);
56+
$this->fail('Expected RuntimeException');
57+
} catch (\RuntimeException $e) {
58+
$this->assertStringContainsString('503', $e->getMessage());
59+
$this->assertStringContainsString('try again', $e->getMessage());
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)