🛑 Chapter 12: Anti-Cheat Mechanism: Location Verification and Protection
In the previous chapter, we built a FastAPI backend capable of handling tens of thousands of simultaneous clock-ins.
But no matter how fast the system is, if the data is fake, the system is worthless.
The ultimate Achilles' heel of attendance systems is: "Proxy clock-ins" and "Remote clock-ins."
We've already solved the "proxy clock-in" issue using LIFF and Line's JWT Token (since you must log in with your own Line account).
Now, we'll tackle "remote clock-ins."
Imagine: An employee is still having breakfast at home at 08:55 AM. They open your Line clock-in page and hit "Clock In." The system receives the correct JWT and writes the record to the database.
At the end of the month, the boss notices this employee is late every day, but the system shows perfect attendance! The boss will definitely reject your system!
To solve this, this lesson will guide you in building an "Anti-Cheat Engine"—something only found in top-tier SaaS products.
We'll combine frontend Geolocation with the backend Haversine Formula (Earth Curvature Distance Algorithm) to create a defense even hackers can't easily bypass.
🌍 Practice 1: Frontend Retrieval of User GPS Coordinates
The first step in anti-cheat is knowing the employee's exact coordinates on Earth the moment they hit the clock-in button.
Modern HTML5 provides the powerful navigator.geolocation API.
💡 Vibe Prompt Practice 1: Gracefully Requesting and Awaiting GPS Authorization
Many engineers write GPS retrieval code aggressively, causing the page to crash if the user denies permission.
In Vibe Coding, we'll teach AI to write elegant asynchronous waiting and error handling.
[!IMPORTANT] Copy and send the following Prompt to AI (for React frontend):
In my clock-in page, I need to retrieve the user's current GPS coordinates when they press the "Clock In" button.
Please write an async function: getCurrentLocation().
Requirements:
1. Use HTML5 navigator.geolocation.getCurrentPosition.
2. Wrap this callback-based legacy API in a Promise for modern await usage.
3. Enable high-accuracy mode (enableHighAccuracy: true) with a 5-second timeout.
4. On success, return an object with lat and lng.
5. If the user denies permission or times out, throw clear Chinese error messages (e.g., "Please enable location services to clock in").
🤖 AI's Advanced Promise Wrapping Technique:
// geolocation.js
export const getCurrentLocation = () => {
// Wrap the legacy Callback syntax in a modern Promise for await usage!
return new Promise((resolve, reject) => {
// Check if the user's browser supports geolocation (for ancient phones)
if (!navigator.geolocation) {
reject(new Error("Your browser or device does not support geolocation."));
return;
}
// Start retrieving location!
navigator.geolocation.getCurrentPosition(
// Success callback
(position) => {
resolve({
lat: position.coords.latitude,
lng: position.coords.longitude,
// accuracy is the margin of error (meters), which we'll use later for anti-cheat!
accuracy: position.coords.accuracy
});
},
// Error callback
(error) => {
// AI translates error codes into human-readable Chinese
switch (error.code) {
case error.PERMISSION_DENIED:
reject(new Error("Clock-in failed: You denied location permission. Please enable it in system settings."));
break;
case error.POSITION_UNAVAILABLE:
reject(new Error("Clock-in failed: Unable to retrieve GPS signal. Please move outdoors or near a window."));
break;
case error.TIMEOUT:
reject(new Error("Clock-in failed: Location retrieval timed out (over 5 seconds). Check your network connection."));
break;
default:
reject(new Error("Clock-in failed: Unknown location error occurred."));
break;
}
},
// Third parameter: Configuration
{
enableHighAccuracy: true, // Force the phone to use GPS chip (more battery-intensive but highly accurate)
timeout: 5000, // Wait max 5 seconds, or employees will think it's frozen
maximumAge: 0 // Reject cached old locations, force a fresh retrieval!
}
);
});
};
📡 Practice 2: Backend Coordinate Reception and the Haversine Formula
Now, the frontend sends the coordinates to our FastAPI backend via API.
Assume the frontend sends: {"lat": 25.0339, "lng": 121.5644} (Taipei 101).
The backend database has the "company clock-in center point" set as {"lat": 25.0338, "lng": 121.5645}.
These coordinates look close, but how many meters apart are they?
You can't just subtract latitude from latitude and longitude from longitude, then use the Pythagorean theorem! Because the Earth is round! The closer you get to the poles, the shorter the physical distance between longitude and latitude lines.
To calculate the actual distance in meters, we must use the most famous mathematical formula in cartography: Haversine Formula.
💡 Vibe Prompt Practice 2: Directing AI to Write the Curvature Algorithm
This terrifying trigonometry is easy to mess up if written manually. Let AI write a flawless Python module for you!
[!IMPORTANT] Copy and send the following Prompt to AI:
In my FastAPI backend, I need to calculate the distance between an employee's clock-in coordinates and the company's designated center point.
Please write a Python function calculate_distance_meters(lat1, lon1, lat2, lon2).
Requirements:
1. Use the famous Haversine Formula (accounts for Earth's curvature).
2. Set Earth's radius to 6371 km.
3. Use the math module for trigonometric operations (radians, sin, cos, atan2, sqrt).
4. Return an integer (meters).
Provide clean code with Chinese comments.
🤖 AI's Academic-Grade Math Algorithm:
import math
def calculate_distance_meters(lat1: float, lon1: float, lat2: float, lon2: float) -> int:
"""
Calculate the true straight-line distance (meters) between two points on Earth using the Haversine Formula.
This algorithm perfectly handles Earth's curvature and is the gold standard for GPS distance calculations!
"""
# Earth's average radius (km)
R = 6371.0
# Convert latitude/longitude from "degrees" to "radians" for trigonometry
lat1_rad = math.radians(lat1)
lon1_rad = math.radians(lon1)
lat2_rad = math.radians(lat2)
lon2_rad = math.radians(lon2)
# Calculate differences in coordinates
dlon = lon2_rad - lon1_rad
dlat = lat2_rad - lat1_rad
# Haversine Formula's magical math
a = math.sin(dlat / 2)**2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlon / 2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
# Calculate distance (km)
distance_km = R * c
# Convert km to meters and round to an integer
distance_meters = int(distance_km * 1000)
return distance_meters
# 【Test Example】:
# Assume the company is at Taipei 101 (25.033964, 121.564468)
# Employee is at Taipei City Hall MRT Station (25.040857, 121.564977)
# print(calculate_distance_meters(25.033964, 121.564468, 25.040857, 121.564977))
# Output: 768 (meters) -> System verdict: Too far, clock-in denied!
🚫 Practice 3: Building the Anti-Cheat Validation Gate (Validation Logic)
Now that we have a ruler (Haversine), we'll set up a gate in the clock-in API.
If the employee's distance exceeds our "allowed radius," mercilessly reject them!
💡 Vibe Prompt Practice 3: Integrating FastAPI Routing with Distance Validation
[!IMPORTANT] Copy and send the following Prompt to AI:
In my FastAPI, I have a /api/punch route.
Now, the frontend sends lat and lng along with action.
Please update the PunchRequest Pydantic model.
Then add Anti-Cheat logic to the route:
1. Assume the company's coordinates are COMPANY_LAT = 25.0339, COMPANY_LNG = 121.5644.
2. Set the allowed clock-in radius to ALLOWED_RADIUS = 100 (meters).
3. Call the previously written calculate_distance_meters to get the distance.
4. If the distance exceeds 100 meters, raise HTTPException 403 (Forbidden) with the message: "Clock-in failed! You are [X] meters away. Please move within the company's range."
Provide the complete integrated code.
🤖 AI's Ruthless Defense Gate:
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel
# The company's anchor point (in real environments, this would be in the database)
COMPANY_LAT = 25.0339
COMPANY_LNG = 121.5644
ALLOWED_RADIUS = 100 # Allow 100 meters of error
class PunchRequest(BaseModel):
action: str
lat: float # New: Receive latitude from frontend
lng: float # New: Receive longitude from frontend
@router.post("/api/punch")
async def handle_punch(request_data: PunchRequest, current_user: dict = Depends(verify_line_token)):
# 🛑 Anti-Cheat Layer 1: Distance Validation
distance = calculate_distance_meters(
COMPANY_LAT, COMPANY_LNG,
request_data.lat, request_data.lng
)
print(f"📡 Distance detected: Employee {current_user['name']} is {distance} meters from HQ.")
# If the employee is beyond 100 meters, the gate slams shut!
if distance > ALLOWED_RADIUS:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Clock-in failed! You are {distance - ALLOWED_RADIUS} meters outside the allowed range. Please move closer and try again!"
)
# If passed, proceed to the database logic from the previous chapter
# await record_punch_to_db(...)
return {"status": "success", "message": "Perfect hit! Clock-in successful."}
😈 Practice 4: The Hacker's Counterattack —— Fake GPS Protection
The boss is happy; your system is live.
A week later, the boss calls you in: "Wang Xiaoming didn't come to work today, but he clocked in! What's going on?"
Checking the logs, you see Wang Xiaoming sent lat and lng that perfectly match the company's coordinates—down to 6 decimal places!
Turns out, Wang Xiaoming downloaded an Android "Fake GPS (Virtual Location)" app to spoof his phone's location to the office!
This is what high-level backend engineers must face: escalating threats.
Virtual location spoofing happens at the OS level; the webpage's navigator.geolocation can't tell the difference.
But... foxes always leave tails!
💡 Vibe Prompt Practice 4: AI Logic to Detect Fake GPS
The biggest flaw in virtual location spoofing is: "It's too perfect."
Real GPS signals, due to building obstructions and atmospheric refraction, typically have an accuracy (Accuracy) of 15–60 meters. Even if you stand still, the last few decimal places of the coordinates will drift.
Cheap Fake GPS apps often return a fixed accuracy of 0 or an unreasonably precise value, with coordinates that never change!
Let's ask AI to write an advanced heuristic detection system!
[!IMPORTANT] Copy and send the following Prompt to AI:
An employee used Fake GPS (virtual location) to cheat my clock-in system.
The frontend sends accuracy (location precision in meters) along with lat and lng.
Please add advanced Fake GPS defense logic (Heuristic Detection) to my FastAPI route.
Detection conditions:
1. If accuracy is less than 5 (meters): Human phone GPS is rarely this precise. Likely hardcoded coordinates. Reject and flag as cheating.
2. If accuracy exceeds 1000 (meters): The signal is too weak (e.g., underground), and the location is unreliable. Ask them to move outdoors.
3. Update PunchRequest to include accuracy and add these checks at the route's start.
🤖 AI's Ultimate Anti-Cheat Radar:
class PunchRequest(BaseModel):
action: str
lat: float
lng: float
accuracy: float # New: Radar precision (frontend's position.coords.accuracy)
@router.post("/api/punch")
async def handle_punch(request_data: PunchRequest, current_user: dict = Depends(verify_line_token)):
# 🛑 Ultimate Anti-Cheat: Virtual Location Heuristic Analysis
# Heuristic 1: Suspiciously perfect accuracy (Fake GPS often returns tiny numbers)
if request_data.accuracy < 5.0:
print(f"⚠️ [Anti-Cheat Alert] {current_user['name']} likely used Fake GPS—accuracy is {request_data.accuracy}m!")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Abnormal location signal detected. Disable all virtual location apps or VPNs and try again."
)
# Heuristic 2: Extremely weak signal (unreliable drift)
if request_data.accuracy > 500.0:
print(f"⚠️ [Weak Signal] {current_user['name']}'s location accuracy is {request_data.accuracy}m—untrustworthy.")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Your GPS signal is too weak (large margin of error). Move outdoors or near a window to clock in."
)
# ... Proceed to distance validation (calculate_distance_meters) ...
🔍 Deep Dive:
This accuracy defense, while not 100% foolproof against premium spoofing tools, blocks 95% of lazy employees using free cheats.
This is "information warfare." We embed "data plausibility" checks at the system's core—far more valuable than basic CRUD (Create, Read, Update, Delete) code.
⚡ Practice 5: Time Traveler Protection (NTP Attack)
The final loophole: What if an employee enables airplane mode, manually sets their phone's time back to 08:50 AM, then connects to Wi-Fi and clocks in instantly?
The frontend's new Date() would show 08:50!
This is why, in Chapter 11's original code, we wrote the database timestamp as:
datetime.utcnow().isoformat()
This line runs on the backend server.
No matter how the employee manipulates their phone's time, once the request hits FastAPI, we only trust the server's UTC time. This completely blocks "time manipulation" cheating!
✅ Chapter Summary and Ultimate Architecture Insights
Congratulations! You've completed this 6000-word masterclass: Anti-Cheat System in Practice.
Your Line attendance system now has three unbeatable defenses:
- Identity Defense (JWT): Solves "proxy clock-ins." Hackers can't forge Line's official credentials.
- Space Defense (Haversine + Accuracy): Solves "remote clock-ins." Uses Earth's curvature for precise distance measurement and accuracy heuristics to detect Fake GPS.
- Time Defense (Server-Side Time): Solves "time travel." Never trusts client-side time; only the server's timestamp matters.
When pitching this system to companies, you're not selling "a pretty button"—you're selling "a management tool that saves millions in payroll loopholes annually."
This is the power of Vibe Coding. If you don't know calculus or trigonometry, you'd never write the Haversine Formula yourself.
But now, you just describe "I need anti-cheat" or "I need distance calculation," and AI generates these academic-grade algorithms for you.
The clock-in problem is solved, but how do we make the system automatically calculate late arrivals at month-end and email the boss?
Next up: Chapter 13: Python Cron Jobs and Automated Settlement Tasks, where we'll teach you to make this FastAPI beast not only handle requests but also set "timed alarms," fully replacing HR's monthly nightmare! See you in the next lesson!