friendship ended with social-app. php is my new best friend
1<?php
2/**
3 * Trait TokenInvalidateTrait
4 *
5 * @created 19.09.2024
6 * @author smiley <smiley@chillerlan.net>
7 * @copyright 2024 smiley
8 * @license MIT
9 */
10declare(strict_types=1);
11
12namespace chillerlan\OAuth\Core;
13
14use chillerlan\HTTP\Utils\MessageUtil;
15use chillerlan\OAuth\Providers\ProviderException;
16use Psr\Http\Message\ResponseInterface;
17use function in_array;
18use function sprintf;
19use function str_contains;
20use function strtolower;
21use function trim;
22
23/**
24 * Implements token invalidation functionality
25 *
26 * @see \chillerlan\OAuth\Core\TokenInvalidate
27 */
28trait TokenInvalidateTrait{
29
30 /**
31 * implements TokenInvalidate::invalidateAccessToken()
32 *
33 * @see \chillerlan\OAuth\Core\TokenInvalidate::invalidateAccessToken()
34 * @throws \chillerlan\OAuth\Providers\ProviderException
35 */
36 public function invalidateAccessToken(AccessToken|null $token = null, string|null $type = null):bool{
37 $type = strtolower(trim(($type ?? 'access_token')));
38
39 // @link https://datatracker.ietf.org/doc/html/rfc7009#section-2.1
40 if(!in_array($type, ['access_token', 'refresh_token'], true)){
41 throw new ProviderException(sprintf('invalid token type "%s"', $type)); // @codeCoverageIgnore
42 }
43
44 $tokenToInvalidate = ($token ?? $this->storage->getAccessToken($this->name));
45 $body = $this->getInvalidateAccessTokenBodyParams($tokenToInvalidate, $type);
46 $response = $this->sendTokenInvalidateRequest($this->revokeURL, $body);
47
48 // some endpoints may return 204, others 200 with empty body
49 if(in_array($response->getStatusCode(), [200, 204], true)){
50
51 // if the token was given via parameter it cannot be deleted from storage
52 if($token === null){
53 $this->storage->clearAccessToken($this->name);
54 }
55
56 return true;
57 }
58
59 // ok, let's see if we got a response body
60 // @link https://datatracker.ietf.org/doc/html/rfc7009#section-2.2.1
61 if(str_contains($response->getHeaderLine('content-type'), 'json')){
62 $json = MessageUtil::decodeJSON($response, true);
63
64 if(isset($json['error'])){
65 throw new ProviderException($json['error']);
66 }
67 }
68
69 return false;
70 }
71
72 /**
73 * Prepares the body for a token revocation request
74 *
75 * @see \chillerlan\OAuth\Core\OAuth2Provider::invalidateAccessToken()
76 *
77 * @return array<string, scalar|bool|null>
78 */
79 protected function getInvalidateAccessTokenBodyParams(AccessToken $token, string $type):array{
80 return [
81 'token' => $token->accessToken,
82 'token_type_hint' => $type,
83 ];
84 }
85
86 /**
87 * Prepares and sends a request to the token invalidation endpoint
88 *
89 * @see \chillerlan\OAuth\Core\OAuth2Provider::invalidateAccessToken()
90 *
91 * @param array<string, scalar|bool|null> $body
92 */
93 protected function sendTokenInvalidateRequest(string $url, array $body):ResponseInterface{
94
95 $request = $this->requestFactory
96 ->createRequest('POST', $url)
97 ->withHeader('Content-Type', 'application/x-www-form-urlencoded')
98 ;
99
100 // some enpoints may require a basic auth header here
101 $request = $this->setRequestBody($body, $request);
102
103 return $this->http->sendRequest($request);
104 }
105
106}