第九章:バックエンドセキュリティ - FastAPIでLIFFトークンを検証し偽造を防ぐ方法

前回のフロントエンド講座では、Reactでliff.getAccessToken()を取得し、打刻APIリクエストのHeadersに付与してバックエンドに送信する方法を教えました。 しかし、FastAPIを開発するバックエンドエンジニアとして、このトークンがハッカーによって適当に作られた(例えばPostmanで生成された)無意味な文字列ではないことをどう確認すればよいでしょうか?

Web開発の世界には有名な格言があります:「フロントエンドから送られてくるデータを決して信用してはいけない」 これを検証しない場合、これは打刻システム全体で最も致命的で攻撃されやすい弱点になります!今日はFastAPIに強固な防壁を築き、この脆弱性を完全に塞ぎます。


🛡️ 1. なぜトークン検証が必須なのか?(痛いビジネス上の代償)

打刻API (POST /api/punch) がトークンを検証しない場合、ハッカーや少しプログラミング知識のある従業員は、APIのURLさえ知っていれば、自分でスクリプトを書いて偽のJSON { "action": "check-in", "user_id": "U123456..." } をサーバーに送信できます。

結果として:彼はバリ島のビーチで毎日くつろぎながら、スクリプトに朝9時ちょうどに打刻させることが可能になり、システムは全く気付きません。もし上司がこれに気付いたら、間違いなく最終報酬を差し押さえられ、訴訟を起こされるでしょう。

したがって、バックエンドがTokenという鍵を受け取った時、私たちが行わなければならない最も重要なことは: この鍵を持って、Lineの公式検証センターに問い合わせること:「この鍵は本当に公式に発行されたものですか?どの実際のLine IDに発行されましたか?有効期限は切れていませんか?」


🧱 2. FastAPIで強力なトークン検証メカニズムを実装

FastAPIの最も強力な機能である**Dependency Injection(依存性注入)**を利用して、「門番」のようなインターセプターを作成します。打刻したい人は誰でも、まずこの門番のチェックを通らなければなりません。

🔥【Vibe Prompt 実戦呪文】 FastAPIでLine打刻バックエンドを開発中です。フロントエンドはHeader: 'Authorization: Bearer <LIFF_TOKEN>'を通じて認証情報を送信します。 Dependency(依存関数)verify_liff_tokenを書いてください: 1. HeaderからTokenを抽出し、なければ401をスロー 2. httpx(非同期)を使用してLineの検証API (https://api.line.me/oauth2/v2.1/verify)を呼び出し 3. responseのclient_idがプロジェクトのLINE_LOGIN_CHANNEL_IDと一致することを確認(クロスサイト攻撃防止) 4. 検証成功後、Line Profile API (https://api.line.me/v2/profile)を呼び出して実際のuserIdを取得しreturn 5. このDependencyをテスト用のPOST /api/punchルートに注入

AIが生成する高品質なコードは以下のようになります:

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

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

# 警備室:全てのTokenを検証
async def verify_liff_token(authorization: str = Header(None)):
    if not authorization or not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="警備:身分証明書を持っていません!")
    
    token = authorization.split(" ")[1]
    
    # ステップ1:Line公式サーバーに鍵の真偽を確認
    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="警備:この鍵は偽物または期限切れです!")
            
        data = verify_res.json()
        
        # ステップ2:クロスサイト攻撃防止(非常に重要!)
        # このTokenが打刻システム用に発行されたものであり、ハッカーが他のゲームAPPから盗んだものではないことを確認
        if data.get("client_id") != LINE_LOGIN_CHANNEL_ID:
            raise HTTPException(status_code=403, detail="警備:この鍵は別のビルのものです!")
            
        # ステップ3:本物の鍵を使って従業員の「実身份证(Line ID)」を取得
        profile_res = await client.get(
            "https://api.line.me/v2/profile", 
            headers={"Authorization": f"Bearer {token}"}
        )
        
        profile_data = profile_res.json()
        
        # 公式認証済みで偽造不可能なLine IDを後続の打刻プログラムに渡す
        return profile_data["userId"]

# 打刻の金庫を保護
@app.post("/api/punch")
async def employee_punch(line_user_id: str = Depends(verify_liff_token)):
    # 💡 ここまでコードが実行されるということは、この人が100% Line公式認証済みの実際のアカウントであることを意味します!
    # line_user_idは絶対に偽造できません!安心してデータベースに打刻できます。
    
    # ... Supabaseデータベースへの書き込みロジックをここに実装 ...
    
    return {
        "status": "success", 
        "message": f"User {line_user_id} punched in successfully."
    }

📍 3. [ビジネス上級] GPSを組み合わせた二重偽造防止(Geofencing)

上記のトークン検証保護があれば、99%のハッカースクリプト攻撃を防ぐことができます。 しかし、ビジネスの現場ではもう一つの「賢い従業員」が現れます: 彼はハッカーではありませんが、毎朝8:50に家のベッドで横になりながら、Lineのグラフィカルメニューから「出勤打刻」をクリックします。彼のトークンは本物なので、バックエンドは通過を許可してしまいます。これをどう防ぐか?

この場合���第一段階で学んだGeolocation APIを組み合わせて**「電子フェンス(Geofencing)」**を実装する必要があります!

  1. フロントエンド(React LIFF)でリクエストを送信する際、強制的に携帯電話のlatitudelongitudeを取得し、この2つの数字をJSONに同梱してバックエンドに送信
  2. バックエンドの打刻ロジックで、まずこのGPS座標と「会社の経緯度座標」を比較
  3. 数学のHaversine公式を使用して直線距離を計算。計算結果が100メートルを超える場合、バックエンドは無情にエラーをスロー:「打刻拒否:現在会社から5.2km離れています。オフィス到着後再度お試しください」

これが、Line認証エコシステムとGPS位置情報を組み合わせて作成した「完全不正防止企業向けSaaSシステム」です。 「代理打刻防止、リモート打刻防止」という2つの売りだけで、このシステムの外注価格は10万円台湾ドルからになります! 次の章では、バックエンドエンジニアの最後の超能力:自動化スケジューリング(Cron Jobs)を解説します。

完全なチュートリアルをロック解除

このチャプターは有料コンテンツです。プロジェクトに参加して、10以上の神レベルのPromptや実際のソースコード例を含む、5000字以上の深い分析をロック解除してください!