WeCom Login
This solution signs users in to a WeCom self-built app through a custom OAuth 2.0 identity source plus a CloudBase Run relay service. To CloudBase the relay acts as a standard OAuth 2.0 authorization server; to WeCom it calls the web authorization APIs, aligning the two protocols.
WeCom redirects the code straight back to CloudBase, so the relay never handles a browser callback. Every request to WeCom APIs leaves from the CloudBase Run fixed public egress IP; add that IP to WeCom's trusted IP list, and no NAT gateway or EIP is needed.
Web only, and the page must be opened in the built-in browser of the WeCom client. Mini Program platforms are not supported.
Protocol Differences
| Step | Standard OAuth 2.0 | WeCom web authorization |
|---|---|---|
| Authorization request | client_id redirect_uri response_type=code scope | appid agentid redirect_uri scope=snsapi_base |
| Credential exchange | POST to the token endpoint, client credentials in the body or Basic header | Fetch the enterprise access_token first, then exchange code for a UserId |
| User information | Standard claims: sub name email phone_number | auth/getuserinfo and auth/getuserdetail, fields userid mobile email |
| Source IP | No restriction | Trusted IPs required, otherwise 60020 |
The relay service absorbs every difference. On the CloudBase side you configure one standard OAuth 2.0 identity source.
Architecture
| Component | Responsibility |
|---|---|
| Business page | Redirects to the hosted login page when not signed in, reads the session after the callback |
| CloudBase Authentication | Starts OAuth authorization, receives the code from WeCom, exchanges it for a token and user information, creates the user and issues the session |
| CloudBase Run relay service | Translates authorization parameters; calls WeCom APIs with the CorpID and Secret passed in by CloudBase to get the UserId; issues its own access token |
| WeCom | Shows the authorization page, issues the code, returns the UserId and sensitive fields |
Prerequisites
| Item | Requirement |
|---|---|
| CloudBase environment | Personal edition or above; the fixed public egress IP depends on plan capability |
| CloudBase Run service | Deployed, bound to a public domain, with the fixed public egress IP enabled |
| WeCom | A self-built app with CorpID, AgentId and Secret |
| Domain | The domain of the CloudBase callback address is registered as a WeCom trusted domain and has passed ownership verification |
Step 1: Configure the WeCom Self-Built App
- WeCom Admin Console → App Management → Self-built, create an app, and record the AgentId and Secret
- My Enterprise → Enterprise Info, and record the CorpID
- App details → Web Authorization and JS-SDK → Set Trusted Domain, enter the domain of the CloudBase callback address, and place
WW_verify_xxx.txtat that domain root as prompted - App details → Developer Interface → Trusted IP, and enter the fixed public egress IP of the CloudBase Run service
⚠️ Without a trusted IP, calls to
qyapi.weixin.qq.comreturn60020 not allow to access from your ip.
The trusted domain constrains the redirect_uri in the authorization URL. Here redirect_uri points at the CloudBase callback address, so the verified domain is the CloudBase callback domain, not the relay domain.
Step 2: Deploy the CloudBase Run Relay Service
Endpoints
| Path | Method | Purpose | Caller |
|---|---|---|---|
/authorize | GET | Translates the standard OAuth authorization request into a WeCom authorization URL and redirects the browser | CloudBase |
/token | POST | Exchanges the code returned by WeCom for a UserId and issues an access token | CloudBase |
/userinfo | GET | Exchanges a Bearer access token for standard user information | CloudBase |
No callback path is needed. WeCom sends the code straight back to CloudBase, and the relay is only called server to server.
Environment Variables
# AgentId of the self-built app, from the app details page
WECOM_AGENT_ID=1000002
# Allowed CloudBase callback addresses, comma separated, exact match
CLOUDBASE_REDIRECT_URIS=https://{envId}.{region}.tcb-api.tencentcloudapi.com/auth/v1/callback
CorpID and Secret are not configured on the relay: CloudBase passes the Client ID and Client Secret from the identity source, and the relay uses them directly as corpid and corpsecret.
Authorization Endpoint
Accept client_id, redirect_uri, response_type, state and scope, then do two things and redirect:
- Check that
redirect_uriexactly matches one entry inCLOUDBASE_REDIRECT_URIS; return 400 otherwise - Build the WeCom authorization URL, using
client_id(the enterprise CorpID) asappidand passingredirect_uriandstatethrough unchanged
https://open.weixin.qq.com/connect/oauth2/authorize
?appid={client_id}
&redirect_uri={urlencode(redirect_uri from CloudBase)}
&response_type=code
&scope=snsapi_base
&state={state from CloudBase}
&agentid={WECOM_AGENT_ID}
#wechat_redirect
Both redirect_uri and state are passed through unchanged, so WeCom lands directly on CloudBase and CloudBase validates its own state. Pass-through is only safe behind the allow-list check in step 1; without it this becomes an open redirect. The authorization stage never receives client_secret, so WeCom decides at /token whether the client ID is valid.
Token Endpoint
/token receives the code returned by WeCom, not a code issued by the relay. Handle it as grant_type=authorization_code:
- Read
client_idandclient_secretfrom the request body or a Basic header; support both GET https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={client_id}&corpsecret={client_secret}for the enterpriseaccess_token; cache it for 7200 seconds and refresh ahead of expiry. WeCom validates the credentials and returns40001on failure, which the relay maps toinvalid_clientGET https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token={TOKEN}&code={CODE}for theUserId- For mobile or email, set
scopetosnsapi_privateinfoin the authorization URL and callPOST https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetailwith the returneduser_ticket - Issue the relay's own access token and return it to CloudBase
{
"access_token": "eyJhbGciOiJIUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
The code is single-use and valid for 5 minutes, so a retry fails. The enterprise access_token is shared across instances; store it in Redis or a database to avoid concurrent refreshes invalidating an older token.
Encode userid, name and email into a JWT as the relay access token. /userinfo can then verify the signature instead of looking up session state.
Userinfo Endpoint
GET /userinfo reads Authorization: Bearer {access_token} and returns standard claims:
{
"sub": "zhangsan",
"name": "Zhang San",
"picture": "https://example.com/avatar.png",
"email": "zhangsan@example.com",
"phone_number": "13800000000"
}
Use the WeCom UserId as sub. Within one enterprise the UserId is unique and stable, so it survives renamed accounts and changed phone numbers, and remains the identity anchor across logins.
Deployment and Fixed Egress IP
The steps below use CloudBase Run as an example. Any service with a fixed public egress IP works as well.
- CloudBase Run → Service → Deploy in any language, listening on
PORT - Note the public default domain on the service details page; it is the domain for the three identity source endpoints
- Service settings → Network settings → enable Fixed Public Egress IP
- Add that IP to the WeCom trusted IP list
⚠️ When the service also uses a VPC, public requests are routed into the VPC by default. In that case configure a NAT gateway and use its egress IP as the trusted IP.
Step 3: Configure the CloudBase Identity Source
- CloudBase console → Authentication → Enterprise Identity Sources → add a custom OAuth 2.0 identity source
- Fill in the table below. All three endpoints point at the CloudBase Run service hosting the relay: copy the public default domain (or a bound custom domain) from that service's details page and append
/authorize,/tokenand/userinfo; do not type a domain by hand
| Field | Value |
|---|---|
| Identity source name | WeCom |
| Authorization endpoint | CloudBase Run service domain + /authorize |
| Token endpoint | CloudBase Run service domain + /token |
| Userinfo endpoint | CloudBase Run service domain + /userinfo |
| Client ID | Enterprise CorpID, from WeCom Admin Console → My Enterprise → Enterprise Info → Enterprise ID |
| Client Secret | Secret of the self-built app, from the app details page |
| Scope | openid; the relay service parses it as needed |
| Callback address | The console-generated https://{envId}.{region}.tcb-api.tencentcloudapi.com/auth/v1/callback; also set it in CLOUDBASE_REDIRECT_URIS on the relay service |
| Client authentication | CLIENT_SECRET_POST |
| Auto sign-up | When enabled, the first login creates the user; when disabled, register first and then bind the identity source |
- Authentication → Hosted Login Page, select WeCom and save
Login Sequence
Troubleshooting
| Symptom | What to check |
|---|---|
WeCom reports an invalid redirect_uri | The redirect_uri domain is not in the trusted domain list, or redirect_uri is not URL-encoded |
WeCom reports 50001 redirect_url not registered | The trusted domain is set to the relay domain; it must be the domain of the CloudBase callback address |
60020 in the /token or /userinfo logs | The trusted IP is missing or wrong, or the fixed public egress IP is not enabled on the CloudBase Run service |
CloudBase returns invalid_client | Client ID is not the CorpID, or the Secret is not the self-built app Secret; it may also be that client authentication should be CLIENT_SECRET_BASIC but is set to CLIENT_SECRET_POST |
| Login succeeds without mobile or email | scope is still snsapi_base; switch to snsapi_privateinfo and call getuserdetail |
| First login reports the user does not exist | Auto sign-up is disabled on the identity source; register first and bind with bindWithProvider |
| The authorization endpoint returns 404 or times out | The endpoint domain is not the service's public default domain, or public access is disabled on the service |
| The page does not redirect at all | The page is not opened in the built-in browser of the WeCom client; WeCom web authorization does not apply to external browsers |
FAQ
Q: Why does the relay need no callback address?
WeCom accepts exactly one redirect_uri. Point it at the CloudBase callback address and the code lands on CloudBase, which then forwards it to the relay's /token. The relay needs no public callback entry, no state storage and no session mapping.
Q: Why is the trusted domain a CloudBase domain?
WeCom validates the domain of redirect_uri. Since the redirect target is the CloudBase callback address, the trusted domain is the CloudBase callback domain, independent of the CloudBase Run default domain hosting the relay.
Q: Does the relay have to run on CloudBase Run?
No. CloudBase Run is chosen because it provides a fixed public egress IP, which WeCom requires; whether the relay shares a network path with WeCom makes no difference. Any service with a fixed public egress IP works.
Q: Can one relay service serve multiple enterprises?
No. WeCom binds a trusted IP to a single enterprise, so one egress IP serves one enterprise. To onboard several enterprises, deploy one relay per enterprise, each with its own fixed public egress IP.