Skip to content

Commit 4e252a6

Browse files
committed
Improve lcobucci/jwt usage in Apple provider
Using the Configuration class forces to know a signer and a key, which is irrelevant for the purpose of the provider, but it introduces some code that looks scary if used incorrectly. The irrelevant classes are removed, additional exception cases are now rethrowing as InvalidStateException. Conditions have been inverted to implement early exit.
1 parent 23d0dd3 commit 4e252a6

File tree

4 files changed

+56
-140
lines changed

4 files changed

+56
-140
lines changed

src/Apple/AppleSignerInMemory.php

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/Apple/AppleSignerNone.php

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/Apple/AppleToken.php

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/Apple/Provider.php

Lines changed: 56 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace SocialiteProviders\Apple;
44

5+
use Carbon\CarbonImmutable;
56
use DateInterval;
67
use Firebase\JWT\JWK;
78
use GuzzleHttp\Client;
@@ -13,11 +14,17 @@
1314
use Laravel\Socialite\Two\InvalidStateException;
1415
use Lcobucci\Clock\SystemClock;
1516
use Lcobucci\JWT\Configuration;
16-
use Lcobucci\JWT\Signer\Rsa\Sha256;
17+
use Lcobucci\JWT\Encoding\JoseEncoder;
18+
use Lcobucci\JWT\Exception;
19+
use Lcobucci\JWT\Signer;
20+
use Lcobucci\JWT\Signer\Ecdsa\Sha256 as EcdsaSha256;
21+
use Lcobucci\JWT\Signer\Key\InMemory;
22+
use Lcobucci\JWT\Signer\Rsa\Sha256 as RsaSha256;
23+
use Lcobucci\JWT\Token\Parser;
1724
use Lcobucci\JWT\Validation\Constraint\IssuedBy;
1825
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
1926
use Lcobucci\JWT\Validation\Constraint\SignedWith;
20-
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
27+
use Lcobucci\JWT\Validation\Validator;
2128
use Psr\Http\Message\ResponseInterface;
2229
use SocialiteProviders\Manager\OAuth2\AbstractProvider;
2330
use SocialiteProviders\Manager\OAuth2\User;
@@ -26,7 +33,7 @@ class Provider extends AbstractProvider
2633
{
2734
public const IDENTIFIER = 'APPLE';
2835

29-
public const URL = 'https://appleid.apple.com';
36+
private const URL = 'https://appleid.apple.com';
3037

3138
protected $scopes = [
3239
'name',
@@ -41,18 +48,11 @@ class Provider extends AbstractProvider
4148
protected $scopeSeparator = ' ';
4249

4350
/**
44-
* JWT Configuration.
51+
* JWT Configuration for Apple Authentication Token.
4552
*
4653
* @var ?Configuration
4754
*/
48-
protected $jwtConfig = null;
49-
50-
/**
51-
* Private Key.
52-
*
53-
* @var string
54-
*/
55-
protected $privateKey = '';
55+
protected ?Configuration $jwtConfig = null;
5656

5757
/**
5858
* {@inheritdoc}
@@ -114,43 +114,53 @@ protected function getUserByToken($token)
114114

115115
protected function getClientSecret()
116116
{
117-
if (!$this->jwtConfig) {
118-
$this->getJwtConfig(); // Generate Client Secret from private key if not set.
117+
if (!empty($this->privateKey)) {
118+
$this->clientSecret = $this->generateApplePrivateTokenString();
119+
config()->set('services.apple.client_secret', $this->clientSecret);
119120
}
120121

121122
return $this->clientSecret;
122123
}
123124

124-
protected function getJwtConfig()
125+
protected function createJwtConfig(): void
125126
{
126-
if (!$this->jwtConfig) {
127+
if (!$this->jwtConfig instanceof Configuration) {
127128
$private_key_path = $this->getConfig('private_key', '');
128129
$private_key_passphrase = $this->getConfig('passphrase', '');
129-
$signer = $this->getConfig('signer', '');
130+
$signerClassName = $this->getConfig('signer', '');
130131

131-
if (empty($signer) || !class_exists($signer)) {
132-
$signer = !empty($private_key_path) ? \Lcobucci\JWT\Signer\Ecdsa\Sha256::class : AppleSignerNone::class;
132+
if (empty($signerClassName) || !class_exists($signerClassName) || !is_a($signerClassName, Signer::class, true)) {
133+
$signerClassName = EcdsaSha256::class;
133134
}
134135

135136
if (!empty($private_key_path) && file_exists($private_key_path)) {
136-
$this->privateKey = file_get_contents($private_key_path);
137+
$key = InMemory::file($private_key_path, $private_key_passphrase);
137138
} else {
138-
$this->privateKey = $private_key_path; // Support for plain text private keys
139+
$key = InMemory::plainText($private_key_path, $private_key_passphrase);
139140
}
140141

141142
$this->jwtConfig = Configuration::forSymmetricSigner(
142-
new $signer(),
143-
AppleSignerInMemory::plainText($this->privateKey, $private_key_passphrase)
143+
new $signerClassName(),
144+
$key
144145
);
145-
146-
if (!empty($this->privateKey)) {
147-
$appleToken = new AppleToken($this->getJwtConfig());
148-
$this->clientSecret = $appleToken->generate();
149-
config()->set('services.apple.client_secret', $this->clientSecret);
150-
}
151146
}
147+
}
152148

153-
return $this->jwtConfig;
149+
private function generateApplePrivateTokenString(): string
150+
{
151+
$now = CarbonImmutable::now();
152+
$this->createJwtConfig();
153+
154+
$token = $this->jwtConfig->builder()
155+
->issuedBy(config('services.apple.team_id'))
156+
->issuedAt($now)
157+
->expiresAt($now->addHour())
158+
->permittedFor(Provider::URL)
159+
->relatedTo(config('services.apple.client_id'))
160+
->withHeader('kid', config('services.apple.key_id'))
161+
->getToken($this->jwtConfig->signer(), $this->jwtConfig->signingKey());
162+
163+
return $token->toString();
154164
}
155165

156166
/**
@@ -179,7 +189,11 @@ public function userByIdentityToken(string $token): User
179189
*/
180190
public function checkToken($jwt)
181191
{
182-
$token = $this->getJwtConfig()->parser()->parse($jwt);
192+
try {
193+
$token = (new Parser(new JoseEncoder()))->parse($jwt);
194+
} catch (Exception $e) {
195+
throw new InvalidStateException($e->getMessage());
196+
}
183197

184198
$data = Cache::remember('socialite:Apple-JWKSet', 5 * 60, function () {
185199
$response = (new Client)->get(self::URL.'/auth/keys');
@@ -190,25 +204,23 @@ public function checkToken($jwt)
190204
$publicKeys = JWK::parseKeySet($data);
191205
$kid = $token->headers()->get('kid');
192206

193-
if (isset($publicKeys[$kid])) {
194-
$publicKey = openssl_pkey_get_details($publicKeys[$kid]->getKeyMaterial());
207+
if (!isset($publicKeys[$kid])) {
208+
throw new InvalidStateException('Invalid JWT Signature');
209+
}
210+
211+
$publicKey = openssl_pkey_get_details($publicKeys[$kid]->getKeyMaterial());
212+
try {
195213
$constraints = [
196-
new SignedWith(new Sha256, AppleSignerInMemory::plainText($publicKey['key'])),
214+
new SignedWith(new RsaSha256, InMemory::plainText($publicKey['key'])),
197215
new IssuedBy(self::URL),
198-
// fix for #1354
199216
new LooseValidAt(SystemClock::fromSystemTimezone(), new DateInterval('PT3S')),
200217
];
201218

202-
try {
203-
$this->jwtConfig->validator()->assert($token, ...$constraints);
204-
205-
return true;
206-
} catch (RequiredConstraintsViolated $e) {
207-
throw new InvalidStateException($e->getMessage());
208-
}
219+
(new Validator())->assert($token, ...$constraints);
220+
} catch (Exception $e) {
221+
throw new InvalidStateException($e->getMessage());
209222
}
210-
211-
throw new InvalidStateException('Invalid JWT Signature');
223+
return true;
212224
}
213225

214226
/**

0 commit comments

Comments
 (0)