Skip to main content
All case studies
feature 2025 · Solo

Selfie attendance with geofencing and server-side time enforcement

Built a biometric attendance flow with geofenced locations, multi-layer timezone resolution, and server-side timestamp enforcement to prevent device-time fraud.

Java Spring Boot MySQL React

Impact

Device-time fraud prevention

Context

Institutions using Digiicampus needed a modern replacement for paper-based or biometric-terminal attendance. The product team wanted a mobile-first flow: open the app, take a selfie at your assigned location, done. Simple for users, which meant all the hard problems fell to the backend.

Problem

“Simple for users” is a tall order when the backend has to trust a device it can’t trust.

Three adversarial patterns were in scope:

  1. Location spoofing. A user checks in from home instead of the office.
  2. Time spoofing. A user manipulates device clock or the request body to back-date an attendance record.
  3. Identity spoofing. A user’s friend checks in for them.

The system also had to handle the non-adversarial edge cases that always show up in real deployments:

  • Users in different timezones from the tenant’s home region.
  • Tenants with multiple geofenced locations (e.g., a university with several campuses).
  • Users whose device GPS is flaky indoors.

Approach

For location: server-side geofence validation. The client sends current coordinates; the server checks them against the assigned location’s polygon. The client is not trusted to say “I’m at the right place” — only to submit coordinates the server then validates.

For time: the server stamps the record with its own clock, not the client’s. The client sends a request; the server’s ingress timestamp is the authoritative attendance time. The client’s timestamp is recorded separately as metadata for audit, never used for business logic.

For timezone resolution: a layered fallback. Use the geofence’s timezone if the check-in is inside one. Otherwise, fall back to the tenant’s home timezone. Otherwise, use the request’s inferred timezone from its IP. This matters for “was this check-in within the valid window for today’s class?” — the answer depends on which day you’re in.

For identity: a selfie captured at check-in time, stored with the record. Automated comparison against the user’s profile photo is a future enhancement; for now, the selfie is a deterrent and an audit artifact. The presence of the photo alone changes behavior.

Implementation

The check-in request carries: geofence ID (which location), client coordinates, client timestamp, selfie image. The server:

  1. Validates the geofence ID exists and the user is assigned to it.
  2. Validates the client coordinates are inside the geofence polygon.
  3. Resolves the correct timezone using the layered fallback.
  4. Stamps the attendance record with its own ingress timestamp, localized via the resolved timezone.
  5. Stores the selfie in S3 with a record-scoped key.
  6. Writes the attendance row with both server and client timestamps.

The image upload is the slow step. I kept the database write synchronous and the S3 upload asynchronous — the user sees success as soon as the row is committed; the image is backfilled by a worker. If the upload fails, the record is flagged for review, not lost.

Impact

  • Device clock manipulation is no longer a valid attack vector — the server’s clock is authoritative.
  • Location spoofing requires defeating OS-level GPS, which is a much higher bar than editing a request body.
  • Every record has a selfie attached, which has proven to be a strong social deterrent against proxy check-ins even without automated face matching.
  • Timezone handling works correctly across campuses and for users traveling outside their tenant’s region.

What I’d do differently: I’d add automated face-matching against the profile photo from day one, gated behind a feature flag per tenant. The hooks are there, but the comparison itself is still manual review.