Home About Me

A Lightweight WeChat QR Login API in PHP

QR-code login does not seem all that complicated after all.

Why I wanted to build it

A while ago, I used an authenticated WeChat official account to make a WeChat push API. The original idea was to turn it into something similar to a platform like WxPusher.

But after thinking about it, that kind of all-in-one platform felt too heavy. Microservices are popular for a reason: each function should be able to run independently without dragging the rest of the system along with it. So I decided to split the features into separate modules, where every module can work on its own and avoid affecting the others.

The piece here is very simple: allow user A, usually the developer, to get user B’s WeChat user ID by asking user B to scan a QR code. Since this kind of thing is still easiest to throw together in PHP, the implementation looks like this:

Code

<?php
$appid='公众号APPID';
$secret='公众号Secret';
$token='和配置的Token配置一致即可';

ini_set('session.gc_maxlifetime', 7200);
session_id('Storagepush');
session_start();
if(!json_decode(file_get_contents('https://api.weixin.qq.com/cgi-bin/get_api_domain_ip?access_token='.$_SESSION['access_token']),true)['ip_list']){
$_SESSION['access_token']=json_decode(file_get_contents('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.$appid.'&secret='.$secret),true)['access_token'];
}
if(isset($_GET["action"])&&isset($_GET["key"])){
$_GET["key"]=addslashes($_GET["key"]);
if(strlen($_GET["key"])<6||strlen($_GET["key"])>32){
    die("Bad Key");
}

if($_GET["action"] == "set"){
    echo file_get_contents('https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token='.$_SESSION['access_token'], false, stream_context_create(array('http' => array('method'=>'POST','header'=>"Content-Type: application/json;charset=utf-8",'content'=>'{"expire_seconds": 3600, "action_name": "QR_STR_SCENE", "action_info": {"scene": {"scene_str": "auth'.$_GET["key"].'"}}}'))));
}
if ($_GET["action"] == "get") {
    if(isset($_SESSION['wxboxauth'.$_GET["key"]])){
        echo $_SESSION['wxboxauth'.$_GET["key"]];
    }else{
        echo "Empty";
    }
}

}else{

$timestamp=$_GET["timestamp"];
$nonce=$_GET["nonce"];
$tmpArr=array($token, $timestamp, $nonce);
sort($tmpArr, SORT_STRING);
if( sha1(implode($tmpArr)) == $_GET["signature"] ){
if($_GET["echostr"]){
echo $_GET["echostr"];
}else{
//  加载XML内容
$content = file_get_contents("php://input");
$p = xml_parser_create();
xml_parse_into_struct($p, $content, $vals, $index);
xml_parser_free($p);
if(($vals[$index['EVENT'][0]]['value'] == "subscribe" || $vals[$index['EVENT'][0]]['value'] == "SCAN") && isset($vals[$index['EVENTKEY'][0]]['value'])){
    if($vals[$index['EVENT'][0]]['value'] == "subscribe"){
        $vals[$index['EVENTKEY'][0]]['value'] = substr($vals[$index['EVENTKEY'][0]]['value'],8);
    }
    $_SESSION['wxbox'.$vals[$index['EVENTKEY'][0]]['value']] = $vals[$index['FROMUSERNAME'][0]]['value'];
    echo '<xml>
  <ToUserName><![CDATA['.$vals[$index['FROMUSERNAME'][0]]['value'].']]></ToUserName>
  <FromUserName><![CDATA['.$vals[$index['TOUSERNAME'][0]]['value'].']]></FromUserName>
  <CreateTime>'.time().'</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[成功请求登录!]]></Content>
</xml>';
}else{
echo "success";
}
}
}else{
    echo "Fail";
}
}

How the API is called

The interface only needs two GET parameters:

<table> <thead> <tr> <th>Parameter</th> <th>Required</th> <th>Method</th> <th>Value</th> </tr> </thead> <tbody> <tr> <td>action</td> <td>Yes</td> <td>GET</td> <td>set/get</td> </tr> <tr> <td>key</td> <td>Yes</td> <td>GET</td> <td>A random string between 6 and 32 bytes</td> </tr> </tbody> </table>

The key is used as the name of the temporary storage box for the user’s OpenID. To reduce the chance of duplicate names, a 32-character UUID is a better choice.

Creating the QR code

The developer first calls the set action to create a box for storing the user’s OpenID. After the request succeeds, WeChat returns a QR-code ticket and a QR-code URL. The QR code is valid for 1 hour.

If you want to generate the QR image yourself, you can use the returned URL as the QR-code content. Another option is to use WeChat’s ticket-to-QR-code endpoint directly. Append the returned ticket to this address:

https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=

That gives you the QR-code image.

Reading the OpenID

To retrieve the OpenID, call the get action with the same key. If the user has already scanned the QR code, the API returns the scanner’s OpenID. If the user has not scanned it yet, or if more than 2 hours have passed after scanning, the result is:

Empty

That simply means the storage box is empty.

A possible use case

For example, a WeChat push system usually needs the user’s OpenID before it can send messages. A website could first use this QR-code API to obtain the user’s OpenID, store it, and then push WeChat messages to that user when needed.

Because the OpenID is unique, the same mechanism can also be used for QR-code account binding or scan-to-login on a website. The actual business-side implementation is straightforward enough, so there is not much point in adding another sample here.

A note on safety

This code still does not include any serious anti-abuse handling, and it has not been properly security-audited. There is a real chance that vulnerabilities exist. If someone with security experience reviews it, the project can definitely be improved further.