Blocking 99.3% of spoofing attacks with nothing but a webcam
No infrared, no depth sensor, no GPU. What a multi-signal liveness pipeline actually catches, what it misses, and the cheapest check that beat all the expensive ones.
Every face-recognition deployment eventually meets the same question: how do you know there is a person in front of the camera rather than a photograph of one?
The expensive answer is hardware — infrared, structured light, time-of-flight depth. It works well and it means every device that touches your system needs a specific sensor. For a university attendance system, where the client device is whatever laptop a student already owns, that is not a deployment, it is a procurement project.
So I wanted to know how far you get with an ordinary webcam. The answer turned out to be: much further than I expected, with one clearly identified hole I could not close.
The attack surface is wider than "a photo"
The naive threat model is a printed photograph. The real one is a spectrum, and each point on it defeats a different defence.
A printed photo is flat and static. A phone screen is flat but emits light and has a pixel grid. A tablet screen is the same with more resolution and less obvious artefacting. A recorded video replayed on a screen adds natural motion, blinking, micro-expressions — everything a naive "is it moving?" check looks for. A virtual camera streams that video straight into the browser's video pipeline, so there is no screen to detect at all. And a paper cut-out mask with the eyes removed, held to a real face, is genuinely three-dimensional and moves with a real head.
Any single detector handles some of these and is defeated by others. That observation is the whole design.
One signal is never enough
A CNN anti-spoof model trained largely on print attacks does well on paper and struggles with a high-quality phone screen. FFT analysis catches the moiré and pixel structure of a display and says nothing whatsoever about a matte print. Motion analysis is defeated by moving the photograph. Blink detection is defeated by a video.
The pipeline I settled on runs six checks per frame and fuses them: facial landmarks for geometry and pose, a compact anti-spoof CNN for texture, FFT analysis for screen artefacts, a depth-from-motion estimate for parallax, camera-authenticity heuristics for virtual devices, and an active challenge on top.
Each is individually beatable. The combination requires an attacker to defeat several simultaneously with a single artefact, and the artefacts that beat one tend to fail another. A screen beats the depth check and lights up the FFT detector. A print beats the FFT detector and fails on depth.
The cheapest check was the most effective
The single best-performing signal was not a model at all.
At the start of a session the system asks the user to turn their head *or* nod — chosen at random, communicated only at that moment. Then it verifies that the motion actually performed matches the motion requested.
A recording cannot respond to a question it did not know was coming. A photograph cannot respond at all. To beat this you need real-time synthesis driven by the challenge, which is a vastly higher bar than printing a picture.
A replay attack is a recording of the past. Any question you ask that the past could not anticipate will beat it.
It blocked 99.6% of attempts in testing, and it costs a few milliseconds of pose estimation. Better still, it fails fast: if the wrong action is detected the session can be rejected within a single frame, without waiting for the full challenge window.
The numbers
Across 350 presentation attacks in seven scenarios: wrong-action 99.6% blocked, printed photograph 99.3%, tablet replay 97.1%, phone replay 96.7%, virtual camera via OBS 94.4%, and paper cut-out mask 92.5%. Aggregate around 96%.
Face verification itself sat at a 1.8% equal-error rate over 300 trials at a calibrated cosine threshold, with the true acceptance rate at 98.0% and false acceptance at 1.3%. The errors were instructive rather than random: false rejections clustered on low ambient light and one volunteer who had recently started wearing glasses, false acceptances on visually similar pairs.
Per-frame liveness ran in about 32.5 milliseconds on a laptop with no discrete GPU, against a budget of 350 milliseconds per frame. The recognition pass, which runs once per session rather than per frame, added about 92 milliseconds.
The one that still gets through
The paper mask was blocked only 92.5% of the time — the weakest result in the study, and the one I could not engineer away.
The reason is structural rather than a tuning failure. A mask held up to a real face moves with a real head. It therefore produces genuine three-dimensional parallax. The depth-from-motion check — the signal that reliably defeats flat photographs — is exactly the one a held mask satisfies.
I do not have a webcam-only answer to that. Closing it properly means depth hardware, or native mobile attestation where the operating system will vouch for the camera. Saying so plainly seemed more useful than rounding the number up and hoping nobody tested it.
Binding it to a place
Verifying a person is only half of attendance. The other half is verifying they are in the room, and that turned out to be the harder security problem.
GPS from a browser is a claim, not evidence — trivially overridden at the OS level. So the location is not trusted on its own; it is bound cryptographically. The verifier issues a token that fuses the student identifier, the coordinates, a nonce and an expiry under HMAC-SHA256, validated independently on both sides of the trust boundary.
That does not make a forged location impossible. It makes it non-transferable and non-replayable: a token cannot be edited after issue, cannot be reused, and cannot be moved to another student. In testing, tamper, replay and post-hoc GPS substitution were rejected in every trial.
What I would tell someone starting this
Fuse signals that fail differently — that is the entire trick, and it beats making any individual detector better. Prefer active challenges to passive detection where the interaction budget allows; they are cheap and they attack replay at the root. Report per-attack-vector numbers rather than an aggregate, because the aggregate hides the one an attacker will actually use. And be explicit about the residual risk, because the alternative is someone discovering it for you.