tourze/wechat-work-security-bundle
A Symfony bundle that integrates WeChat Work (企业微信) security features into your PHP application, handling message encryption and decryption, signature verification, and callback validation so you can safely receive and respond to WeChat Work events without writing the cryptography yourself.
What this is. tourze/wechat-work-security-bundle is a Tourze-published package that wires WeChat Work (企业微信 / WeCom) authentication and security helpers into a PHP application. The -bundle suffix and the Tourze namespace convention indicate a Symfony bundle, not a native Laravel package — this matters a lot for how you consume it (see Gotchas).
Version 0.0.1 reality check. This is the first tagged release. The only release artifact is a changelog link with no described features, fixes, or upgrade notes. Treat the public API as unstable: expect namespace moves, constructor signature changes, and config key renames in 0.x. Pin the exact version ("tourze/wechat-work-security-bundle": "0.0.1") and read the source before relying on it.
Minimal steps.
composer require tourze/wechat-work-security-bundle:0.0.1composer.json (declared dependencies tell you whether it expects symfony/security-bundle, symfony/http-client, etc.) and then src/. In a Tourze bundle the two files that answer 90% of your questions are:
src/DependencyInjection/*Extension.php — the real config schema (service ids, env var names, defaults).src/DependencyInjection/*Bundle.php / *Pass.php — what gets auto-registered and any compiler passes.config/bundles.php if you are in Symfony, or, in Laravel, pull the underlying service classes manually (see Gotchas).corp_id), the app's Secret (corp_secret / agent secret), and the Agent ID for the self-built app you are authenticating against.First use case. The highest-value thing this class of package gives you is: given a WeChat Work OAuth code, resolve the employee's userid and profile. That is the single flow to get working first — a controller route that receives ?code=... from the WeChat Work authorization redirect, exchanges it, and hands you a stable employee identifier you can map to a local user row. Everything else (signature verification, JS-SDK tickets, department sync) is additive.
Config-first, hardcoded-never. Register credentials through the bundle's extension config (or the equivalent Laravel config/services.php entry if you are wiring services by hand) reading from env: WECHAT_WORK_CORP_ID, WECHAT_WORK_CORP_SECRET, WECHAT_WORK_AGENT_ID. Never inline the secret — WeCom secrets leak via committed .env.example files constantly, and a leaked corp secret grants full API access to the corp's contacts.
OAuth login flow. The canonical shape:
/oauth/start -> redirect to https://open.weixin.qq.com/connect/oauth2/authorize
?appid=CORP_ID&redirect_uri=<encoded callback>&response_type=code
&scope=snsapi_base&state=<csrf nonce>#wechat_redirect
/oauth/callback -> validate `state` against the session nonce
exchange `code` -> access_token + userid
map userid -> local user, log in, redirect
Use snsapi_base (silent, returns userid only) for normal logins and snsapi_privateinfo only when you actually need avatar/mobile/email — the broader scope prompts the user and requires the app to have those permissions granted in the admin console.
Map userid to your own identity. Do not let the WeCom userid become your primary key or your users.email. Store it in a nullable, unique, indexed column (wecom_user_id) and resolve/create a local user from it. Employees leave, get renamed, and move departments; the external identifier should be an attribute, not the identity.
Wrap it in a service, not in controllers. Inject the package's client/exchange service into a thin WeComAuthenticator of your own. This is the single most useful pattern with a 0.0.1 dependency: when the upstream class is renamed in 0.0.2, you change one file instead of grepping controllers.
Cache access tokens centrally. WeCom access_token is corp-wide and rate-limited, and fetching it per-request will get you throttled. Cache it (Laravel Cache::remember, expiry ~7000s) under a single key, and use a lock around the refresh to avoid a thundering herd on cold cache.
Test with fakes. Point the HTTP client at a stub (Symfony MockHttpClient, or Laravel Http::fake() if you bypass the bundle's transport) returning a canned token/userinfo payload. Because docs are absent at 0.0.1, write a contract test asserting the exact JSON keys you depend on — it doubles as your upgrade canary.
Callback/message signature verification. If you also use this for the callback URL (event push, trusted-domain checks), verify signature before parsing the body, and make the route CSRF-exempt but signature-checked. Keep the verification in middleware so no future controller can forget it.
symfony/security-bundle and hand-register the bundle in a minimal kernel — usually not worth it — or (b) bypass the bundle wiring and instantiate the few concrete client/service classes yourself in a small Laravel service provider. Option (b) is almost always the pragmatic choice, but it means the bundle's DI config is not your config, so read the extension class to learn the real constructor arguments.^0.0.1 constraint resolves to <0.0.2, so you are safe by accident, but do not loosen it to ^0.1 or * without reading the diff. Vendor the class names you depend on into your own adapter.src/DependencyInjection/; that file is the documentation. If the extension has no Configuration tree, assume env vars are read directly with $_ENV/getenv and grep for them.errcode 60020 (IP not in whitelist) if your server egress IP is not added. Both are console configuration, not code — check them before debugging the bundle.errcode on failure. Any client that only checks the HTTP status will treat a failed token exchange as success. Assert explicitly on errcode == 0 in your adapter, and log errcode + errmsg (never the secret or the raw token).access_token early (e.g. secret rotation). Handle errcode 40001/42001 by clearing the cache and retrying exactly once — a retry loop without cache invalidation just burns rate limit.json_encode flags (JSON_UNESCAPED_UNICODE) don't mangle them.*Interface in src/ are your upgrade-stable seams. Prefer implementing/decorating those interfaces over subclassing concrete classes; Tourze packages frequently swap concrete implementations between patch releases.git diff between tags, run your contract test, and check the DI extension for renamed config keys before deploying.How can I help you explore Laravel packages today?