Skip to content

Repository files navigation


Vanilla-PHP-SaaS-Kit

PHP Version HTML Version CSS Version JavaScript Version Security Status

An ultra-lightweight, feature-complete, secure-by-default PHP SaaS Website ready to deploy right out-the-box.
ZERO third-party Frameworks, Packages, or Dependencies.*

Includes everything SaaS needs to get started - a complete Auth system with REST API, OAuth2 SSO, First-Party Analytics, Custom Link Tracking, Multi-level User Permissions with a User Administration Dashboard, and a Developer Console to control it all.

* PHPMailer is vendored and included in the source at a pinned version. It only updates when you update it.

Table of Contents

Getting Started

Requirements

  • PHP 8.0+ (Tested on 8.3)
  • Apache Server
  • MySQL

Installation

  1. You will need to create three (3) SQL databases named site_loginsystem, site_interactions, and site_statistics. For the site_loginsystem and site_statistics databases, find the .sql file with the corresponding name in assets/setup/, and execute it in the DBMS. If you wish to rename the databases, you may do so, but those names must be carried forward into Step 2.

  2. Edit the file assets/setup/env.php and setup the Application, Database Connection, and SMTP Server information. If you changed the database names in Step 1, be sure to fill in the corresponding new names here. Port value is usually not needed in Database connections, so only edit if you know what you are doing. The email server and account provided, will be used to send confirmation, validation, and notification emails.

  3. Synchronize files to your web server via SSH or FTP. Visit the site and test / verify that it works.

  4. (Optional) If your host allows it, create a directory one-level above the webroot, called private. This will usually be in your /home/[user]/ directory, where [user] is your SSH/FTP username. If the host does allow read/write from this directory, and you have visited a page, the directory may have already been created. Move env.php into this directory. Test and verify that the site still works. This greatly improves the security of the site, so it is highly advised to complete this step. Sometimes it may take some tweaking of code paths.

Existing Account(s)

After setup, the database contains a sample account for testing. Use that or head over to the signup page and start making new accounts.

// credentials for existing account

username: testuser
password: 123testing456

Project File Structure

Path / File Purpose
[accessible URLs/Pages] All folders in root directory except assets.
assets/css Folder for global or layout-specific custom CSS files.
assets/images Images used across the site.
assets/includes Reusable Functions or Classes.
assets/js Javascript files.
assets/setup Project configuration and setup files.
assets/uploads Folder for all content uploaded by application users.
assets/uploads/users Profile-Images uploaded by users.
assets/vendor All vendored dependencies (PHPMailer).

Building On-Top

The system is developed with the default PHP Application file structure in order to avoid conflicts with most other projects. The primary landing page is /home/, which is set in the root index.php file. You can change this at your discretion. Generally, site-wide function groups or classes live in /assets/includes/. This includes things like the Database Connection Manager class, and security-related functions.

Site-wide page initialization components live in /assets/layouts/. These must generally be called on every page. init.php initializes the framework, global constants, and database connections. No page output, so header() calls can redirect post-init. header.php writes the HTML page opening elements, links stylesheets and javascript files, and opens the content. navbar.php writes the navigation-bar to the page. footer.php writes some final javascript to the page, closes and tags.

To Create A New Page

Recommended: Simply clone or duplicate an existing page. /home/ is a good candidate for this, since it has no extra functionality. Clone the entire directory and modify index.php to your liking.

Each folder in the web root directory is treated as its own "page" - by default routing to the index.php page it contains. Each page must follow this general structure;

<?php
define('TITLE', 'Your Page Title');
define('META_DESCRIPTION', 'Your Meta Description');
define('META_KEYWORDS', 'Your Meta Keywords');
require_once $_SERVER['DOCUMENT_ROOT'] . '/assets/layouts/init.php';
check_verified(); // Checks that the user is logged-in AND has verified their email address, aborts if not.
require_access_level(4); // gates the page to ONLY be visible to users with access_level >= 4.
require $_SERVER['DOCUMENT_ROOT'] . '/assets/layouts/header.php'; // Optional for pages that do not need markup or navigation.
?>

// PAGE CONTENT HERE

<?php require $_SERVER['DOCUMENT_ROOT'] . '/assets/layouts/footer.php'; ?>

Aside from that you can do pretty much anything you want in the middle.

If a page needs any kind of specific functionality to that page (like form-handlers or config files), you should create a ./includes/ subdirectory, and place that file there. The included .htaccess rules will ensure the file is not publicly accessible. See /login/ or /register/ for examples.

Zero-Dependency Threat Model

Why not use a framework like Laravel or Magento?

For a multitude of reasons, but primarily to reduce bloat, and increase security.
If that last one sounds counter-intuitive to you, you are exactly the person this project is for. Read on.

Avoiding Framework Bloat

Even the slimmest and lightest of these frameworks, are still incredibly bloated compared to a vanilla system. They include features many developers may never use. Not everyone needs an AI SDK, Cloud integrations, or ORM layers. While these frameworks are all heavily optimized at what they do, they attempt to do so much that the up-front performance cost of merely running the framework itself must be considered.

Wordpress is probably the single worst about this of any framework that I have seen. It is incredibly capable and versatile - but at what cost?
Even a fresh WordPress install running the basic, default template theme that ships with the framework, will instantly drop your sites performance score to about 75-80 on Google's PageSpeed Insights.
It is nearly impossible for a WordPress site to function any better or faster than this, merely because of the sheer volume of things it is doing behind-the-scenes.

Supply-Chain Attack Mitigation

Most frameworks REQUIRE a supply-chain dependency manager like Composer or NPM. Both Magento and Laravel have Composer as a hard-requirement, for example.
These dependency managers themselves introduce the surface for Supply-Chain Attacks. They themselves, put your server at immense risk simply by being installed.

Supply-Chain Attacks are an exploit of developer laziness and trust - You assume the updates are pulled from a trusted repository, so therefore you feel you can be lazy and not double-check the code before it is pushed to your server.
This was ALWAYS a bad idea. From the very start. There is no world where that dynamic makes sense and doesn't pose a massive security risk.

Core Features

User Login & Registration System

The login system is relatively straightforward; each page follows the exact same structure. /login/ contains the login page, with /login/includes/ containing the form-submission handler code. Registration is handled by the /register/ page.

First-Party Image CAPTCHA

This system includes a first-party, self-hosted CAPTCHA Challenge Generation library. It currently lives inside /register/includes/, because it is not used anywhere else. It can be moved to /assets/includes/ if you wish to do so. The CAPTCHA library generates a challenge and answer, and stores the answer in $_SESSION. New challenge types and questions can easily be added, as well as new fonts, and additional noise layers. Fully configurable in code.

Email Verification & Password Reset

Upon registration, a verification email is sent to the email address supplied by the user. This email contains a link which the user can click to confirm their email, and achieve "Verified" status. The verification email can be resent from the /verify/ page, or the /profile-edit/ page.

The login page provides a "Recover Pssword" link. Submitting this form will send a password reset email, containing a link, to the email on file for that account. The user must have an accurate email set in order for this to work. If they typo'ed their email or never verified, they may not be able to reset their password. This is gated with a brute-force prevention lockout - repeated attempts to reset will trigger a delay before the request can be sent again.

Inactivity-based Auto-Logout

The system runs a Javascript snippet which routinely checks if the user is actively doing something on the page. When it detects the user has been inactive for a specified length of time, it will automatically log the user out. The Inactivity Time threshold can be adjusted in the Developer Console.

Remember Me Feature

Remember Me will keep the user signed in on the current device, past the inactivity timeout duration. If they sign in on another device, the first device will be signed out. (token revoked)

Secure Cookie Headerd

The system's Remember Me Cookies set secure, httponly, and samesite on every instantiation.

OAuth2 Single-Sign-On

The Auth system provides OAuth2 Single-Sign-On support for four included providers: Discord, GitHub, Google, and Microsoft. Each provider can be individually enabled/disabled in the Dev Console. Adding new providers must be done in code, but only requires adding a single entry to assets/includes/oauth.php: Look for the $providers array under "PROVIDER REGISTRY". On the back-end, there are two (2) user tables, one for first-party accounts, and one for provider account data.

Account Linking

Account Linking allows users to sign in using either method (Email & Password OR provider OAuth), to a single account - the user session will be created using the email account's permissions. This can only be done from a first-party (email-&-password-based) account. They will have the ability to link one account from each enabled provider simultaneously to their email account. Users can access this feature from the profile-edit page.

Auth REST API

The Auth REST API can be used to issue client tokens and secrets, which can be used by external apps to leverage this Auth system to authenticate users and verify permissions. To get started using the REST API, first ensure the feature is enabled in the Developer Console (it defaults to disabled). Then click the API Clients tab at the top. Create a Client, type a name and hit Create Client. Copy the secret immediately, as it will only ever be shown once. Input it into your app, along with the Client ID. The API request format is in the Endpoints box at the bottom of the page.

Stateless Bearer-Token Auth

The API usess stateless bearer-token authorization. Access tokens are short-lived (1 hour) and refresh tokens are long-lived (30 days) rotating on every use. Refresh tokens are grouped into "families"; if an already-rotated refresh token is ever presented again, the entire family is revoked. The same theft-detection model is used by the remember-me cookies. Client secrets are stored as SHA-256 digests and shown only once at creation, so make sure you save it.

Editable User Profiles

Users can Edit their own Profiles - they can change their username, email, password, bio information, and avatar image. Updating the password requires confirming the old one. Updating the username checks for and prevents duplicates.

User Avatar Upload

Avatar uploads are validated by actual file content (MIME sniffing via finfo) against an allow-list rather than by file extension, then stored under a randomized hex filename. Uploaded avatars are stored in the assets/uploads/users/ directory where .htaccess refuses script execution, so a disguised or polyglot upload cannot become executable code.

GDPR Compliance Tools

Account Data Export

Users can request to download a copy of their user account data from the server, including tracked clicks and link interactions. They can initiate this from the /profile-edit/ page. It is self-service, and the download is provided immediately.

Account Soft-Delete

Users may request to have their account deleted from the server. This does not immediately delete the account, to prevent abuse. It marks an account as deleted by setting a date to the deleted_at column of their user table row. The database maintenance Cron Job will eventually cull the row from the database whenever it next runs.

Global ERROR & STATUS values

Global values stored in $_SESSION for Error and Status reporting. These are echo'ed and reset on every page. Creates a better debugging experience for developers, but also is the primary user-facing error reporting mechanism. Allows an error message to still be displayed when a page exits or redirects to another page before displaying anything.

User Permission System

Multi-level Access Gating

Users have a column named access_level which controls their permissions across the site. This is a simple integer value, ranging from zero (0) to five (5). The framework includes a simple function you can call to gate a page to a specific user permission level; require_access_level([int]) where [int] is the minimum level you want to give access to that page. The existing defined roles are as follows;

  • 0 = Unverified users, banned or deleted users
  • 1 = Verified users
  • 2 = Additional permission slot
  • 3 = Additional permission slot
  • 4 = Administrator
  • 5 = Developer

User Administration Dashboard

The Admin Dashboard provides a simple table-layout which displays a summary of all user accounts, allowing you to select one to load in full. Once loaded, the users permissions and account details can be modified, then saved back to the database. The search allows you to search for any term across all user data columns.

It has specific checks to ensure no user can set another user's, or their own, permission level higher than it already is. It also attempts to prevent showing higher-level accounts from being shown to lower-level accounts - an Admin, does not even see a Developer account.

First-Party Analytics Suite

The system includes built-in, self-hosted analytics - all data is stored on your own server, and never shared with any third-party. For server-side analytics, the server logs every page or resource request it recieves to a requests table in the database. For client-side analytics, a Javascript worker is executed on every page, which reports to a data-collection endpoint in the web root.

Analytics Dashboard

The Analytics Dashboard displays everything you need to know about your site's traffic; General visitor statistics, a graph of sessions/visits over time, share by browser, OS, and Device Type, Visitor breakdowns by Country and Time of Day, and a seaerchable list of all recent visits and page requests so you can see exactly what your server is doing in real time. Advanced search filters for recent visits, to filter by date, type, browser, OS, device, IP, URL, and more. Uses a remote service geoiplookup.com to cache and provide geolocation for IP's.

Client Fingerprinting

The analytics background-worker javascript functions as a unique fingerprint generator. It generates a unique hash (called a fingerprint) from the current browser and device's capabilities, by doing an in-depth analysis of the GPU, rendering capabilities, CPU & GPU speed, API availability, and more. This fingerprint ID can be used to track visitors across multiple sessions. It is also used to determine whether a visitor is a first-time or repeat visitor.

Session Replay & Heatmaps

Analytics tracks every major event that occurs on the page. Any time the user interacts with the page, a datapoint is generated describing the action the user took. It tracks everything from mouse movements, clicks, scrolls, and page navigations, to focus-changes, element visibility changes, and form submissions.

From the Analytics Dashboard, any request listed with a linked (blue) client ID, has a replayable session. You can click this to be taken to the Session Replay UI. The Session Replay UI shows the current Client Session ID being viewed at the top, along with how many different events of various types are contained within it. Using the provided controls, you can select a specific page toview interactions on, filter interaction types, pause/resume replay, adjust replay speed, and scrub. The main panel displays the page the user was viewing at the time, with an added Heatmap overlay showing where interactions ocurred. The sidebar on the right displays the full list of all interaction events that were recorded during that session. You can click one to jump to that action within the replay timeline.

Custom Link Tracking

This tool can be accessed from the Analytics Dashboard. It allows you to create custom links, which can be used both within and outside of the site itself, in order to track link traffic. The primary use-case is for tracking the effectiveness of links placed on other, third-party sites. For example, if you aadvertise on Facebook, you could place a tracked link into your ad so your site can track click-through independently - no need to trust Meta's numbers.

Developer Console

Maintenance Mode

A one-click maintenance mode toggle that will deny all requests from all IP's not in the whitelist, with a modifiable message which is displayed to users. Accounts with Developer-level access (5) are always allowed to connect and are never denied. The login is also always served, even in maintenance mode, to allow developers to log in.

User Feature Toggles

Allows you to enable & disable nearly every single individual feature of the site. When a feature is turned off, it simply becomes unreachable / non-functional. Each feature is standalone and functions independently.

Security Feature Toggles

Allows you to enable & disable several security features such as login-attempt throttling, Requiring verified emails for access, and restricting the Auth REST API to HTTPS-only. Only one option here defaults to off (disabled) - HSTS (HTTP Strict Transport Security). You should seek to enable this option as soon as possible, once you have established an SSL Cert for your website. It is recommended to keep all security features enabled whenever possible.

Global Settings

General site configuration and constant values which can be modified and changed. You can set things like the Application Name, Title, and Description, Meta Keywords, Public URL, Inactivity timeout length, and others.

Cron Task Orchestrator

The "Scheduled Tasks" tab in the Developer Console, gives your developers direct control over your cron jobs and automated recurring tasks from the web UI - no back-end or host login required. The /cron/ directory, is where Cron jobs and recurring task scripts are intended to live. Most importantly, it contains cron_orchestrator.php - the main orchestrator for site-wide recurring tasks.

This folder should not be placed in the web root if possible. Instead, it should exist one level higher in your directory tree, like the automatically-created private directory.

To enable the custom orchestrator, you will first need to create a single Cron Job within your host's dashboard, or your server software. Point it at cron_orchestrator.php, and select the shortest possible interval you are comfortable with. Recommended: 1 to 10 minutes for best performance. Once this is done, you can now use the web interface within the Developer Console to create and modify your Cron jobs. There are four (4) jobs pre-populated into the database upon install - these correspond to the four self-maintenance functions included with this system, described below.

Automatic Maintenance Tasks

Included with the repo are several cron job CLI scripts to maintenance the various databases, cull old rows, and archive very old analytics data. These jobs can be selectively enabled or disabled as you see fit - but generally I recommend keeping them as they prevent accumulation of stale data within the database. Specifically, with analytics turned on, the analytics database will blow up in size rather quickly as it generates a lot of data.

Each Task's frequency determines how often it is run - you can adjust these independently as you see fit. Each Task provides an error log - for quick debugging, and checking on failed runs.

Tasks can only be created from scripts in the cron directory by default. A script that exists anywhere else, will refuse to run. New directories can be added by modifying assets/includes/cron-lib.php if you would like to change this, but it is not recommended. Look for:

/** Directories (relative to the document root) that may contain schedulable jobs. */
function cron_roots(): array
{
    // This repository keeps every schedulable job in cron/. Add a directory
    // here to make its scripts selectable in the UI; anything outside these
    // roots is refused both on save AND at execution time.
    return ['cron'];
}

DNS Integrity Monitor

The Developer Console has a built in DNS Monitoring tool which allows you to establish a baseline for the DNS configuration of your server. If anything changes or differs from this baseline in the future, the system can notify you of exactly what changed.

Security Features & Paradigms

Parameterized SQL Queries

All database connections are routed through the DBManager class, which lives in /assets/setup/db_manager.inc.php. All queries, are consistently parameterized using PDO. Strict type checking is enforced, and data is sanity checked both ways.

Header & Email Injection Protection

Client-side hardening headers are properly set; X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy: ..., and Content-Security-Policy: default-src 'self'; .... All emails are checked for injection using regex matching on all fields.

CSRF Protection

Every form or interaction control across every single page, includes a server-generated CSRF Token, to prevent CSRF vulnerabilities. This verifies that every form is only submitted once, and makes automated submissions from adversarial servers more difficult and obvious.

Secure Selectors & Validators

Token values use a CSPRNG for generation. Session ID is never leaked to the user.

Brute-Force & Enumeration Defense

Repeat form submissions and login attempts are gated by a configurable throttle. User enumeration of public user profiles is prevented by the same rate-limiting.

Contribution Guidelines

Public contributions such as bug reports and fixes, feature additions, or modifications are welcome! Before submitting a pull request, please refer to the Contribution Guidelines.

AI Usage Policy

You can check our policy on LLM generated code in the AI Usage Policy

Credits

This project is built upon the code of an archived repo; https://github.com/msaad1999/PHP-Login-System/tree/master
Credits to msaad1999 (Muhammad Saad) for developing the original skeleton, which while it had its flaws, served as the inspiration for this repo.

License

We retain the permissive MIT License for the project, from the original repo on which it was based.
You are free to use, modify, change, and distribute this source as you see fit.
I only ask that if you do make a substantial change from which others might benefit, that you consider contributing your feature back to this repo.

About

An ultra-lightweight, battle-tested, secure-by-default PHP SaaS Website Kit. Zero third-party Frameworks, Packages, or Dependencies. Includes a complete Auth system with REST API, Multi-Vendor OAuth2 SSO, First-Party Analytics with Custom Link Tracking, User Administration Dashboard, and Developer Console.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages