tourze/wechat-official-account-oauth2-bundle
A Symfony bundle from Tourze that adds WeChat Official Account OAuth2 authentication to your app. It streamlines the authorization flow, letting users sign in with their WeChat accounts while handling token exchange and profile retrieval through configurable services and standard Symfony integration...
This package targets WeChat 公众号 (Official Account) OAuth2 — the snsapi_base / snsapi_userinfo web authorization flow, not the QR/scan login and not the global cgi-bin/token API. At 0.0.1 treat it as pre-release: the changelog entry is empty, so there is no documented API contract yet.
composer require tourze/wechat-official-account-oauth2-bundle:0.0.1 — pin the exact tag, since a 0.0.x bump is allowed to break the public API under semver.src/ before the README. The distribution is published as a bundle (Symfony DI convention). There is very likely no WechatOAuthServiceProvider or config/wechat.php for Laravel. Find the concrete OAuth client class and its constructor signature — that tells you whether you get a ready-made service or need to instantiate it yourself with appid / secret / an HTTP client.AppServiceProvider binding the client from config('services.wechat_mp') and injecting Laravel's Illuminate\Http\Client\Factory. Ten lines, and it makes the rest of the integration testable.open.weixin.qq.com/connect/oauth2/authorize endpoint, one that receives ?code=&state=. Both must be outside auth middleware — this flow is the login.snsapi_base returns only an openid with no consent screen; that is enough to bind a session to a follower and is by far the most common day-to-day need. Add snsapi_userinfo only when you actually need nickname/avatar/UnionID.Before writing any code, confirm two things in the MP admin console: the account is a 认证服务号 (verified service account — subscription accounts cannot use snsapi_userinfo), and your callback host is set under 设置与开发 → 公众号设置 → 网页授权域名.
Two-route controller pair with a server-side state. Never trust state from the query string alone; stash it in the session when you build the authorize URL and compare on return. Bind the intended post-login destination to the same session entry.
public function redirect(Request $request)
{
$state = Str::random(32);
$request->session()->put('wechat.oauth', [
'state' => $state,
'intended' => $request->query('redirect', '/'),
]);
return redirect()->away($this->oauth->authorizationUrl(
redirectUri: route('wechat.oauth.callback'),
scope: 'snsapi_base',
state: $state,
));
}
Scope escalation as a two-stage flow. Do not request snsapi_userinfo on every page load — it shows a consent interstitial each time the user's authorization has lapsed, which is a real conversion killer. Pattern: silent snsapi_base on entry to capture openid, persist it against the user/session, and only fire the consent flow when the user hits a screen that genuinely needs profile data (profile completion, checkout with name prefill).
Bind openid to your users table, not to the session long-term. Add wechat_openid (and wechat_unionid if you're on Open Platform) with a unique index. First OAuth callback → look up or create → Auth::login(). This is the whole point of the package; everything downstream (notification templates, customer service messages) keys off that openid.
Cache the OAuth access token per openid, not globally. The sns/oauth2/access_token token is scoped to one user and lives ~2 hours; the refresh_token lives ~30 days. Cache key wechat:oauth:{openid} in Redis with a TTL of expires_in - 300, and refresh rather than re-running the whole redirect when you can. Do not reuse the account-wide cgi-bin/token here — the two are not interchangeable and mixing them produces baffling 40001 errors.
Multi-account support via a named config map. If you run more than one 公众号 (common: a service account plus a test account), key config by an account slug and resolve the client per request. Avoid a single global appid — retrofitting multi-tenancy after the fact means touching every call site.
Test with a faked HTTP layer. Because the flow is two outbound calls plus a browser redirect, Http::fake() on the authorize/token/userinfo endpoints gives you full coverage without a WeChat sandbox. Assert on the built authorize URL separately — that string is where the subtle bugs live (redirect_uri encoding, missing fragment).
Middleware for the WeChat browser. A small EnsureWeChatBrowser middleware checking the MicroMessenger user agent lets you fail fast with a QR-code page instead of letting users hit redirect_uri 参数错误 on desktop Safari.
10003 redirect_uri 域名与后台配置不一致. Plan a separate path (QR login or SMS) for out-of-WeChat traffic; a large share of your users will hit this.redirect_uri matching is exact on host and scheme. The domain must be configured in the MP console, must not include a port (WeChat only tolerates 80/443), and the scheme must match. http vs https mismatches are the single most common cause of a failed integration.errcode. 40029 = invalid code, 40163 = code already used, 40030 = invalid refresh token, 41008 = missing code. A naive $response->json('openid') returns null and you spend an afternoon debugging a "successful" request.errcode and restart the flow instead.code (a bearer credential for that user's token exchange) and the state. Scrub both in your request logging middleware.utf8mb4 columns — a utf8 (three-byte) column will throw Incorrect string value on a meaningful fraction of real users. This bites in production, never in seeded tests.UnionID is absent unless the account is bound to 开放平台. Don't design cross-app identity around it until you've verified the binding; the field simply won't appear in the response and any $data['unionid'] access will be an undefined-key warning.state is your only CSRF defence. WeChat echoes it back verbatim and does nothing else with it. Also keep it short and opaque — it lands in the URL, in referrers, and in WeChat's own logs.#wechat_redirect fragment on the authorize URL. WeChat's docs require it; hand-rolled URL builders frequently drop it. Behaviour is generally fine without it, but including it avoids inconsistency across clients.localhost is a non-starter. Use a public tunnel host that you've added as a 网页授权域名, or stub the token exchange entirely for local work. Keep a dedicated test 公众号 for this rather than reconfiguring the production domain.0.0.1 the most likely instability is the token/cache layer. If the bundle hardcodes an in-memory or filesystem token store, wrap it behind your own interface early — that's where you'll want Redis, and it's the change most likely to be forced on you by a 0.0.2 release.How can I help you explore Laravel packages today?