Skip to content

Commit f11a8c9

Browse files
committed
✨ TikTok provider
1 parent 8b67cd7 commit f11a8c9

File tree

4 files changed

+210
-0
lines changed

4 files changed

+210
-0
lines changed

.config/.env_example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,12 @@ STRIPE_SECRET=
230230
STRIPE_CALLBACK_URL=
231231
#STRIPE_TESTUSER=
232232

233+
# https://developers.tiktok.com/apps/
234+
TIKTOK_KEY=
235+
TIKTOK_SECRET=
236+
TIKTOK_CALLBACK_URL=
237+
#TIKTOK_TESTUSER=
238+
233239
# https://www.tumblr.com/oauth/apps
234240
TUMBLR_KEY=
235241
TUMBLR_SECRET=

examples/get-token/TikTok.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
/**
3+
* @link https://developers.tiktok.com/doc/login-kit-web/
4+
*
5+
* @created 11.04.2024
6+
* @author Smiley <[email protected]>
7+
* @copyright 2024 smiley
8+
* @license MIT
9+
*/
10+
declare(strict_types=1);
11+
12+
use chillerlan\OAuth\Providers\TikTok;
13+
14+
require_once __DIR__.'/../provider-example-common.php';
15+
16+
/** @var \OAuthExampleProviderFactory $factory */
17+
$provider = $factory->getProvider(TikTok::class);
18+
19+
require_once __DIR__.'/_flow-oauth2.php';
20+
21+
exit;

src/Providers/TikTok.php

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
/**
3+
* Class TikTok
4+
*
5+
* @created 11.04.2024
6+
* @author smiley <[email protected]>
7+
* @copyright 2024 smiley
8+
* @license MIT
9+
*
10+
* @noinspection PhpUnused
11+
*/
12+
13+
namespace chillerlan\OAuth\Providers;
14+
15+
use chillerlan\OAuth\Core\{CSRFToken, OAuth2Provider, PKCE, TokenRefresh};
16+
use function array_merge, implode;
17+
18+
/**
19+
* @see https://developers.tiktok.com/doc/login-kit-web/
20+
* @see https://developers.tiktok.com/doc/oauth-user-access-token-management/
21+
*/
22+
class TikTok extends OAuth2Provider implements CSRFToken, PKCE, TokenRefresh{
23+
24+
public const IDENTIFIER = 'TIKTOK';
25+
26+
public const SCOPE_VIDEO_UPLOAD = 'video.upload';
27+
public const SCOPE_VIDEO_LIST = 'video.list';
28+
public const SCOPE_VIDEO_PUBLISH = 'video.publish';
29+
public const SCOPE_USER_INFO_BASIC = 'user.info.basic';
30+
public const SCOPE_USER_INFO_PROFILE = 'user.info.profile';
31+
public const SCOPE_USER_INFO_STATS = 'user.info.stats';
32+
public const SCOPE_PORTABILITY_PPOSTPROFILE_ONGOING = 'portability.postsandprofile.ongoing';
33+
public const SCOPE_PORTABILITY_PPOSTPROFILE_SINGLE = 'portability.postsandprofile.single';
34+
public const SCOPE_PORTABILITY_ALL_ONGOING = 'portability.all.ongoing';
35+
public const SCOPE_PORTABILITY_ALL_SINGLE = 'portability.all.single';
36+
public const SCOPE_PORTABILITY_DIRECTMESSAGES_ONGOING = 'portability.directmessages.ongoing';
37+
public const SCOPE_PORTABILITY_DIRECTMESSAGES_SINGLE = 'portability.directmessages.single';
38+
public const SCOPE_PORTABILITY_ACTIVITY_ONGOING = 'portability.activity.ongoing';
39+
public const SCOPE_PORTABILITY_ACTIVITY_SINGLE = 'portability.activity.single';
40+
41+
public const DEFAULT_SCOPES = [
42+
self::SCOPE_USER_INFO_BASIC,
43+
];
44+
45+
protected string $authorizationURL = 'https://www.tiktok.com/v2/auth/authorize/';
46+
protected string $accessTokenURL = 'https://open.tiktokapis.com/v2/oauth/token/';
47+
protected string $revokeURL = 'https://open.tiktokapis.com/v2/oauth/revoke/';
48+
protected string $apiURL = 'https://open.tiktokapis.com';
49+
protected string|null $apiDocs = 'https://developers.tiktok.com/doc/overview/';
50+
protected string|null $applicationURL = 'https://developers.tiktok.com/apps/';
51+
protected string|null $userRevokeURL = 'https://example.com/user/settings/connections';
52+
53+
/**
54+
* @inheritDoc
55+
*/
56+
protected function getAuthorizationURLRequestParams(array $params, array $scopes):array{
57+
58+
unset($params['client_secret']);
59+
60+
$params = array_merge($params, [
61+
'client_key' => $this->options->key,
62+
'redirect_uri' => $this->options->callbackURL,
63+
'response_type' => 'code',
64+
]);
65+
66+
if(!empty($scopes)){
67+
$params['scope'] = implode($this::SCOPES_DELIMITER, $scopes);
68+
}
69+
70+
$params = $this->setCodeChallenge($params, PKCE::CHALLENGE_METHOD_S256);
71+
72+
return $this->setState($params);
73+
}
74+
75+
/**
76+
* @inheritDoc
77+
*/
78+
protected function getAccessTokenRequestBodyParams(string $code):array{
79+
80+
$params = [
81+
'client_key' => $this->options->key,
82+
'client_secret' => $this->options->secret,
83+
'code' => $code,
84+
'grant_type' => 'authorization_code',
85+
'redirect_uri' => $this->options->callbackURL,
86+
];
87+
88+
return $this->setCodeVerifier($params);
89+
}
90+
91+
/**
92+
* @inheritDoc
93+
*/
94+
protected function getRefreshAccessTokenRequestBodyParams(string $refreshToken):array{
95+
return [
96+
'client_key' => $this->options->key,
97+
'client_secret' => $this->options->secret,
98+
'grant_type' => 'refresh_token',
99+
'refresh_token' => $refreshToken,
100+
];
101+
}
102+
103+
}

tests/Providers/Unit/TikTokTest.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
/**
3+
* Class TikTokTest
4+
*
5+
* @created 18.04.2024
6+
* @author smiley <[email protected]>
7+
* @copyright 2024 smiley
8+
* @license MIT
9+
*/
10+
declare(strict_types=1);
11+
12+
namespace chillerlan\OAuthTest\Providers\Unit;
13+
14+
use chillerlan\HTTP\Utils\QueryUtil;
15+
use chillerlan\OAuth\Providers\TikTok;
16+
use function implode;
17+
18+
/**
19+
* @property \chillerlan\OAuth\Providers\TikTok $provider
20+
*/
21+
final class TikTokTest extends OAuth2ProviderUnitTestAbstract{
22+
23+
protected function getProviderFQCN():string{
24+
return TikTok::class;
25+
}
26+
27+
public function testGetAuthURL():void{
28+
$uri = $this->provider->getAuthorizationURL();
29+
$params = QueryUtil::parse($uri->getQuery());
30+
31+
$this::assertSame($this->getReflectionProperty('authorizationURL'), (string)$uri->withQuery(''));
32+
33+
$this::assertArrayHasKey('client_key', $params);
34+
$this::assertArrayHasKey('redirect_uri', $params);
35+
$this::assertArrayHasKey('response_type', $params);
36+
37+
$this::assertArrayHasKey('state', $params);
38+
}
39+
40+
public function testGetAuthURLRequestParams():void{
41+
$extraparams = ['response_type' => 'whatever', 'foo' => 'bar'];
42+
$scopes = ['scope1', 'scope2', 'scope3'];
43+
44+
$params = $this->invokeReflectionMethod('getAuthorizationURLRequestParams', [$extraparams, $scopes]);
45+
46+
$this::assertSame($this->options->key, $params['client_key']);
47+
$this::assertSame($this->options->callbackURL, $params['redirect_uri']);
48+
$this::assertSame('code', $params['response_type']);
49+
$this::assertSame(implode($this->provider::SCOPES_DELIMITER, $scopes), $params['scope']);
50+
$this::assertSame('bar', $params['foo']);
51+
}
52+
53+
public function testGetAccessTokenRequestBodyParams():void{
54+
$verifier = $this->provider->generateVerifier($this->options->pkceVerifierLength);
55+
56+
$this->storage->storeCodeVerifier($verifier, $this->provider->name);
57+
58+
$params = $this->invokeReflectionMethod('getAccessTokenRequestBodyParams', ['*test_code*']);
59+
60+
$this::assertSame('*test_code*', $params['code']);
61+
$this::assertSame($this->options->callbackURL, $params['redirect_uri']);
62+
$this::assertSame('authorization_code', $params['grant_type']);
63+
$this::assertSame($this->options->key, $params['client_key']);
64+
$this::assertSame($this->options->secret, $params['client_secret']);
65+
66+
$this::assertSame($verifier, $params['code_verifier']);
67+
68+
}
69+
70+
public function testGetRefreshAccessTokenRequestBodyParams():void{
71+
$params = $this->invokeReflectionMethod('getRefreshAccessTokenRequestBodyParams', ['*refresh_token*']);
72+
73+
$this::assertSame('*refresh_token*', $params['refresh_token']);
74+
$this::assertSame($this->options->key, $params['client_key']);
75+
$this::assertSame($this->options->secret, $params['client_secret']);
76+
$this::assertSame('refresh_token', $params['grant_type']);
77+
}
78+
79+
80+
}

0 commit comments

Comments
 (0)