Open apps like Taobao, WeChat, or Douyin on your phone, and you will find that QR code login has become a standard feature for almost all multi-device (PC + mobile) applications.

For users, QR code login greatly enhances the experience by eliminating the need to tediously type account credentials on a keyboard; simply pick up your phone, scan, and authorize. For enterprises, this technology serves as an important operational tool to divert PC traffic to mobile, boosting app daily active users and user stickiness.

So, what technical implementation principles lie behind this seemingly simple scan-and-tap action? How do the frontend and backend cooperate?

I. Why Do We Need QR Code Login?

Before diving into technical details, let's clarify the business background. Traditional username/password login has several pain points:

  1. High input cost: Especially on public or unfamiliar devices, entering a long password is very inconvenient.
  2. Security risks: Entering passwords on internet cafe or public computers carries the risk of being stolen by keyloggers and other trojans.
  3. Multi-device fragmentation: Users are clearly already logged in on their mobile phones, yet they must go through the identity verification process again on the PC.

QR code login perfectly solves these problems. Its essence is:Using the already authenticated mobile app to authorize the unauthenticated PC client.

II. The Core Lifecycle of QR Code Login

Whether it's WeChat scan, QQ scan, or various internal system scan logins, the underlying logic remains fundamentally the same. We can abstract the entire complex lifecycle into five major steps and streamline them intothree core phasesin engineering implementation.

The complete five-step user experience:

  1. Generate QR Code: The PC displays a time-sensitive QR code.
  2. Scan: The user takes out their phone app and scans the QR code.
  3. Confirm Login: The mobile app prompts Allow login on PC? and the user taps confirm.
  4. PC Polling/Listening: The PC perceives the successful authorization.
  5. Login Successful: The PC obtains the identity credential (Token) and accesses the system normally.

In backend architecture and business flow design, we distill this into three core technical steps:Generate QR Code -> Scan -> Confirm Login. Below, we will conduct a source-code-level deep analysis of each.


III. Core Step Breakdown and Technical Implementation

To implement QR code login, we need a key middleware:Redis. Because QR code login involves state sharing and communication between multiple devices (PC browser and mobile app), Redis, with its high-performance in-memory read/write and key expiration mechanism, is the best choice for handling such state transitions.

Phase 1: Generate QR Code (Create Authorization Credential)

When the user clicks QR Code Login on the PC, the story begins.

1. Frontend requests backend to generate credential

The PC frontend sends a request to the backend server to obtain the login QR code.

2. Backend generates a globally unique ID (QR_Code_ID)

Upon receiving the request, the backend generates a globally unique ID (usually a UUID). This ID is the soul of this QR code and will run through the entire login process.

3. Redis State Storage

After generating theQR_Code_ID, the backend stores it in Redis. Here, we need to maintain the state machine of the QR code. The initial state isNEW(pending scan).

To prevent security vulnerabilities and memory leaks caused by indefinitely valid QR codes, we must set an expiration time (TTL) for this Key, typically 1 to 5 minutes.

Redis data structure example:

  • Key: qrcode:status:{QR_Code_ID}
  • Value: NEW (or corresponding enum value 0)
  • Expire: 300s

4. Respond to frontend and generate image

The backend returns thisQR_Code_IDto the PC client.

Regarding the generation of the QR code image, there are two approaches:

  • Backend Generation: The backend uses libraries like ZXing to combine theQR_Code_IDwith a specific URL Scheme to generate a QR code image, converts it to a Base64 string, and returns it to the frontend for direct rendering.
  • Frontend Generation (Recommended): The backend only returns theQR_Code_IDand the protocol link for scan redirection (e.g.,myapp://login/auth?qrcodeId=xxx). The frontend uses a JavaScript library (likeqrcode.js) to generate the image directly in the local browser. This approach significantly reduces the CPU computation pressure on the server.

Phase 2: App Scan (State Transition and Temporary Authorization)

After the QR code is generated, a pattern waiting to be scanned appears on the PC screen. At this point, the PC cannot just wait idly; it needs to know whether the user has scanned it or not.

1. The PC's Eager Anticipation (State Synchronization)

The PC needs to continuously check the status of this QR code with the backend. There are three common technical solutions:

  • Short Polling: The frontend usessetIntervalto send an HTTP request to the backend every 1-2 seconds. Implementation is extremely simple, but it puts high pressure on the server and generates many invalid requests. (Taobao still uses polling in some scenarios).
  • Long Connection (WebSocket): A bidirectional communication channel is established between the PC and the backend. Once the state changes, the backend actively pushes it to the frontend. Real-time performance is extremely high, but it increases the architectural complexity of the backend.
  • SSE (Server-Sent Events): A unidirectional long connection technology where the backend can push data to the frontend. It is very suitable for scenarios like QR code login, which involves backend-to-frontend unidirectional notification.

2. App initiates scan

The user opens the mobile app, which must already be in a logged-in state. The app calls the system camera to scan the QR code and parses out theQR_Code_ID.

3. App sends scan request to backend

After obtaining theQR_Code_ID, the app immediately sends an I scanned it request to the backend.

This request is critical and must carry two things:

  • The just-parsedQR_Code_ID.
  • The current user's identity credential from the App (e.g.,App_User_Token).

4. Backend processes scan logic

Upon receiving the request, the backend first verifies the legitimacy of theApp_User_Token. If the Token is invalid, it will require the user to log in on the App first.

After successful verification, the backend confirms which user is scanning. Then, the backend updates Redis:

  • Changes the status ofqrcode:status:{QR_Code_ID}fromNEW(pending scan) toSCANNED(scanned, pending confirmation).
  • Note: Generate Temporary Token (Temp_Token). The backend generates a one-time temporary Token, binds it with theQR_Code_IDand stores it in Redis. Then it returns thisTemp_Tokento the App.

Why not log in directly? Why the extra step of a Temporary Token?

This is forsecurity and user control. Scanning does not mean immediate authorization. The user might have scanned accidentally or been tricked into scanning without knowing. Therefore, after scanning, the App must display a Confirm login on PC interface on the phone. This temporary Token serves as anti-counterfeiting verification for the next confirmation step, ensuring that the confirmation request indeed comes from the same phone that just scanned.

At this point, the PC perceives through polling or WebSocket that the status in Redis has changed toSCANNED. The page will immediately provide feedback, usually overlaying a semi-transparent mask on the QR code and prompting: Scan successful, please confirm login on your mobile device.


Phase 3: Confirm Login (Issue Formal Credential)

Now, the pressure shifts to the mobile app, where the interface displays the Confirm Login on Web button.

1. App sends confirmation request

The user clicks Confirm Login. The app carries the temporary credential obtained in the previous step,Temp_Token, and sends the final confirmation request to the backend.

2. Backend performs final verification and authorization

Upon receiving the confirmation request, the backend begins executing the core login logic:

  • Verify Temp_Token: Check in Redis whether this temporary Token is legitimate and matches theQR_Code_IDbeing operated on.
  • Anti-replay handling: Once verification succeeds, immediately delete thisTemp_Tokenfrom Redis (or mark it invalid) to ensure its one-time use security.
  • Status change to confirmed: Change the status ofqrcode:status:{QR_Code_ID}fromSCANNEDtoVERIFIED(confirmed/used).
  • Issue PC Token: Since the user has agreed, the backend now generates a brand newPC_User_Tokenwith corresponding permissions for the PC.
  • Bind user information: Bind thePC_User_Tokenwith the user's account information and store it in Redis (or the backend's Session system).
  • Associate Token with QR Code: To allow the PC to obtain this Token, the backend needs to temporarily store the generatedPC_User_Tokenin Redis, with the Key still being thisQR_Code_ID.

3. PC obtains the key, the door opens

That polling request (or WebSocket listener) on the PC finally gets the good news. It queries that the QR code status has becomeVERIFIED, and the backend includes the just-generatedPC_User_Tokenin the response.

After the PC frontend receives this Token, it stores it in the local Cookie or LocalStorage. Subsequently, the page redirects to the system's homepage.

At this point, the entire QR code login process is perfectly closed-loop!


IV. Core Code Structure Display

Below is a pseudocode skeleton of the backend processing logic, demonstrating the state machine's judgment logic.

Enum Class: QR Code Status Definition

public enum QRCodeStateEnum {
    NEW(0, "待扫描"),
    SCANNED(1, "已扫描,待确认"),
    VERIFIED(2, "已确认登录"),
    EXPIRED(3, "二维码已过期");

    // ... 省略属性和构造方法
}

Backend Confirm Login Interface

@PostMapping("/qrcode/confirm")
public Result confirmLogin(@RequestParam("tempToken") String tempToken,
                           @RequestHeader("App-Token") String appToken) {

    // 1. 校验 App Token 是否有效
    UserInfo user = authService.verifyAppToken(appToken);
    if (user == null) {
        return Result.error(ErrorCodeEnum.LOGIN_FAIL, "App登录已失效,请重新登录");
    }

    // 2. 校验临时 Token,并解析出对应的二维码 ID
    String qrCodeId = redisService.get(RedisKey.TEMP_TOKEN_PREFIX + tempToken);
    if (StringUtils.isBlank(qrCodeId)) {
        return Result.error(ErrorCodeEnum.EXPIRED, "操作已过期,请重新扫码");
    }

    // 3. 防并发与幂等:删除临时 Token
    redisService.delete(RedisKey.TEMP_TOKEN_PREFIX + tempToken);

    // 4. 生成 PC 端的正式 Token
    String pcToken = jwtUtils.generateToken(user.getUserId());

    // 5. 更新二维码状态为已确认,并将 PC Token 放入 Redis 供前端轮询获取
    Map<String, Object> stateData = new HashMap<>();
    stateData.put("status", QRCodeStateEnum.VERIFIED.getCode());
    stateData.put("pcToken", pcToken);

    redisService.set(
        RedisKey.QR_CODE_STATUS_PREFIX + qrCodeId,
        stateData,
        60,
        TimeUnit.SECONDS
    );

    return Result.success("登录成功");
}

PC Polling Interface

@GetMapping("/qrcode/status")
public Result checkStatus(@RequestParam("qrCodeId") String qrCodeId) {

    Object data = redisService.get(RedisKey.QR_CODE_STATUS_PREFIX + qrCodeId);
    if (data == null) {
        return Result.error(ErrorCodeEnum.QRCODE_EXPIRED, "二维码已失效");
    }

    // 解析 Redis 中的状态
    Map<String, Object> stateData = (Map<String, Object>) data;
    Integer status = (Integer) stateData.get("status");

    switch (status) {
        case 0: // NEW
            return Result.success(data).setMessage("请用手机扫码");
        case 1: // SCANNED
            return Result.success(data).setMessage("已扫描,请在手机端确认");
        case 2: // VERIFIED
            // 此时 data 中包含了 pcToken
            return Result.success(data).setMessage("登录成功");
        default:
            return Result.error(ErrorCodeEnum.SERVER_ERROR, "未知状态");
    }
}

V. Abnormal Scenarios and Security Design

In real enterprise applications, a series of abnormal scenarios may be encountered:

1. Security Consideration: What if the QR code is hijacked?

The entire communication process must strictly use the HTTPS protocol to prevent man-in-the-middle packet capture. Additionally, the QR code'sQR_Code_IDshould have sufficient randomness (like UUIDv4) to prevent brute-force traversal or guessing. When the app scans, device environment detection can also be added (such as IP address location risk alerts). If the scanning IP and the PC request IP are found to be across provinces, secondary verification can be added.

2. Why must it be divided into Scanned and Confirmed states?

This is a classic CSRF (Cross-Site Request Forgery) defense concept. If scanning meant immediate login, a hacker could disguise your login QR code as a tempting image (like Scan to claim a red packet) and post it in a group. Unsuspecting users who scan it with their app would directly log the hacker's PC into their account. Introducing the Pending Confirmation state forces the user to see a clear prompt like Confirm login to WeChat for Windows on the app interface, returning the final authorization decision to the user.

3. How to design the QR code expiration mechanism?

Rely on Redis's TTL mechanism. When generating theQR_Code_ID, set the Key's expiration time to 3 minutes. When the PC's polling interface finds that the Key cannot be queried, it can determine that the QR code has expired. The frontend then displays a mask saying QR code expired, please click to refresh. Refreshing restarts the Phase 1 process.