mirror of
https://github.com/delight-im/PHP-Auth.git
synced 2025-08-08 00:56:28 +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+
|
* PHP 5.6.0+
|
||||||
* PDO (PHP Data Objects) extension (`pdo`)
|
* PDO (PHP Data Objects) extension (`pdo`)
|
||||||
* MySQL Native Driver (`mysqlnd`)
|
* MySQL Native Driver (`mysqlnd`) **or** SQLite driver (`sqlite`)
|
||||||
* OpenSSL extension (`openssl`)
|
* 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
|
## Installation
|
||||||
|
|
||||||
@@ -39,6 +39,7 @@ Completely framework-agnostic and database-agnostic.
|
|||||||
1. Set up a database and create the required tables:
|
1. Set up a database and create the required tables:
|
||||||
|
|
||||||
* [MySQL](Database/MySQL.sql)
|
* [MySQL](Database/MySQL.sql)
|
||||||
|
* [SQLite](Database/SQLite.sql)
|
||||||
|
|
||||||
## Upgrading
|
## Upgrading
|
||||||
|
|
||||||
@@ -75,7 +76,13 @@ Migrating from an earlier version of this project? See our [upgrade guide](Migra
|
|||||||
```php
|
```php
|
||||||
// $db = new PDO('mysql:dbname=my-database;host=localhost;charset=utf8mb4', 'my-username', 'my-password');
|
// $db = new PDO('mysql:dbname=my-database;host=localhost;charset=utf8mb4', 'my-username', 'my-password');
|
||||||
// or
|
// 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');
|
// $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);
|
$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.
|
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
|
### What about password hashing?
|
||||||
* secure password storage using the bcrypt algorithm
|
|
||||||
* email verification through message with confirmation link
|
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.
|
||||||
* assurance of unique email addresses
|
|
||||||
* customizable password requirements and enforcement
|
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$`.
|
||||||
* optional usernames with customizable restrictions
|
|
||||||
* login
|
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.
|
||||||
* keeping the user logged in for a long time (beyond expiration of browser session) via secure long-lived token ("remember me")
|
|
||||||
* account management
|
### How can I implement custom password requirements?
|
||||||
* change password
|
|
||||||
* tracking the time of sign up and last login
|
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.
|
||||||
* check if user has been logged in via "remember me" cookie
|
|
||||||
* logout
|
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:
|
||||||
* full and reliable destruction of session
|
|
||||||
* session management
|
```php
|
||||||
* protection against session hijacking via cross-site scripting (XSS)
|
function isPasswordAllowed($password) {
|
||||||
* do *not* permit script-based access to cookies
|
if (strlen($password) < 8) {
|
||||||
* restrict cookies to HTTPS to prevent session hijacking via non-secure HTTP
|
return false;
|
||||||
* protection against session fixation attacks
|
}
|
||||||
* protection against cross-site request forgery (CSRF)
|
|
||||||
* works automatically (i.e. no need for CSRF tokens everywhere)
|
$blacklist = [ 'password1', '123456', 'qwerty' ];
|
||||||
* do *not* use HTTP `GET` requests for "dangerous" operations
|
|
||||||
* throttling
|
if (in_array($password, $blacklist)) {
|
||||||
* per IP address
|
return false;
|
||||||
* per account
|
}
|
||||||
* enhanced HTTP security
|
|
||||||
* prevents clickjacking
|
return true;
|
||||||
* prevent content sniffing (MIME sniffing)
|
}
|
||||||
* disables caching of potentially sensitive data
|
|
||||||
* miscellaneous
|
if (isPasswordAllowed($password)) {
|
||||||
* ready for both IPv4 and IPv6
|
$auth->register($email, $password);
|
||||||
* works behind proxy servers as well
|
}
|
||||||
* privacy-friendly (e.g. does *not* save readable IP addresses)
|
```
|
||||||
|
|
||||||
## Exceptions
|
## Exceptions
|
||||||
|
|
||||||
|
@@ -584,7 +584,7 @@ final class Auth extends UserManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// ensure that the account has been verified before initiating a password reset
|
// ensure that the account has been verified before initiating a password reset
|
||||||
if ($userData['verified'] !== 1) {
|
if ((int) $userData['verified'] !== 1) {
|
||||||
throw new EmailNotVerifiedException();
|
throw new EmailNotVerifiedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -679,7 +679,7 @@ final class Auth extends UserManager {
|
|||||||
$this->updatePassword($userData['id'], $password);
|
$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);
|
$this->onLoginSuccessful($userData['id'], $userData['email'], $userData['username'], $userData['status'], false);
|
||||||
|
|
||||||
// continue to support the old parameter format
|
// continue to support the old parameter format
|
||||||
|
@@ -40,8 +40,6 @@ class DatabaseError extends AuthError {}
|
|||||||
|
|
||||||
class DatabaseDriverError extends DatabaseError {}
|
class DatabaseDriverError extends DatabaseError {}
|
||||||
|
|
||||||
class WrongMysqlDatabaseDriverError extends DatabaseDriverError {}
|
|
||||||
|
|
||||||
class MissingCallbackError extends AuthError {}
|
class MissingCallbackError extends AuthError {}
|
||||||
|
|
||||||
class HeadersAlreadySentError 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`');
|
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';
|
require __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
$db = new PDO('mysql:dbname=php_auth;host=127.0.0.1;charset=utf8mb4', 'root', 'monkey');
|
$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);
|
$auth = new \Delight\Auth\Auth($db);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user