socialiteproviders/qq
QQ OAuth provider for Laravel Socialite, letting your app add Tencent QQ login with minimal setup. Install via Composer, configure your app ID and key, and authenticate QQ users through the familiar Socialite API.
The package is a single-provider shim: it lets Laravel Socialite authenticate users against Tencent QQ's OAuth 2.0 ("QQ Connect") endpoints. It is a subtree split of the SocialiteProviders/Providers monorepo, so treat upstream as the source of truth.
1. Install
composer require socialiteproviders/qq
This pulls in socialiteproviders/manager (required) plus laravel/socialite.
2. Register the provider listener
In app/Providers/EventServiceProvider.php (or, in Laravel 11+, via Event::listen in a service provider):
protected $listen = [
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
\SocialiteProviders\QQ\QQExtendSocialite::class.'@handle',
],
];
The concrete provider is SocialiteProviders\QQ\Provider; the QQExtendSocialite listener registers it under the driver name qq.
3. Add config
// config/services.php
'qq' => [
'client_id' => env('QQ_CLIENT_ID'),
'client_secret' => env('QQ_CLIENT_SECRET'),
'redirect' => env('QQ_REDIRECT_URI'),
],
4. Get credentials
Create an app at https://connect.qq.com. The "App ID" is client_id; the "App Key" is client_secret. The callback domain you register must match QQ_REDIRECT_URI exactly (host at minimum — QQ is strict here).
5. First use case — a login button
Route::get('/auth/qq', fn () => Socialite::driver('qq')->redirect());
Route::get('/auth/qq/callback', function () {
$qqUser = Socialite::driver('qq')->user();
// $qqUser->getId() => openid (per-app identifier)
// $qqUser->getNickname() => nickname
// $qqUser->getAvatar() => figureurl_qq_2
// $qqUser->getEmail() => null (QQ does not expose email)
});
If you see Driver [qq] is not supported, the event listener in step 2 is missing or cached — run php artisan config:clear and php artisan event:clear.
Standard redirect + callback controller
Keep the Socialite calls thin and push account resolution into a service/action. The typical shape:
public function redirect()
{
return Socialite::driver('qq')->redirect();
}
public function callback(Request $request)
{
try {
$qq = Socialite::driver('qq')->user();
} catch (\Throwable $e) {
report($e);
return redirect('/login')->withErrors('QQ login failed.');
}
$user = User::firstOrCreate(
['qq_openid' => $qq->getId()],
['name' => $qq->getNickname(), 'avatar' => $qq->getAvatar()]
);
Auth::login($user, remember: true);
return redirect()->intended('/dashboard');
}
Store openid in its own indexed column rather than overloading email — QQ does not return an email, so email-based matching will fail.
Account linking for already-logged-in users
Because openid is a stable per-app identity, it works well as a link key. Use a separate route that requires auth middleware and attaches openid to the current user instead of logging in a fresh one.
Stateless / SPA / mobile flows
For token-based clients, use stateless(). Note the provider still performs the same server-side code exchange; you must forward the code (and state when applicable) from the client.
$qq = Socialite::driver('qq')->stateless()->user();
Requesting extra scopes
QQ scopes are comma-joined, not space-joined. The provider defaults to get_user_info. Add more only after Tencent has approved them for your app:
Socialite::driver('qq')
->scopes(['get_user_info', 'list_album'])
->redirect();
Refreshing tokens
QQ issues a refresh_token with the access token. Keep the raw token via $qq->token / $qq->refreshToken if you plan to call QQ APIs (e.g., Qzone) later. The Socialite User object exposes ->token, ->refreshToken, ->expiresIn on the manager user instance.
Multi-provider apps
The listener approach registers QQ alongside GitHub/Google etc. Keep a single OAuthController with a {provider} parameter, and map provider → identity column in a config array, rather than branching per provider inside the controller.
Testing
Socialite::shouldReceive('driver') mocks work, but for integration confidence, point QQ_CLIENT_ID/QQ_CLIENT_SECRET at a test app and assert on getRaw(). The raw payload is the single best debugging surface.
Read-only repository. Do not open PRs against socialiteproviders/qq; contribute to SocialiteProviders/Providers and the split is regenerated. Bug fixes you "make locally" will be overwritten on the next composer update unless you fork properly.
Release cadence is ancient. Last tagged release was 2022-07-25. The provider still works, but verify it against your Laravel/Socialite major (Laravel 11/12 need socialiteproviders/manager v3+ / Socialite 5). Pin versions deliberately and include this package in your upgrade testing.
JSONP responses. QQ's https://graph.qq.com/oauth2.0/me endpoint historically returns callback( {"client_id":"...","openid":"..."} ); rather than JSON. The provider strips the callback(...) wrapper, but if you ever call /me yourself, remember to strip it before json_decode.
openid is app-scoped, not global. The same QQ user gets a different openid for each QQ Connect app. If you need a cross-app identifier, you need the unionid via a different Tencent mechanism — this provider does not surface it. Design your schema knowing openid is only meaningful for your app.
Token endpoint returns a query string, not JSON. https://graph.qq.com/oauth2.0/token responds with access_token=...&expires_in=...&refresh_token=.... The provider parses this; don't assume json_decode when debugging with curl.
Errors can arrive with HTTP 200. QQ's get_user_info may return a 200 with ret != 0 and a msg (e.g., 100030 = app not authorized for the scope, 100016 = invalid token). The provider surfaces this by returning partial user data; guard against a missing nickname or openid and treat it as failure.
$raw = $qq->getRaw();
if (($raw['ret'] ?? 0) !== 0) {
// log $raw['msg'], abort login
}
App review is a real gate. Unreviewed QQ apps only allow a small set of test accounts and often cannot call get_user_info. If logins succeed in the QQ sandbox but fail in production, check the app's review/publish status on connect.qq.com before blaming the package.
Redirect URI must match the registered domain. QQ validates the callback host against the app config. http vs https, www. vs apex, and trailing slashes all matter. Mismatches produce a QQ error page, not a Laravel exception.
email is always null. Do not make email required downstream; collect it separately after login if your product needs it.
Nickname encoding. QQ nicknames can contain emoji and CJK characters. Ensure the name/nickname column is utf8mb4 and that your connection charset is utf8mb4, or you will silently mangle names.
Avatar URLs are third-party CDN links. figureurl_qq_2 points at QQ's CDN and can change or expire. Download and rehost avatars on first login rather than storing the URL as canonical.
Stateless mode caveat. With stateless()->user(), the provider skips the session state check. That's correct for SPA/mobile, but it means you must enforce CSRF/state yourself in your own client.
Debugging recipe. Enable Guzzle logging on the Socialite HTTP client, or temporarily dump $qq->getRaw(). Most failures are one of: missing listener registration, services.qq not set, redirect URI mismatch, or an unapproved app — all of which appear before or in the raw payload, not in the returned User object.
Config caching. After editing config/services.php or the listener, run php artisan config:clear && php artisan cache:clear. QQ setup mistakes are frequently just stale config in bootstrap/cache.
How can I help you explore Laravel packages today?