mirror of
https://github.com/delight-im/PHP-Auth.git
synced 2025-08-08 09:06:29 +02:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
47afa1c411 | ||
|
26cb41e992 | ||
|
ee485f99ab | ||
|
8fc0b98493 | ||
|
45553afaea | ||
|
7834455e16 | ||
|
e49adf0150 | ||
|
0fb653d6e0 | ||
|
dc233d9d46 |
56
Database/SQLite.sql
Normal file
56
Database/SQLite.sql
Normal file
@@ -0,0 +1,56 @@
|
||||
-- PHP-Auth (https://github.com/delight-im/PHP-Auth)
|
||||
-- Copyright (c) delight.im (https://www.delight.im/)
|
||||
-- Licensed under the MIT License (https://opensource.org/licenses/MIT)
|
||||
|
||||
PRAGMA foreign_keys = OFF;
|
||||
|
||||
CREATE TABLE "users" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK ("id" >= 0),
|
||||
"email" VARCHAR(249) NOT NULL,
|
||||
"password" VARCHAR(255) NOT NULL,
|
||||
"username" VARCHAR(100) DEFAULT NULL,
|
||||
"status" INTEGER NOT NULL CHECK ("status" >= 0) DEFAULT "0",
|
||||
"verified" INTEGER NOT NULL CHECK ("verified" >= 0) DEFAULT "0",
|
||||
"registered" INTEGER NOT NULL CHECK ("registered" >= 0),
|
||||
"last_login" INTEGER CHECK ("last_login" >= 0) DEFAULT NULL,
|
||||
CONSTRAINT "email" UNIQUE ("email")
|
||||
);
|
||||
|
||||
CREATE TABLE "users_confirmations" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK ("id" >= 0),
|
||||
"email" VARCHAR(249) NOT NULL,
|
||||
"selector" VARCHAR(16) NOT NULL,
|
||||
"token" VARCHAR(255) NOT NULL,
|
||||
"expires" INTEGER NOT NULL CHECK ("expires" >= 0),
|
||||
CONSTRAINT "selector" UNIQUE ("selector")
|
||||
);
|
||||
CREATE INDEX "users_confirmations.email_expires" ON "users_confirmations" ("email", "expires");
|
||||
|
||||
CREATE TABLE "users_remembered" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK ("id" >= 0),
|
||||
"user" INTEGER NOT NULL CHECK ("user" >= 0),
|
||||
"selector" VARCHAR(24) NOT NULL,
|
||||
"token" VARCHAR(255) NOT NULL,
|
||||
"expires" INTEGER NOT NULL CHECK ("expires" >= 0),
|
||||
CONSTRAINT "selector" UNIQUE ("selector")
|
||||
);
|
||||
CREATE INDEX "users_remembered.user" ON "users_remembered" ("user");
|
||||
|
||||
CREATE TABLE "users_resets" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK ("id" >= 0),
|
||||
"user" INTEGER NOT NULL CHECK ("user" >= 0),
|
||||
"selector" VARCHAR(20) NOT NULL,
|
||||
"token" VARCHAR(255) NOT NULL,
|
||||
"expires" INTEGER NOT NULL CHECK ("expires" >= 0),
|
||||
CONSTRAINT "selector" UNIQUE ("selector")
|
||||
);
|
||||
CREATE INDEX "users_resets.user_expires" ON "users_resets" ("user", "expires");
|
||||
|
||||
CREATE TABLE "users_throttling" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK ("id" >= 0),
|
||||
"action_type" TEXT NOT NULL CHECK ("action_type" IN ("login", "register", "confirm_email")),
|
||||
"selector" VARCHAR(44) DEFAULT NULL,
|
||||
"time_bucket" INTEGER NOT NULL CHECK ("time_bucket" >= 0),
|
||||
"attempts" INTEGER NOT NULL CHECK ("attempts" >= 0) DEFAULT "1",
|
||||
CONSTRAINT "action_type_selector_time_bucket" UNIQUE ("action_type", "selector", "time_bucket")
|
||||
);
|
79
README.md
79
README.md
@@ -18,9 +18,9 @@ Completely framework-agnostic and database-agnostic.
|
||||
|
||||
* PHP 5.6.0+
|
||||
* PDO (PHP Data Objects) extension (`pdo`)
|
||||
* MySQL Native Driver (`mysqlnd`)
|
||||
* MySQL Native Driver (`mysqlnd`) **or** SQLite driver (`sqlite`)
|
||||
* OpenSSL extension (`openssl`)
|
||||
* MySQL 5.5.3+ **or** MariaDB 5.5.23+ **or** other SQL databases that you create the [schema](Database) for
|
||||
* MySQL 5.5.3+ **or** MariaDB 5.5.23+ **or** SQLite 3.14.1+ **or** other SQL databases that you create the [schema](Database) for
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -39,6 +39,7 @@ Completely framework-agnostic and database-agnostic.
|
||||
1. Set up a database and create the required tables:
|
||||
|
||||
* [MySQL](Database/MySQL.sql)
|
||||
* [SQLite](Database/SQLite.sql)
|
||||
|
||||
## Upgrading
|
||||
|
||||
@@ -75,7 +76,13 @@ Migrating from an earlier version of this project? See our [upgrade guide](Migra
|
||||
```php
|
||||
// $db = new PDO('mysql:dbname=my-database;host=localhost;charset=utf8mb4', 'my-username', 'my-password');
|
||||
// or
|
||||
// $db = new PDO('sqlite:../Databases/my-database.sqlite');
|
||||
|
||||
// or
|
||||
|
||||
// $db = new \Delight\Db\PdoDsn('mysql:dbname=my-database;host=localhost;charset=utf8mb4', 'my-username', 'my-password');
|
||||
// or
|
||||
// $db = new \Delight\Db\PdoDsn('sqlite:../Databases/my-database.sqlite');
|
||||
|
||||
$auth = new \Delight\Auth\Auth($db);
|
||||
```
|
||||
@@ -483,41 +490,41 @@ $uuid = \Delight\Auth\Auth::createUuid();
|
||||
|
||||
For detailed information on how to read and write session data conveniently, please refer to [the documentation of the session library](https://github.com/delight-im/PHP-Cookie#reading-and-writing-session-data), which is included by default.
|
||||
|
||||
## Features
|
||||
## Frequently asked questions
|
||||
|
||||
* registration
|
||||
* secure password storage using the bcrypt algorithm
|
||||
* email verification through message with confirmation link
|
||||
* assurance of unique email addresses
|
||||
* customizable password requirements and enforcement
|
||||
* optional usernames with customizable restrictions
|
||||
* login
|
||||
* keeping the user logged in for a long time (beyond expiration of browser session) via secure long-lived token ("remember me")
|
||||
* account management
|
||||
* change password
|
||||
* tracking the time of sign up and last login
|
||||
* check if user has been logged in via "remember me" cookie
|
||||
* logout
|
||||
* full and reliable destruction of session
|
||||
* session management
|
||||
* protection against session hijacking via cross-site scripting (XSS)
|
||||
* do *not* permit script-based access to cookies
|
||||
* restrict cookies to HTTPS to prevent session hijacking via non-secure HTTP
|
||||
* protection against session fixation attacks
|
||||
* protection against cross-site request forgery (CSRF)
|
||||
* works automatically (i.e. no need for CSRF tokens everywhere)
|
||||
* do *not* use HTTP `GET` requests for "dangerous" operations
|
||||
* throttling
|
||||
* per IP address
|
||||
* per account
|
||||
* enhanced HTTP security
|
||||
* prevents clickjacking
|
||||
* prevent content sniffing (MIME sniffing)
|
||||
* disables caching of potentially sensitive data
|
||||
* miscellaneous
|
||||
* ready for both IPv4 and IPv6
|
||||
* works behind proxy servers as well
|
||||
* privacy-friendly (e.g. does *not* save readable IP addresses)
|
||||
### What about password hashing?
|
||||
|
||||
Any password or authentication token is automatically hashed using the ["bcrypt"](https://en.wikipedia.org/wiki/Bcrypt) function, which is based on the ["Blowfish" cipher](https://en.wikipedia.org/wiki/Blowfish_(cipher)) and (still) considered one of the strongest password hash functions today. "bcrypt" is used with 1,024 iterations, i.e. a "cost" factor of 10. A random ["salt"](https://en.wikipedia.org/wiki/Salt_(cryptography)) is applied automatically as well.
|
||||
|
||||
You can verify this configuration by looking at the hashes in your database table `users`. If the above is true with your setup, all password hashes in your `users` table should start with the prefix `$2$10$`, `$2a$10$` or `$2y$10$`.
|
||||
|
||||
When new algorithms (such as [Argon2](https://en.wikipedia.org/wiki/Argon2)) may be introduced in the future, this library will automatically take care of "upgrading" your existing password hashes whenever a user signs in or changes their password.
|
||||
|
||||
### How can I implement custom password requirements?
|
||||
|
||||
Enforcing a minimum length for passwords is usually a good idea. Apart from that, you may want to look up whether a potential password is in some blacklist, which you could manage in a database or in a file, in order to prevent dictionary words or commonly used passwords from being used in your application.
|
||||
|
||||
To allow for maximum flexibility and ease of use, this library has been designed so that it does *not* contain any further checks for password requirements itself, but instead allows you to wrap your own checks around the relevant calls to library methods. Example:
|
||||
|
||||
```php
|
||||
function isPasswordAllowed($password) {
|
||||
if (strlen($password) < 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$blacklist = [ 'password1', '123456', 'qwerty' ];
|
||||
|
||||
if (in_array($password, $blacklist)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isPasswordAllowed($password)) {
|
||||
$auth->register($email, $password);
|
||||
}
|
||||
```
|
||||
|
||||
## Exceptions
|
||||
|
||||
|
@@ -584,7 +584,7 @@ final class Auth extends UserManager {
|
||||
);
|
||||
|
||||
// ensure that the account has been verified before initiating a password reset
|
||||
if ($userData['verified'] !== 1) {
|
||||
if ((int) $userData['verified'] !== 1) {
|
||||
throw new EmailNotVerifiedException();
|
||||
}
|
||||
|
||||
@@ -679,7 +679,7 @@ final class Auth extends UserManager {
|
||||
$this->updatePassword($userData['id'], $password);
|
||||
}
|
||||
|
||||
if ($userData['verified'] === 1) {
|
||||
if ((int) $userData['verified'] === 1) {
|
||||
$this->onLoginSuccessful($userData['id'], $userData['email'], $userData['username'], $userData['status'], false);
|
||||
|
||||
// continue to support the old parameter format
|
||||
|
@@ -40,8 +40,6 @@ class DatabaseError extends AuthError {}
|
||||
|
||||
class DatabaseDriverError extends DatabaseError {}
|
||||
|
||||
class WrongMysqlDatabaseDriverError extends DatabaseDriverError {}
|
||||
|
||||
class MissingCallbackError extends AuthError {}
|
||||
|
||||
class HeadersAlreadySentError extends AuthError {}
|
||||
|
@@ -66,16 +66,6 @@ abstract class UserManager {
|
||||
|
||||
throw new \InvalidArgumentException('The database connection must be an instance of either `PdoDatabase`, `PdoDsn` or `PDO`');
|
||||
}
|
||||
|
||||
$this->db->addOnConnectListener(function (PdoDatabase $db) {
|
||||
// if a MySQL database is used
|
||||
if ($db->getDriverName() === 'MySQL') {
|
||||
// if the required MySQL Native Driver (mysqlnd) is not used (but instead the older MySQL Client Library (libmysqlclient))
|
||||
if (\extension_loaded('mysqlnd') === false && \stripos($db->getClientVersion(), 'mysqlnd') === false) {
|
||||
throw new WrongMysqlDatabaseDriverError('You must use PDO with the newer \'mysqlnd\' driver instead of the older \'libmysqlclient\' driver');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -20,6 +20,8 @@ header('Content-type: text/html; charset=utf-8');
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
$db = new PDO('mysql:dbname=php_auth;host=127.0.0.1;charset=utf8mb4', 'root', 'monkey');
|
||||
// or
|
||||
// $db = new PDO('sqlite:../Databases/php_auth.sqlite');
|
||||
|
||||
$auth = new \Delight\Auth\Auth($db);
|
||||
|
||||
|
Reference in New Issue
Block a user