A classroom attendance system that makes proxy attendance hard to pull off. Instead of trusting a roll call, a student can only mark themselves present if they satisfy three independent checks at the same time: a short-lived session key, being physically inside the classroom, and a live webcam snapshot attached to the record.
Stack: React · Node.js/Express · MongoDB (Mongoose) · JWT · Cloudinary
Manual roll call is slow and trivially gamed — a friend answers for you. Most digital replacements only move the same trust problem online: if attendance is a button, someone else can press it from the canteen.
A faculty member opens a session. The server generates a 6-digit session key and stores the classroom's GPS coordinates plus an allowed radius in metres. The session auto-expires (3 hours by default) so a leaked key has a short life.
When a student marks attendance, the backend enforces, in order:
- Session key — must match an active, unexpired session.
- Geofence — the student's reported coordinates are checked against the session location using
the Haversine formula. Outside the radius, the request is rejected with the exact overshoot:
"You are out of range by 47 metres." - Snapshot — a webcam photo is streamed to Cloudinary and stored on the attendance record alongside the captured coordinates, giving faculty a reviewable audit trail for any disputed entry.
Only if all three pass is an Attendance document written.
- Three independent factors. Defeating one isn't enough — you need the key and to be in the room and to appear in the photo. Each factor alone is weak; combined they raise the cost of proxy attendance well above the effort of just attending.
- Snapshots are an audit trail, not a biometric check. The system stores a photo for human review rather than running face matching. That is a deliberate scope choice: no biometric templates are computed or stored, which keeps the privacy surface small.
- Uploads never touch disk. Multer uses
memoryStorageand the buffer is streamed straight to Cloudinary, so there are no temp files to leak or clean up. - Session data is snapshotted.
subjectandclassNameare copied onto the session at creation, so renaming a class later doesn't rewrite historical attendance records. - Role-based access. Separate admin / faculty / student routes behind JWT middleware, with bcrypt-hashed credentials.
Frontend (React) Backend (Express) Storage
student dashboard ──POST──▶ studentController ──▶ MongoDB
webcam capture ├─ verify session key ├─ User
geolocation API ├─ Haversine geofence ├─ Classroom
└─ stream photo ────────▶ ├─ Session
└─ Attendance
Cloudinary ◀── snapshot
Models: User (admin/faculty/student) · Classroom · Session (key, lat/lng, radius, expiry) ·
Attendance (session, student, snapshotUrl, locationCaptured)
# Backend
cd Backend && npm install
# .env: MONGO_URI, JWT_SECRET, CLOUDINARY_URL, EMAIL_USER, EMAIL_PASS
npm start
# Frontend
cd Frontend && npm install && npm start- GPS accuracy is the weak link. Consumer GPS drifts 5–20 m indoors, so the radius must be set generously — which means a student just outside the room can still fall inside the fence. A BLE beacon or the classroom Wi-Fi BSSID would be a far tighter proximity signal.
- Snapshots are reviewed manually. Automatic face verification against an enrolled photo would close the loop, but it introduces biometric storage and consent obligations that I deliberately avoided at this scope.
- Coordinates are client-reported. A rooted device can spoof them; server-side attestation or a beacon check would be needed before this could be trusted in a high-stakes setting.