socialiteproviders/weixin-web
Add WeChat (Weixin) web login to Laravel Socialite with this provider from SocialiteProviders. It implements WeChat Open Platform QR-code OAuth authorization, letting users sign in to your Laravel app by scanning a code, with a simple driver you register in Socialite.
1. Install the package (and the manager it depends on).
composer require socialiteproviders/weixin-web socialiteproviders/manager
Pin to the PHP 8 line if you're on a modern stack:
composer require "socialiteproviders/weixin-web:^4.1"
2. Register the provider listener. In app/Providers/EventServiceProvider.php (Laravel ≤10):
protected $listen = [
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
\SocialiteProviders\WeixinWeb\WeixinWebExtendSocialite::class.'@handle',
],
];
On Laravel 11+ (no default EventServiceProvider), put the same Event::listen(...) call in AppServiceProvider::boot().
3. Add credentials to config/services.php. The config key must match the driver name you pass to Socialite::driver(). For this package that is weixinweb (verify against WeixinWebExtendSocialite in vendor/ — the extend() key is the driver name):
'weixinweb' => [
'client_id' => env('WEIXINWEB_CLIENT_ID'),
'client_secret' => env('WEIXINWEB_CLIENT_SECRET'),
'redirect' => env('WEIXINWEB_REDIRECT_URI'), // must be registered in WeChat Open Platform
],
4. First use case — QR-code login for desktop web. Two routes and a controller is the whole integration:
// routes/web.php
Route::get('/auth/wechat', [WeChatAuthController::class, 'redirect'])->name('wechat.redirect');
Route::get('/auth/wechat/callback', [WeChatAuthController::class, 'callback'])->name('wechat.callback');
class WeChatAuthController extends Controller
{
public function redirect()
{
return Socialite::driver('weixinweb')->redirect();
}
public function callback()
{
$wxUser = Socialite::driver('weixinweb')->user();
$user = User::firstOrCreate(
['weixin_openid' => $wxUser->getId()],
[
'name' => $wxUser->getNickname() ?: 'WeChat User',
'avatar' => $wxUser->getAvatar(),
'weixin_unionid'=> $wxUser->getRaw()['unionid'] ?? null,
]
);
Auth::login($user);
return redirect()->intended('/dashboard');
}
}
Where to look first when something is wrong: vendor/socialiteproviders/weixin-web/src/Provider.php. It's small — it documents the exact driver key, the scope it sends (snsapi_login), and how raw WeChat fields are mapped onto Socialite's User. That file is your source of truth for config keys and field names, because the package has had no release since 2020-12-01 (4.1.0, the PHP 8 bump) and its README is thin.
The default flow above is the cleanest and works for most Laravel apps. Keep the redirect URI on a route that requires no auth, and Auth::login() in the callback. Store the Socialite user only after you resolve it to your own User model — don't leak the Socialite user object into the session.
WeChat's web login is a QR scan; many teams embed the QR inside an existing login modal instead of bouncing the user away. You lose nothing by building the URL yourself and reusing the same callback:
$params = http_build_query([
'appid' => config('services.weixinweb.client_id'),
'redirect_uri' => urlencode(route('wechat.callback')),
'response_type' => 'code',
'scope' => 'snsapi_login',
'state' => csrf_token(),
]);
$qrUrl = "https://open.weixin.qq.com/connect/qrconnect?{$params}#wechat_redirect";
Pass $qrUrl to the view and render it in an iframe (<iframe src="{{ $qrUrl }}">) or via the WeChat JS SDK. Because you now own the state, validate it on the callback yourself.
If the browser session isn't available on the callback (API token auth, separate SPA origin), use:
return Socialite::driver('weixinweb')->stateless()->redirect();
// callback
$wxUser = Socialite::driver('weixinweb')->stateless()->user();
You then own state/CSRF entirely (put a signed nonce in the OAuth state and verify it on return). Don't mix stateless and session flows on the same route — you'll get silent InvalidStateExceptions.
getId() returns the openid, which is scoped to this specific app. Fine if WeChat web is your only WeChat entry point.$wxUser->getRaw()['unionid'] and key accounts on it. It's the only stable cross-app identifier.unionid being absent: WeChat only returns it when the app is bound to the Open Platform account and the user relationship qualifies. Store it nullable.A common day-to-day pattern is letting an already-logged-in user attach WeChat:
$wxUser = Socialite::driver('weixinweb')->user();
if (Auth::check()) {
Auth::user()->update(['weixin_openid' => $wxUser->getId()]);
} else {
// find-or-create, or prompt to link
}
Uniqueness constraints: put a unique index on weixin_openid and weixin_unionid (nullable), and catch the resulting QueryException to recover from double-submits.
The login exchange calls sns/userinfo each time. If you need fresh profile data later (nickname/avatar changes), don't re-run OAuth — cache the profile at login and treat WeChat's data as best-effort. WeChat does not guarantee current, complete profile data.
Socialite is mockable, so your controller test shouldn't hit WeChat:
Socialite::shouldReceive('driver->user')->andReturn(
(new \Laravel\Socialite\Two\User)->map([
'id' => 'openid-123', 'nickname' => 'tester', 'avatar' => 'https://…',
])
);
For an end-to-end check, exercise Provider::user() against a fixture response — the mapping logic is the only real risk, and it's small.
If WeChat is one of several Socialite drivers (Google, GitHub, etc.), keep one SocialAuthController that switches on $provider and one config/services.php block per provider. This package slots in exactly like the others because it implements the same Two\Provider contract.
Socialite::driver('weixinweb') reads config('services.weixinweb') and resolves the extend key registered by the package. If you used a different key (e.g. weixin), the manager throws "Driver not supported" or uses the wrong credentials. Confirm both against the package source.php artisan config:cache after editing .env. The credentials are read through config(), so a stale cache is easy to mistake for a bad App Secret.localhost are not accepted in production. For local dev, tunnel a public HTTPS URL (ngrok/Cloudflare) and register that domain.snsapi_login is hardcoded for the web QR flow. Don't change it to snsapi_userinfo — that belongs to the official-account package (socialiteproviders/wechat). Mixing scopes produces invalid scope (10003 / 40029-class errors).40163 ("code been used") or 40029 ("invalid code"). Redirect the user back to /auth/wechat on those errors rather than surfacing them.getEmail() will be null. Design your user table so email is nullable or prompt for it after registration.utf8mb4 and utf8mb4_unicode_ci on the relevant columns, otherwise firstOrCreate blows up on insertion.headimgurl is unreliable — sometimes blank, sometimes http://, sometimes a default. Validate before persisting and fall back to a local placeholder.getRaw() is your escape hatch for unionid, province, city, country, sex, and any WeChat error payload. Always null-coalesce; keys vary by user and authorization state.user() in try/catch. Token-exchange failures throw \Laravel\Socialite\Two\InvalidStateException or a generic ClientException; catch both and return a friendly message with a "Try again" link back to /auth/wechat.^4.1) and run the full suite on your PHP version. Use composer why-not socialiteproviders/weixin-web when a Laravel/Socialite bump complains.Two\AbstractProvider contract) could break this package first, since it's unmaintained. Add a focused test around Provider::user() so an upgrade fails loudly.config('services.weixinweb') is populated).WEIXINWEB_REDIRECT_URI byte-for-byte matches what's registered in WeChat Open Platform./auth/wechat directly — if you don't land on open.weixin.qq.com/connect/qrconnect, the driver isn't resolving.error/errcode query params (40029, 40163, 10003, 40013 for bad AppID).$wxUser->getRaw() on the callback to see the exact payload WeChat returned before your mapping runs.stateless()).How can I help you explore Laravel packages today?