← Selected work

Live · auth, hardening, and a test suite

Prime Accounts Inc authentication & hardening

Full-stack developer

Most real engineering happens on top of something that already runs. This was a live FrontAccounting/PHP site: table-based markup, vendored webmail and calendar apps, no bundler, and no chance of a rewrite. Sign-in did not work at all. The submission guard had been copy-pasted into two dozen handlers with drifting variable names, so no two behaved the same and none of them was tested. I replaced the authentication system, centralised the guard, and wrote the suite that proves both still work.

  • PHP
  • PDO
  • Security hardening
  • Session management
  • Regression testing
  • Microsoft Graph API

primestandard.ca

Auth
PDO · split-token links
Guard
5 gates · 24 handlers
Tests
8 suites · HTTP-driven
IFTA refactor
Two copies · one library

Authentication / split-token links

The database never holds a secret that would work.

An invite or reset link carries a 128-bit selector and a 256-bit verifier. The selector finds the row; only a SHA-256 of the verifier is stored, and it is compared with hash_equals so the check cannot be walked one character at a time.

Links are single-purpose, single-use, and expire. A reset bumps the account's session epoch, which ends every other session it had open.

A leaked tokens table contains nothing an attacker can present.

The problem

A production legacy system with real users depending on its routes, forms, and email flows. Login was broken outright, the same honeypot-to-CSRF gauntlet existed in twenty-four slightly different copies, and identifiers as sensitive as social insurance numbers were moving through it. I could not add a bundler, drop in a framework, or rewrite the data layer. Everything had to be fixed in place, without changing a single field name or POST target.

What I owned

The authentication system end to end: PDO-backed accounts, split-token invite and reset links, two-axis throttling, session epochs, and Luhn-checked identifiers stored masked. One shared library for the five-gate submission guard, replacing the copy-pasted version in every handler. An invoice generator with a server-authoritative tax engine and a from-scratch PDF writer. And an eight-suite regression harness that drives the real pages over HTTP against a throwaway database.

The hard parts

  1. Authenticating people on a site with no working login, and no framework to lean on.


    Split-token links carry a 128-bit selector to find the row and a 256-bit verifier compared with hash_equals, so the database never stores a secret that would work if it leaked. Throttled at eight failures per address and thirty per IP in a fifteen-minute window.

  2. Holding identifiers nobody wants to be holding.


    Social insurance numbers are Luhn-checked before they are accepted and masked everywhere they are shown. The identifier's prefix distinguishes a person from a corporation, so the filing pages never ask a company for its marital status.

  3. Proving any of it, on a stack with no test culture at all.


    Eight suites on stock PHP with no dependencies. The auth suite drives the real pages over HTTP against a throwaway SQLite database, and a preloaded mail stub means nothing can reach Microsoft Graph.

The result

Twenty-four handlers now refuse an unauthenticated POST and send nothing, each gate still bites on its own, and a wrong password is indistinguishable from an unknown address. All of it is guarded by a suite that runs in ninety seconds on stock PHP, and every check in it exists because something actually broke.