Chapter 9: Backend Security - How to Validate LIFF Tokens in FastAPI to Prevent Forgery?

In our previous frontend lessons, we taught how React can obtain liff.getAccessToken() and include it in the headers of the punch-in API request sent to the backend. But if you're the backend engineer developing the FastAPI service, how do you know this Token isn't just some random string fabricated by a hacker using Postman... or even MS Paint?

There's a famous saying in web development: "Never trust any data coming from the frontend." If you don't validate it, this will become the most critical and easily exploitable vulnerability in your entire punch-in system! Today, we'll build an impenetrable wall in FastAPI to completely seal this security hole.


๐Ÿ›ก๏ธ 1. Why Must We Validate Tokens? (The Costly Business Consequences)

If your punch-in API (POST /api/punch) doesn't validate Tokens, any hacker or tech-savvy employee who knows your API endpoint can simply write a script to send a fake JSON like { "action": "check-in", "user_id": "U123456..." } to your server.

The result? They could be lounging on a Bali beach while their script punches them in at 9 AM sharp every morning, with the system being none the wiser. If the boss finds out, they'll likely withhold your final payment and sue you.

Therefore, when the backend receives this so-called Token key, we must do one crucial thing: Take this key to Line's official verification center and ask: "Was this key genuinely issued by you? Which real Line ID was it issued to? Has it expired?"


๐Ÿงฑ 2. Implementing a Robust Token Validation Mechanism in FastAPI

We'll leverage FastAPI's most powerful feature: Dependency Injection, to create an interceptor that acts like a "security guard". Anyone wanting to punch in must first pass the guard's inspection.

๐Ÿ”ฅใ€Vibe Prompt Practical Incantationใ€‘ I'm developing a Line punch-in backend with FastAPI. The frontend sends credentials via Header: 'Authorization: Bearer <LIFF_TOKEN>'. Please help me write a Dependency function verify_liff_token that: 1. Extracts Token from Header, throws 401 if missing. 2. Uses httpx (async) to call Line's verification API (https://api.line.me/oauth2/v2.1/verify). 3. Verifies the response's client_id matches my LINE_LOGIN_CHANNEL_ID (prevent CSRF). 4. If valid, calls Line Profile API (https://api.line.me/v2/profile) to get real userId and returns it. 5. Inject this Dependency into a test POST /api/punch route.

The AI-generated high-quality code would look like this:

import httpx
from fastapi import FastAPI, Depends, HTTPException, Header
import os

app = FastAPI()
LINE_LOGIN_CHANNEL_ID = os.getenv("LINE_LOGIN_CHANNEL_ID")

# Security Guard: Validates all Tokens
async def verify_liff_token(authorization: str = Header(None)):
    if not authorization or not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Guard: No credentials provided!")
    
    token = authorization.split(" ")[1]
    
    # Step 1: Verify key authenticity with Line's official server
    async with httpx.AsyncClient() as client:
        verify_res = await client.get(f"https://api.line.me/oauth2/v2.1/verify?access_token={token}")
        
        if verify_res.status_code != 200:
            raise HTTPException(status_code=401, detail="Guard: Invalid or expired key!")
            
        data = verify_res.json()
        
        # Step 2: Prevent cross-site attacks (Critical!)
        # Ensure this Token was issued to your punch-in system, not stolen from another app
        if data.get("client_id") != LINE_LOGIN_CHANNEL_ID:
            raise HTTPException(status_code=403, detail="Guard: This key belongs to another building!")
            
        # Step 3: Exchange this valid key for the employee's "real ID (Line ID)"
        profile_res = await client.get(
            "https://api.line.me/v2/profile", 
            headers={"Authorization": f"Bearer {token}"}
        )
        
        profile_data = profile_res.json()
        
        # Return the officially verified, unforgeable Line ID to the punch-in logic
        return profile_data["userId"]

# Protect your punch-in vault
@app.post("/api/punch")
async def employee_punch(line_user_id: str = Depends(verify_liff_token)):
    # ๐Ÿ’ก If execution reaches here, this is 100% a Line-verified real account!
    # line_user_id cannot be forged! You can safely use it for database operations.
    
    # ... Implement your Supabase database logic here ...
    
    return {
        "status": "success", 
        "message": f"User {line_user_id} punched in successfully."
    }

๐Ÿ“ 3. [Advanced Business] Implementing Dual Authentication with GPS (Geofencing)

With the above Token validation, we've blocked 99% of hacker script attacks. But in the business world, we face another type of "clever employee": Not a hacker, but someone who lies in bed at 8:50 AM, clicking "Punch In" via Line's menu. Their Token is valid, so your backend lets them through. How to prevent this?

This is where we combine what we learned in Phase 1 about the Geolocation API to implement Geofencing!

  1. Force the frontend (React LIFF) to obtain and include the device's latitude and longitude in the request JSON.
  2. In backend logic, compare these GPS coordinates with "company coordinates".
  3. Use the Haversine formula to calculate distance. If >100 meters, throw error: "Punch denied: You're 5.2 km from office. Please try again when onsite."

This is how we combine Line's verification ecosystem with GPS to create an "absolute fraud-proof enterprise SaaS system". Just the "no proxy punching, no remote punching" features alone make this system worth at least 100,000 NTD in the freelance market! Next chapter, we'll unlock the backend engineer's final superpower: Automated Scheduling (Cron Jobs).

Unlock Full Tutorial

This chapter is paid content. Join the project to unlock over 5000 words of deep analysis, including 10+ god-tier Prompts and real Source Code examples!