Skip to content

Commit d9d1797

Browse files
Added method for revoking access and refresh tokens
1 parent 3a4576b commit d9d1797

File tree

4 files changed

+134
-0
lines changed

4 files changed

+134
-0
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ All Notable changes to `oauth2-apple` will be documented in this file
1818
### Security
1919
- Nothing
2020

21+
## 0.2.9 - 2022-07-09
22+
23+
### Added
24+
- Method for revoking access and refresh tokens [#37](https://github.com/patrickbussmann/oauth2-apple/issues/37)
25+
2126
## 0.2.8 - 2022-05-10
2227

2328
### Fixed

README.md

+28
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,34 @@ if (!isset($_POST['code'])) {
8888
}
8989
```
9090

91+
### Revoke Code Flow
92+
93+
```php
94+
// $leeway is needed for clock skew
95+
Firebase\JWT\JWT::$leeway = 60;
96+
97+
$provider = new League\OAuth2\Client\Provider\Apple([
98+
'clientId' => '{apple-client-id}',
99+
'teamId' => '{apple-team-id}', // 1A234BFK46 https://developer.apple.com/account/#/membership/ (Team ID)
100+
'keyFileId' => '{apple-key-file-id}', // 1ABC6523AA https://developer.apple.com/account/resources/authkeys/list (Key ID)
101+
'keyFilePath' => '{apple-key-file-path}', // __DIR__ . '/AuthKey_1ABC6523AA.p8' -> Download key above
102+
'redirectUri' => 'https://example.com/callback-url',
103+
]);
104+
105+
$token = $token->getToken(); // Use the token of "Authorization Code Flow" which you saved somewhere for the user
106+
107+
108+
try {
109+
$provider->revokeAccessToken($token /*, 'access_token' or 'refresh_token' */);
110+
// Successfully revoked the token!
111+
112+
} catch (Exception $e) {
113+
114+
// Failed to revoke
115+
exit(':-(');
116+
}
117+
```
118+
91119
### Managing Scopes
92120

93121
When creating your Apple authorization URL, you can specify the state and scopes your application may authorize.

src/Provider/Apple.php

+58
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,16 @@ public function getBaseAccessTokenUrl(array $params)
152152
return 'https://appleid.apple.com/auth/token';
153153
}
154154

155+
/**
156+
* Get revoke token url to revoke token
157+
*
158+
* @return string
159+
*/
160+
public function getBaseRevokeTokenUrl(array $params)
161+
{
162+
return 'https://appleid.apple.com/auth/revoke';
163+
}
164+
155165
/**
156166
* Get provider url to fetch user details
157167
*
@@ -247,6 +257,54 @@ public function getAccessToken($grant, array $options = [])
247257
return parent::getAccessToken($grant, $options);
248258
}
249259

260+
/**
261+
* Revokes an access or refresh token using a specified token.
262+
*
263+
* @param string $token
264+
* @param string|null $tokenTypeHint
265+
* @return \Psr\Http\Message\RequestInterface
266+
*/
267+
public function revokeAccessToken($token, $tokenTypeHint = null)
268+
{
269+
$configuration = $this->getConfiguration();
270+
$time = new \DateTimeImmutable();
271+
$time = $time->setTime($time->format('H'), $time->format('i'), $time->format('s'));
272+
$expiresAt = $time->modify('+1 Hour');
273+
$expiresAt = $expiresAt->setTime($expiresAt->format('H'), $expiresAt->format('i'), $expiresAt->format('s'));
274+
275+
$clientSecret = $configuration->builder()
276+
->issuedBy($this->teamId)
277+
->permittedFor('https://appleid.apple.com')
278+
->issuedAt($time)
279+
->expiresAt($expiresAt)
280+
->relatedTo($this->clientId)
281+
->withHeader('alg', 'ES256')
282+
->withHeader('kid', $this->keyFileId)
283+
->getToken($configuration->signer(), $configuration->signingKey());
284+
285+
$params = [
286+
'client_id' => $this->clientId,
287+
'client_secret' => $clientSecret->toString(),
288+
'token' => $token
289+
];
290+
if ($tokenTypeHint !== null) {
291+
$params += [
292+
'token_type_hint' => $tokenTypeHint
293+
];
294+
}
295+
296+
$method = $this->getAccessTokenMethod();
297+
$url = $this->getBaseRevokeTokenUrl($params);
298+
if (property_exists($this, 'optionProvider')) {
299+
$options = $this->optionProvider->getAccessTokenOptions(self::METHOD_POST, $params);
300+
} else {
301+
$options = $this->getAccessTokenOptions($params);
302+
}
303+
$request = $this->getRequest($method, $url, $options);
304+
305+
return $this->getParsedResponse($request);
306+
}
307+
250308
/**
251309
* @return Configuration
252310
*/

test/src/Provider/AppleTest.php

+43
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,49 @@ public function testGetAccessTokenFailedBecauseAppleHasError()
199199
]);
200200
}
201201

202+
public function testRevokeAccessToken()
203+
{
204+
$provider = new TestApple([
205+
'clientId' => 'mock.example',
206+
'teamId' => 'mock.team.id',
207+
'keyFileId' => 'mock.file.id',
208+
'keyFilePath' => __DIR__ . '/../../resources/p256-private-key.p8',
209+
'redirectUri' => 'none'
210+
]);
211+
$provider = m::mock($provider);
212+
213+
$client = m::mock(ClientInterface::class);
214+
$client->shouldReceive('send')
215+
->times(1)
216+
->andReturn(new Response(200, [], json_encode([])));
217+
$provider->setHttpClient($client);
218+
219+
$this->assertEmpty($provider->revokeAccessToken('hello-world', 'access_token'));
220+
}
221+
222+
public function testRevokeAccessTokenFailedBecauseAppleHasError()
223+
{
224+
$this->expectException('Exception');
225+
$this->expectExceptionMessage('invalid_request');
226+
227+
$provider = new TestApple([
228+
'clientId' => 'mock.example',
229+
'teamId' => 'mock.team.id',
230+
'keyFileId' => 'mock.file.id',
231+
'keyFilePath' => __DIR__ . '/../../resources/p256-private-key.p8',
232+
'redirectUri' => 'none'
233+
]);
234+
$provider = m::mock($provider);
235+
236+
$client = m::mock(ClientInterface::class);
237+
$client->shouldReceive('send')
238+
->times(1)
239+
->andReturn(new Response(400, [], json_encode(['error' => 'invalid_request'])));
240+
$provider->setHttpClient($client);
241+
242+
$provider->revokeAccessToken('hello-world');
243+
}
244+
202245
public function testFetchingOwnerDetails()
203246
{
204247
$provider = $this->getProvider();

0 commit comments

Comments
 (0)