A hardened, zero-dependency PHP login system.
A production-ready, fully self-hosted starter kit for building online service platforms in plain, readable PHP. Zero external dependencies. Minimal attack surface. Maximal features & scalability.
Everything SaaS needs, built-in.
No add-ons to install, no services to wire up. It all ships in the repo.
- Login & registration
- Email verification & activation
- First-party image CAPTCHA
- Inactivity auto-logout
- Email password reset
- Selector / validator tokens
- Remember-me
- Rotating, hardened cookies
- Editable user profiles
- OAuth2 single sign-on
- Discord, GitHub, Google, Microsoft
- Add a provider in one array entry
- REST authentication API
- Bearer tokens with refresh rotation
- GDPR compliance tools
- Account data export
- Account soft-delete
- Permission system
- Multi-level access gating
- User administration dashboard
- Developer console
- Maintenance mode
- Feature & security toggles
- Global settings
- Scheduled task runner
- Automatic database backups
- DNS integrity monitor
- Global error & status messaging
- First-party analytics suite
- Analytics dashboard
- Client fingerprinting
- Session replay & heatmaps
- Custom link tracking
- Create unique links for unique uses / posts
- Tracks per-day & cumulative link analytics
- Custom link codes
- Site-wide defenses
- Parameterized SQL everywhere
- CSRF tokens on every mutation
- Header & email-injection guards
- Brute-force & enumeration defense
- Argon2id password hashing
- Requirements
- PHP 8.0+
- MySQL or MariaDB
- Apache-compatible host
- Shared-hosting friendly
- Nothing to install
- No Composer, no npm
- No CDN — origin-served assets
- One pinned library (PHPMailer)
The threat ledger.
Attack classes this codebase defends against, the countermeasure for each, and the file where it lives.
| # | Attack class | Defense as implemented | Where |
|---|---|---|---|
| 01 | SQL injection | Prepared statements on every query that carries user input; no string-built SQL, no blocklist filters. | */includes/*.inc.php |
| 02 | Cross-site scripting | Output encoding via e() at render time; admin panel renders exclusively through DOM APIs. | security_functions.php, dashboard.js |
| 03 | Cross-site request forgery | Per-session synchronizer tokens on every state change, verified with hash_equals, backed by SameSite cookies. | security_functions.php |
| 04 | Credential brute force | Per-account throttling inside a rolling window, plus a CSPRNG-answered CAPTCHA gating registration. | login.inc.php, SecureCaptcha.php |
| 05 | User enumeration | A dummy hash is verified when the account is unknown, keeping response timing and messages uniform. | login.inc.php |
| 06 | Session fixation & hijack | Strict session mode, id regeneration on privilege change, HttpOnly / Secure / SameSite cookie flags. | secure_session_start() |
| 07 | Remember-me token theft | Selector/validator pairs hashed with SHA-256, rotated on each use; reuse of a stale validator revokes the whole family. | auth_functions.php |
| 08 | Offline password cracking | Argon2id hashing with a bcrypt fallback and a minimum-length policy; oversized inputs are rejected before hashing. | hash_password() |
| 09 | Reset / verification token forgery | 256-bit random tokens stored as SHA-256 digests, single-use, one-hour expiry, constant-time comparison. | sendtoken.inc.php, verify.inc.php |
| 10 | Privilege escalation & IDOR | Access levels enforced inside the SQL of every admin action; no request may grant a level above its caller's. | dashboard/includes/api.inc.php |
| 11 | Malicious file upload | Real MIME detection via finfo against an allow-list, random hex filenames, and a storage directory that refuses to execute scripts. | profile-edit.inc.php, uploads/.htaccess |
| 12 | OAuth login CSRF | Cryptographically random single-use state bound to provider and intent, checked in constant time; PKCE where supported; unverified provider emails refused. | login/index.php, oauth.php |
| 13 | OAuth account pre-hijacking | Identities matched on provider user id, never on email address, so a matching email cannot inherit an existing account. Server-side SSRF allow-list on every provider call. | oauth.php |
| 14 | Verification-token session confusion | Redeeming a token elevates the session only when the token's email matches the signed-in user, compared in constant time. | verify.inc.php |
| 15 | Supply-chain compromise | No registry installs and no CDN loads exist to poison; the one vendored library updates only by reviewed commit; CSP forbids off-origin script. | repository root |
| 16 | Clickjacking & MIME confusion | Frame-ancestors restricted to same origin, nosniff on every response, content types declared explicitly. | send_security_headers() |
| 17 | Information disclosure | Generic error pages, display_errors off, secrets stored above the document root, include-only paths answered with 404. | error.php, .htaccess, phprc |
Before you commit
Questions worth asking.
Whether you're weighing this against a framework, a CMS, or a hosted identity service, here's what usually decides it.
Why run this instead of a framework's built-in auth?
Framework auth — Django's auth, a Laravel starter kit, Passport in Node — assumes you've already adopted the framework, its ORM, its middleware, and its dependency tree. This is the account layer without that commitment: it drops onto any PHP host, owns only the tables it needs, and leaves the rest of your stack alone. You read the whole thing in an afternoon instead of tracing behavior through a vendor directory you'll never fully audit.
We're on WordPress for the logins. Why move off it?
WordPress carries an entire CMS, theme layer, and plugin ecosystem to give you user accounts, and each plugin is a maintainer you're trusting and a patch cadence you're chasing. If accounts are all you actually need, this replaces that surface with a fixed, readable codebase that has no plugin auto-updates, no unknown third-party code, and no admin surface you didn't write. Fewer moving parts is fewer things that break at 3am.
What does "zero dependencies" actually buy me?
Package registries are the road most modern supply-chain attacks travel: typosquatting, dependency confusion, hijacked maintainer accounts, malicious post-install scripts. There is no composer install or npm install here, so that road doesn't reach you. The entire runtime bill of materials is four items, and the one vendored library (PHPMailer) changes only by a commit you can review.
Is a single-file-config, no-build project actually production-ready?
Being small is not the same as being unfinished. It was hardened across many review passes — Argon2id hashing, parameterized queries everywhere, CSRF tokens, output encoding, session and enumeration defenses, throttling, hardened headers, locked-down deployment paths — and the threat ledger above maps each defense to the file that implements it. Run your own review and a staging pass before launch, as you would with anything that guards accounts.
Can I build my own product on top of it?
That's the intent — it's a starter kit, not a finished app. You get registration, sessions, roles, an admin dashboard, a REST auth API, and first-party analytics as a foundation, then build your product's own pages beside them. It works equally well as the front door to a SaaS, a membership site, an internal tool, or the account layer next to an API or AI backend.
How are passwords and tokens stored?
Passwords are hashed with Argon2id, falling back to bcrypt where unavailable. Reset, verification, remember-me, and API tokens are 256-bit random values stored as SHA-256 digests and compared in constant time, with single-use expiry and rotation. Plaintext secrets never touch the database, and a leaked table yields nothing replayable.
Does it support social sign-in and an API?
Yes to both. OAuth2 sign-in ships for Discord, GitHub, Google, and Microsoft, each with a single-use cryptographic state parameter, PKCE where the provider supports it, and a verified-email requirement; adding another provider is one registry entry. A stateless bearer-token REST API with refresh-token rotation is included for programmatic and mobile clients.
What are the server requirements?
PHP 7.4 or newer, MySQL or MariaDB, and an Apache-compatible host for the included .htaccess rules. Shared hosting works — the whole thing was designed for environments without server-level access, so there's no daemon to run and no build step to trigger on deploy.
Read it. Audit it. Build on it.
The whole thing is public. Star it to follow along, or fork it and make it yours.