Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing WeChat Web Authentication with Flask

Tech 1

Understanding the WeChat Web Login Flow

The WeChat web login process relies on QR code scanning and long-polling mechanisms. The official entry point is accessible via:

https://login.wx.qq.com/

Generating Authentication Tokens

Each QR code generation requires a unique identifier. The token retrieval endpoint follows this pattern:

https://login.wx.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Flogin.wx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=en_US&_={timestamp}

Flask Implementation

Below is a complete implementation demonstrating the authentication flow:

import re
import time
import requests
from flask import Flask, render_template, session, jsonify
from bs4 import BeautifulSoup

flask_app = Flask(__name__)
flask_app.secret_key = 'complex-secret-key-for-session-management'

def parse_xml_response(xml_content):
    """Extract authentication parameters from XML response"""
    credentials = {}
    soup = BeautifulSoup(xml_content, 'html.parser')
    error_node = soup.find('error')
    if error_node:
        for child in error_node.find_all():
            credentials[child.name] = child.get_text()
    return credentials

@flask_app.route('/auth')
def generate_qr():
    """Generate QR code for WeChat login"""
    timestamp = int(time.time() * 1000)
    token_url = f"https://login.wx.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=en_US&_={timestamp}"
    
    response = requests.get(token_url)
    uuid_match = re.search(r'uuid\s*=\s*"([^"]+)"', response.text)
    
    if uuid_match:
        qr_uuid = uuid_match.group(1)
        session['qr_uuid'] = qr_uuid
        return render_template('auth.html', qr_token=qr_uuid)
    
    return "Failed to generate QR code", 500

@flask_app.route('/status')
def check_auth_status():
    """Poll for QR scan status and login confirmation"""
    qr_uuid = session.get('qr_uuid')
    if not qr_uuid:
        return jsonify({'status': 'error', 'message': 'No active session'})
    
    timestamp = int(time.time() * 1000)
    status_url = f'https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid={qr_uuid}&tip=0&r=-976036168&_={timestamp}'
    
    response = requests.get(status_url)
    result = {'status': 'waiting'}
    
    if 'window.code=408' in response.text:
        result['status'] = 'waiting'
    elif 'window.code=201' in response.text:
        avatar_match = re.search(r"window\.userAvatar\s*=\s*'([^']+)';", response.text)
        if avatar_match:
            result['status'] = 'scanned'
            result['avatar'] = avatar_match.group(1)
    elif 'window.code=200' in response.text:
        redirect_match = re.search(r'window\.redirect_uri="([^"]+)";', response.text)
        if redirect_match:
            redirect_url = redirect_match.group(1) + "&fun=new&version=v2"
            auth_response = requests.get(redirect_url)
            
            credentials = parse_xml_response(auth_response.text)
            session['auth_tokens'] = credentials
            result['status'] = 'confirmed'
    
    return jsonify(result)

@flask_app.route('/dashboard')
def load_dashboard():
    """Initialize user dashboard after successful login"""
    tokens = session.get('auth_tokens')
    if not tokens:
        return "Unauthorized", 401
    
    init_url = f"https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-979112921&lang=en_US&pass_ticket={tokens['pass_ticket']}"
    
    payload = {
        'BaseRequest': {
            'DeviceID': "e700290354098676",
            'Sid': tokens['wxsid'],
            'Skey': tokens['skey'],
            'Uin': tokens['wxuin'],
        }
    }
    
    response = requests.post(init_url, json=payload)
    response.encoding = 'utf-8'
    profile_data = response.json()
    
    return render_template('dashboard.html', user_profile=profile_data)

if __name__ == '__main__':
    flask_app.run(debug=True)

Frontend Templates

auth.html - QR Code Display with Polling

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WeChat Login</title>
</head>
<body>
    <div style="max-width: 300px; margin: 50px auto; text-align: center;">
        <h2>Scan QR Code to Login</h2>
        <img id="qrImage" src="https://login.weixin.qq.com/qrcode/{{qr_token}}" 
             alt="WeChat QR Code" style="width: 250px; height: 250px;">
        <div id="statusMessage"></div>
    </div>

    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
        $(document).ready(function() {
            function pollStatus() {
                $.getJSON('/status', function(data) {
                    if (data.status === 'waiting') {
                        $('#statusMessage').text('Waiting for scan...');
                        setTimeout(pollStatus, 2000);
                    } else if (data.status === 'scanned') {
                        $('#qrImage').attr('src', data.avatar);
                        $('#statusMessage').text('Scan successful! Confirm on mobile.');
                        setTimeout(pollStatus, 2000);
                    } else if (data.status === 'confirmed') {
                        window.location.href = '/dashboard';
                    } else {
                        $('#statusMessage').text('Error occurred');
                    }
                }).fail(function() {
                    setTimeout(pollStatus, 5000);
                });
            }
            
            pollStatus();
        });
    </script>
</body>
</html>

dashboard.html - User Information Display

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WeChat Dashboard</title>
</head>
<body>
    <div style="max-width: 800px; margin: 0 auto; padding: 20px;">
        <h1>Welcome, {{user_profile.User.NickName}}!</h1>
        
        <section>
            <h2>Recent Contacts</h2>
            <ul>
                {% for contact in user_profile.ContactList %}
                    <li>{{contact.NickName}}</li>
                {% endfor %}
                <li><a href="#">View All Contacts</a></li>
            </ul>
        </section>

        <section>
            <h2>Official Accounts</h2>
            {% for account in user_profile.MPSubscribeMsgList %}
                <div style="margin-bottom: 20px;">
                    <h3>{{account.NickName}}</h3>
                    <ul>
                        {% for article in account.MPArticleList %}
                            <li><a href="{{article.Url}}" target="_blank">{{article.Title}}</a></li>
                        {% endfor %}
                    </ul>
                </div>
            {% endfor %}
        </section>
    </div>
</body>
</html>

Authentication Flow Summary

  1. Token Generation: Request a unique UUID from the WeChat server
  2. QR Rendering: Display the QR code using the UUID
  3. Status Polling: Continuously check scan status via long-polling
  4. Avatar Fetch: Retrieve user avatar upon successful scan
  5. Login Confirmation: Wait for mobile confirmation and extract redirect URL
  6. Credential Exchange: Parse XML response for session tokens
  7. Session Initialization: Post tokens to retrieve complete user profile
  8. Data Rendering: Display contacts and subscription content

Access the application at http://127.0.0.1:5000/auth and navigate to /dashboard after successful authentication.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.