Passwordless authentication engineered from first principles β secure magic links, airtight rate limiting, and observable identity flows without frameworks.
Passless is a complete passwordless authentication system built to show what robust, production-ready identity looks like without relying on Laravel, Symfony, or security frameworks.
Itβs intentionally minimal, portable, and auditable. Every component β tokens, rate limits, sessions, CSRF, CAPTCHA, fingerprinting, audit logs β is implemented directly in PHP to make the architecture fully transparent.
Runs anywhere: shared hosting, containers, bare metal, or cloud platforms.
- Magic links hashed + fingerprint-bound (IP + User-Agent)
- Single-use tokens with transactional verification
- Account unlocks and session refresh on successful login
- Multi-dimensional rate limits (email, IP, email+IP)
- Progressive CAPTCHA after thresholds
- Account lockouts & security notifications
- Randomized failure delays to reduce timing analysis
- Append-only audit logs
- Structured security events
- GeoIP change alerts
- Admin console with realtime activity and risk indicators
- Database-backed sessions with:
- Sliding expiration
- Absolute expiration
- Device metadata
- Self-service session revocation
- CSRF protection with rotating tokens
- Unit + integration tests (SQLite replicas of MySQL tables)
- PHPStan (level 7)
- Docker Compose environment
- Automated deployments via GitHub Actions
- PHP 8 (framework-free, PDO)
- MySQL 8 (SQLite for testing)
- Vanilla JS + modern CSS
- SendGrid / Mailgun for transactional mail
- GitHub Actions, Docker Compose, PHPStan
This system is built to thrive in constraints:
- No npm, no build toolchains
- No frameworks
- Only core PHP extensions
- Works on FTP-only shared hosting
- Scales up cleanly to containers and cloud
If an architecture functions under severe constraints, it remains reliable anywhere.
- Vanilla PHP β transparent logic, small attack surface
- MySQL β atomic UPSERTs, universally available
- Magic links β no password storage, low-friction onboarding
- Server-side sessions β simple revocation, CSRF pairing, predictable security
A practical demonstration of:
- Token hashing, fingerprinting, rate limiting, CSRF, audit trails
- Clean database schemas for auth + security + observability
- Fully transparent control flow, ideal for interviews or teaching
- A real deployment used as a reference implementation
Note: For production-scale workloads, SaaS identity providers (Auth0, Clerk, Supabase) may be more appropriate unless compliance, cost, or vendor policy prevents it.
- You need OAuth/social login
- You want dashboards, MFA, analytics, and SLAs out of the box
- Team velocity and convenience matter most
- Air-gapped or compliance-restricted environments
- Need custom audit logs & forensics
- Vendor pricing scales poorly with MAU
- You want to demonstrate security fundamentals
-
Clone the repository:
git clone https://github.com/your-org/passless.git cd passless -
Copy the environment template and edit values:
cp .env.example .env
-
Create a MySQL database (UTF8MB4).
-
Import schema:
mysql -u <user> -p <database> < htdocs/database/install.sql
-
Ensure PHP extensions: pdo_mysql, openssl, mbstring, curl, json, session, pdo_sqlite (tests)
-
Make htdocs/ the document root (or upload only that folder).
-
Optional local stack:
docker compose up --build
Starts PHP 8.2 + MySQL + Mailpit on https://localhost:8443.
Minimum required:
APP_ENV=production
APP_URL=https://your-domain/passless
DB_HOST=
DB_NAME=
DB_USER=
DB_PASS=
MAIL_PROVIDER=
MAIL_FROM=
SENDGRID_API_KEY or MAILGUN_*
All other .env.example variables have safe defaults for security, sessions, rate limits, CAPTCHA, and GeoIP.
Usage
- Request link: Submit email on /. Dev mode shows link in browser Production sends via email
- Verify link: /auth/verify.php enforces hashing, fingerprint, rate limits, lockouts.
- Dashboard: /app.php Active sessions, device metadata, events, revocation.
- Admin console: /admin.php Suspicious IPs, rate-limit pressure, failed attempts, lockouts.
- Automation: php htdocs/deploy/cleanup.php removes expired tokens/sessions/limits.
- **Testing:
php tests/run.php
php phpstan.phar analyse --configuration=phpstan.neon
graph LR
A[User Browser] -->|POST /auth/request.php| B(Request Controller)
B --> C{Security Services}
C -->|Rate limiting| D[(MySQL)]
C -->|Token creation| D
C -->|Audit events| E[(Audit & Security Logs)]
D --> F[Mailer]
F --> A
A -->|GET /auth/verify.php| G(Verification Controller)
G --> C
C -->|Session issue| H[(DB-Backed Sessions)]
H --> A
- Benchmarked on a 2-core Codespaces VM (SQLite harness, bcrypt cost 10):
- Magic link request: ~1.6 ops/s
- Verification: ~1.6 ops/s
- Rate limit UPSERTs: ~25k ops/s (SQLite in-memory)
These are intentionally conservative because password hashing is slow by design.
For throughput:
- Switch to Argon2id
- Move rate limits to Redis
- Horizontal PHP scaling (sessions already persistent)
Reproduce:
php tools/load_test.php
- Atomic UPSERTs remove race conditions without Redis
- Layering mitigations (fingerprint + throttling + lockouts) prevents single-point failures
- Magic links are simple UX-wise but require careful state management
- Bcrypt cost tuning is a tradeoff: UX vs. threat model
- Designing for shared hosting enforces discipline that scales upwards
- Unit tests: TokenService, RateLimiter, CSRF token handling.
- Integration tests: Full request β verify β session flows using SQLite fixtures.
- Static analysis: PHPStan level 7.
- Security simulations: Rate-limit exhaustion, fingerprint mismatch, and replay attempts.
Run all:
php tests/run.php- Hashed magic links (bcrypt)
- Fingerprint binding (IP + UA)
- Secure, HttpOnly, SameSite cookies
- CSRF tokens on state-changing requests
- Email/IP/combined rate limits
- Account lockouts\
- Append-only logs
- GeoIP anomaly alerts
- No plaintext secrets in repo
- Multi-scope rate limiting
- Progressive CAPTCHA
- Token fingerprint binding
- Account lockouts
- GeoIP alerts
- Structured JSON logs (lib/Support/Log.php)
- Security event stream
- Admin console risk indicators
- Integrations possible: Sentry / Datadog / Prometheus
- Indexed expiration fields for tokens/sessions/logs
- Horizontal scaling requires sticky sessions or Redis-backed sessions
- Cleanup via deploy/cleanup.php
- GeoIP cached 7 days
- Future enhancements for large workloads:
- Redis rate limiter / session cache
- WebAuthn or TOTP
- Device-bound session attestations
- Prometheus/OpenTelemetry metrics
- Lightweight framework integration (optional)
All omitted intentionally here to keep the project fully framework-free.
passless/
βββ htdocs/
β βββ index.php
β βββ app.php
β βββ admin.php
β βββ assets/
β βββ auth/
β βββ lib/
β βββ database/
β βββ deploy/
β βββ .htaccess
βββ tests/
βββ tools/
βββ docs/
βββ docker-compose.yml
βββ .env.example
- No email? Check provider API keys and outbound HTTP.
- Blocked by rate limit? Clear rate_limits table or lower thresholds.
- Missing session cookie? Check HTTPS + cookie domain settings.
- FTP deploy failing? Verify passive mode + firewall.
- GeoIP errors? Disable or update endpoint.
- Docker TLS warnings? Trust or replace the self-signed cert.
- WebAuthn & TOTP
- Redis-backed rate limiting
- Disposable email domain checks
- Automated coverage reports
- Prometheus/Grafana dashboards
MIT License.
Dominic Minischetti