For developers

Self-host it in an afternoon.

PHP-based, no database server, no credentials file. Everything you need to evaluate, run, and harden it.

Requirements

  • PHP 8.1+ with the pdo_sqlite extension (bundled with most PHP builds).
  • Any web server that runs PHP — Apache, Nginx, or PHP's built-in server for local testing.
  • No MySQL, no Composer, no build step required for the demo.

Quick start

terminal
# 1. get the code
git clone https://github.com/MohammedNasrallah/passnumber-demo.git
cd passnumber-demo

# 2. run it (creates the SQLite file on first request)
php -S 127.0.0.1:8000 -t public

# 3. open the demo
# http://127.0.0.1:8000/index.php  — register
# http://127.0.0.1:8000/login.php  — log in

On shared hosting, upload the project and point the document root at the public/ folder (or move its contents into public_html). The data/ folder must be writable by PHP so the SQLite file can be created.

Data model

A single table. Note what is not here: no plaintext passnumber, no symbol choices, no positions.

schema.sql
CREATE TABLE users (
  id                INTEGER PRIMARY KEY,
  username          TEXT NOT NULL UNIQUE,
  hashed_passnumber TEXT NOT NULL,  -- salted bcrypt
  grid_rows         INTEGER,
  grid_cols         INTEGER,
  neglect_count     INTEGER,
  failed_attempts   INTEGER DEFAULT 0,
  locked_until      INTEGER DEFAULT 0
);

Hashing

Registration derives one canonical token from the user's choices (each row encoded independently so the secret is collision-free), then stores only its salted hash:

store.php
$token = PassNumber::canonicalToken($positions);
$hash  = password_hash($token, PASSWORD_DEFAULT);

// login verifies in constant time
if (password_verify($rebuiltToken, $hash)) { /* ok */ }

Security checklist

Before you put this in front of real users:

  • ☐ Serve everything over HTTPS; redirect HTTP.
  • ☐ Add security headers — CSP, HSTS, X-Frame-Options, X-Content-Type-Options.
  • ☐ Add IP-level rate limiting in front of the app, not just per-account lockout.
  • ☐ Build an account-recovery flow.
  • ☐ Turn on audit logging and monitoring of failed attempts.
  • ☐ Use a large enough grid for your risk level.
  • ☐ Get a professional security review.

Limits & compatibility

The demo is plain PHP — there is no Laravel dependency. If your marketing or internal docs mention a framework, reconcile that with what actually ships. The demo targets PHP 8.1+ and is not tested against PHP 7. SQLite is used for zero-config portability; swapping to MySQL/Postgres is a straightforward change to the data layer but is left to the implementer.

Reminder. This is a reference implementation of a method. Treat it as a starting point you harden, not a finished product you deploy as-is.