socialiteproviders/tiktok
TikTok OAuth 2.0 provider for Laravel Socialite. Add TikTok login to your Laravel app using Socialite’s familiar redirect and callback flow. Install with Composer, configure your TikTok client ID and secret, and authenticate users via the socialiteproviders/tiktok driver.
Install the driver together with the manager package it depends on:
composer require socialiteproviders/manager socialiteproviders/tiktok
Register the driver's event listener. In Laravel ≤10 that lives in app/Providers/EventServiceProvider.php:
protected $listen = [
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
\SocialiteProviders\TikTok\TikTokExtendSocialite::class.'@handle',
],
];
If you're on the Laravel 11+ skeleton (no EventServiceProvider by default), do the same thing in AppServiceProvider::boot():
Event::listen(
\SocialiteProviders\Manager\SocialiteWasCalled::class,
\SocialiteProviders\TikTok\TikTokExtendSocialite::class.'@handle',
);
Add credentials in config/services.php:
'tiktok' => [
'client_id' => env('TIKTOK_CLIENT_ID'),
'client_secret' => env('TIKTOK_CLIENT_SECRET'),
'redirect' => env('TIKTOK_REDIRECT_URI'),
],
Wire two routes and you have your first use case — a login button that redirects out and a callback that returns a SocialiteProviders\Manager\OAuth2\User:
Route::get('/auth/tiktok/redirect', fn () =>
Socialite::driver('tiktok')
->scopes(['user.info.basic']) // TikTok needs explicit scopes
->redirect()
);
Route::get('/auth/tiktok/callback', function () {
$tiktokUser = Socialite::driver('tiktok')->user();
$user = User::firstOrCreate(
['tiktok_id' => $tiktokUser->getId()],
['name' => $tiktokUser->getName(), 'avatar' => $tiktokUser->getAvatar()],
);
Auth::login($user, remember: true);
return redirect()->intended('/dashboard');
});
The first place to look when something breaks is the provider class itself (vendor/socialiteproviders/tiktok/Provider.php) — it's small, and it tells you exactly which scopes, URLs, and response keys the driver expects.
Standard web redirect flow. Redirect → TikTok consent → callback → ->user()->getId(). Keep the scopes() call explicit and identical between redirect and callback; TikTok ties the issued token to the scope set requested at authorize time.
Stateless / API-driven flow. For a SPA or mobile client that obtains the code itself, use stateless() so Socialite doesn't attempt session state validation, and pass the code back to your API:
$user = Socialite::driver('tiktok')->stateless()->user();
If the client sends an access token instead of a code, call ->userFromToken($token) (present on most Socialite Providers drivers — verify against the vendor class before relying on it).
Account linking table. Store both identifiers, because TikTok exposes two IDs with different scopes:
Schema::create('social_accounts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('provider');
$table->string('provider_user_id'); // tiktok open_id
$table->string('union_id')->nullable();
$table->text('access_token');
$table->text('refresh_token')->nullable();
$table->timestamp('expires_at')->nullable();
$table->timestamps();
$table->unique(['provider', 'provider_user_id']);
});
open_id is per-app per-user; union_id is stable across all apps under the same developer account. Use open_id for login, union_id if you ever add a second TikTok app.
Token lifecycle. Store $user->token, $user->refreshToken, and compute expires_at from $user->expiresIn. TikTok access tokens are short-lived (~24h), so schedule a refresh job well before expiry rather than refreshing on request. The driver exposes TikTok's refresh endpoint; typical usage is $provider->refreshToken($storedRefreshToken) — check the vendor signature, and always overwrite the stored refresh token with the one in the response, since it is re-issued.
Raw payload access. $user->getRaw() / $user->user gives the mapped user array; $user->accessTokenResponseBody gives the raw token exchange body (open_id, scope, token_type). Persist these if you need to debug or support multiple apps.
Testing. Mock the driver so tests never hit TikTok:
Socialite::shouldReceive('driver->user')->andReturn(
(new \SocialiteProviders\Manager\OAuth2\User)->map(['id' => 'abc', 'name' => 'Test'])
);
Or use Http::fake() around the token/user endpoints if you want to exercise the provider's mapping logic.
Multi-provider abstraction. Since the driver is just a string, keep a SocialiteController with redirect($provider) / callback($provider) and an allowlist of providers — swapping in Google/GitHub later is a config change, not a new controller.
Driver [tiktok] is not supported is almost always the missing event listener, or a cached event map. Run php artisan event:clear and php artisan config:clear. In Laravel 11+, forgetting that EventServiceProvider no longer exists is the usual cause.services.tiktok and then forgetting php artisan config:cache gives you null client IDs and a cryptic TikTok error page. Cache after editing.user.info.basic returns open_id, union_id, display_name, and avatar_url — no email. $user->getEmail() will be null. Don't build a standard Laravel starter-kit auth flow expecting an email; either prompt for one after linking or generate a placeholder.->scopes([...]) frequently produces an authorization error from TikTok. user.info.basic is the minimum for login; user.info.profile adds display_name/avatar_url, user.info.stats adds follower counts.http://localhost will be rejected outside sandbox configuration.{ "data": { "user": { ... } } }; the driver unwraps it for you, but if you inspect getRaw() and see no open_id, check whether you're looking at $user->user (unwrapped) versus $user->accessTokenResponseBody.refresh_token; failing to persist it will break the next refresh and force re-consent.InvalidStateException on callback usually means session cookies aren't shared across instances, or the redirect was opened in a different browser tab than the callback. stateless() sidesteps this at the cost of CSRF protection — only use it when you have your own state handling.try/catch (Throwable $e) and report($e), then replay the token request manually with Http::asForm()->post('https://open.tiktokapis.com/v2/oauth/token/', [...]) to see TikTok's actual error/error_description.Socialite::extend('tiktok', fn ($app) => new YourSubclass($app['request'], $config)) and override getAuthUrl/mapUserToObject.composer.json and re-test the OAuth flow after any Socialite core upgrade — TikTok has revised its v2 endpoints before.How can I help you explore Laravel packages today?