1
0
mirror of https://github.com/delight-im/PHP-Auth.git synced 2025-08-08 17:16:29 +02:00

8 Commits

Author SHA1 Message Date
Marco
b1fa54efc9 Automatically re-hash passwords over time when necessary 2016-02-22 18:55:41 +01:00
Marco
2f8aaec42a Remove unnecessary code from MySQL data structure file 2016-02-22 18:53:52 +01:00
Marco
e5777f8bf2 Add '.editorconfig' 2016-01-28 16:26:01 +01:00
Marco
4c27a5a185 Update copyright notices 2016-01-28 16:25:45 +01:00
Marco
90fe75c27e Fix referenced namespaces to be absolute or fully qualified 2015-10-30 09:34:32 +01:00
Marco
5a954ca13b Improve usage guide and feature list in documentation 2015-10-29 21:07:09 +01:00
Marco
6ca92ecb11 Reduce visibility of 'throttle(...)' method that wrongly was 'public' 2015-10-29 20:59:39 +01:00
Marco
6ced34789f Update project tagline 2015-10-29 20:18:02 +01:00
8 changed files with 157 additions and 63 deletions

9
.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
# editorconfig.org
root = true
[*]
charset = utf-8
indent_style = tab
trim_trailing_whitespace = true
end_of_line = lf
insert_final_newline = true

View File

@@ -1,12 +1,3 @@
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
CREATE TABLE IF NOT EXISTS `users` ( CREATE TABLE IF NOT EXISTS `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(254) NOT NULL, `email` varchar(254) NOT NULL,
@@ -17,7 +8,7 @@ CREATE TABLE IF NOT EXISTS `users` (
`last_login` int(10) unsigned DEFAULT NULL, `last_login` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`) UNIQUE KEY `email` (`email`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `users_confirmations` ( CREATE TABLE IF NOT EXISTS `users_confirmations` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
@@ -28,7 +19,7 @@ CREATE TABLE IF NOT EXISTS `users_confirmations` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `selector` (`selector`), UNIQUE KEY `selector` (`selector`),
KEY `email_expires` (`email`,`expires`) KEY `email_expires` (`email`,`expires`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `users_remembered` ( CREATE TABLE IF NOT EXISTS `users_remembered` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
@@ -39,7 +30,7 @@ CREATE TABLE IF NOT EXISTS `users_remembered` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `selector` (`selector`), UNIQUE KEY `selector` (`selector`),
KEY `user` (`user`) KEY `user` (`user`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `users_resets` ( CREATE TABLE IF NOT EXISTS `users_resets` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
@@ -50,7 +41,7 @@ CREATE TABLE IF NOT EXISTS `users_resets` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `selector` (`selector`), UNIQUE KEY `selector` (`selector`),
KEY `user` (`user`) KEY `user` (`user`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `users_throttling` ( CREATE TABLE IF NOT EXISTS `users_throttling` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
@@ -60,8 +51,4 @@ CREATE TABLE IF NOT EXISTS `users_throttling` (
`attempts` mediumint(8) unsigned NOT NULL DEFAULT '1', `attempts` mediumint(8) unsigned NOT NULL DEFAULT '1',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `action_type_selector_time_bucket` (`action_type`,`selector`,`time_bucket`) UNIQUE KEY `action_type_selector_time_bucket` (`action_type`,`selector`,`time_bucket`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

124
README.md
View File

@@ -1,6 +1,8 @@
# Auth # Auth
Secure authentication for PHP, once and for all, really simple to use. Authentication for PHP. Simple, lightweight and secure.
Written once, to be used everywhere.
Completely framework-agnostic and database-agnostic. Completely framework-agnostic and database-agnostic.
@@ -44,11 +46,17 @@ Completely framework-agnostic and database-agnostic.
// $db = new PDO('mysql:dbname=database;host=localhost;charset=utf8', 'username', 'password'); // $db = new PDO('mysql:dbname=database;host=localhost;charset=utf8', 'username', 'password');
// $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$auth = new Delight\Auth\Auth($db); $auth = new \Delight\Auth\Auth($db);
``` ```
If you have an open `PDO` connection already, just re-use it. If you have an open `PDO` connection already, just re-use it.
If you do enforce HTTPS on your site, pass `true` as the second parameter to the constructor. This is optional and the default is `false`.
Only in the very rare case that you need access to your cookies from JavaScript, pass `true` as the third argument to the constructor. This is optional and the default is `false`. There is almost always a *better* solution than enabling this, however.
If your web server is behind a proxy server and `$_SERVER['REMOTE_ADDR']` only contains the proxy's IP address, you must pass the user's real IP address to the constructor in the fourth argument. The default is `null`.
### Sign up a new user (register) ### Sign up a new user (register)
```php ```php
@@ -59,20 +67,22 @@ try {
// we have signed up a new user with the ID `$userId` // we have signed up a new user with the ID `$userId`
} }
catch (Delight\Auth\InvalidEmailException $e) { catch (\Delight\Auth\InvalidEmailException $e) {
// invalid email address // invalid email address
} }
catch (Delight\Auth\InvalidPasswordException $e) { catch (\Delight\Auth\InvalidPasswordException $e) {
// invalid password // invalid password
} }
catch (Delight\Auth\UserAlreadyExistsException $e) { catch (\Delight\Auth\UserAlreadyExistsException $e) {
// user already exists // user already exists
} }
catch (Delight\Auth\TooManyRequestsException $e) { catch (\Delight\Auth\TooManyRequestsException $e) {
// too many requests // too many requests
} }
``` ```
The username in the third parameter is optional. You can pass `null` here if you don't want to manage usernames.
For email verification, you should build an URL with the selector and token and send it to the user, e.g.: For email verification, you should build an URL with the selector and token and send it to the user, e.g.:
```php ```php
@@ -89,20 +99,22 @@ try {
// user is logged in // user is logged in
} }
catch (Delight\Auth\InvalidEmailException $e) { catch (\Delight\Auth\InvalidEmailException $e) {
// wrong email address // wrong email address
} }
catch (Delight\Auth\InvalidPasswordException $e) { catch (\Delight\Auth\InvalidPasswordException $e) {
// wrong password // wrong password
} }
catch (Delight\Auth\EmailNotVerifiedException $e) { catch (\Delight\Auth\EmailNotVerifiedException $e) {
// email not verified // email not verified
} }
catch (Delight\Auth\TooManyRequestsException $e) { catch (\Delight\Auth\TooManyRequestsException $e) {
// too many requests // too many requests
} }
``` ```
The third parameter controls whether the login is persistent with a long-lived cookie. This is known as the "remember me" feature. Set this to `false` to disable the feature. Otherwise, ask the user if they want to enable "remember me". This is usually done with a checkbox in your user interface. Then use their input to decide between `false` and `true` here. This is optional and the default is `false`.
### Perform email verification ### Perform email verification
Extract the selector and token from the URL that the user clicked on in the verification email. Extract the selector and token from the URL that the user clicked on in the verification email.
@@ -113,13 +125,13 @@ try {
// email address has been verified // email address has been verified
} }
catch (Delight\Auth\InvalidSelectorTokenPairException $e) { catch (\Delight\Auth\InvalidSelectorTokenPairException $e) {
// invalid token // invalid token
} }
catch (Delight\Auth\TokenExpiredException $e) { catch (\Delight\Auth\TokenExpiredException $e) {
// token expired // token expired
} }
catch (Delight\Auth\TooManyRequestsException $e) { catch (\Delight\Auth\TooManyRequestsException $e) {
// too many requests // too many requests
} }
``` ```
@@ -134,10 +146,10 @@ try {
// password has been changed // password has been changed
} }
catch (Delight\Auth\NotLoggedInException $e) { catch (\Delight\Auth\NotLoggedInException $e) {
// not logged in // not logged in
} }
catch (Delight\Auth\InvalidPasswordException $e) { catch (\Delight\Auth\InvalidPasswordException $e) {
// invalid password(s) // invalid password(s)
} }
``` ```
@@ -150,6 +162,81 @@ $auth->logout();
// user has been signed out // user has been signed out
``` ```
### Check if the user is signed in
```php
if ($auth->isLoggedIn()) {
// user is signed in
}
else {
// user is *not* signed in yet
}
```
A shorthand/alias for this method is `$auth->check()`.
### Get the user's ID
```php
$id = $auth->getUserId();
```
If the user is not currently signed in, this returns `null`.
A shorthand/alias for this method is `$auth->id()`.
### Get the user's email address
```php
$email = $auth->getEmail();
```
If the user is not currently signed in, this returns `null`.
### Get the user's display name
```php
$email = $auth->getUsername();
```
Remember that usernames are optional and there is only a username if you supplied it during registration.
If the user is not currently signed in, this returns `null`.
### Check if the user was "remembered"
```php
if ($auth->isRemembered()) {
// user did not sign in but was logged in through their long-lived cookie
}
else {
// user signed in manually
}
```
If the user is not currently signed in, this returns `null`.
### Get the user's IP address
```php
$ip = $auth->getIpAddress();
```
### Utilities
#### Create a random string
```php
$length = 24;
$randomStr = \Delight\Auth\Auth::createRandomString($length);
```
#### Create a UUID v4 as per RFC 4122
```php
$uuid = \Delight\Auth\Auth::createUuid();
```
## Features ## Features
* registration * registration
@@ -167,6 +254,7 @@ $auth->logout();
* logout * logout
* full and reliable destruction of session * full and reliable destruction of session
* session management * session management
* protection against session hijacking
* protection against session fixation attacks * protection against session fixation attacks
* throttling * throttling
* per IP address * per IP address
@@ -175,6 +263,10 @@ $auth->logout();
* prevents clickjacking * prevents clickjacking
* prevent content sniffing (MIME sniffing) * prevent content sniffing (MIME sniffing)
* disables caching of potentially sensitive data * 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)
## Exceptions ## Exceptions
@@ -201,7 +293,7 @@ All contributions are welcome! If you wish to contribute, please create an issue
## License ## License
``` ```
Copyright 2015 delight.im <info@delight.im> Copyright (c) delight.im <info@delight.im>
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -1,6 +1,6 @@
{ {
"name": "delight-im/auth", "name": "delight-im/auth",
"description": "Secure authentication for PHP, once and for all, really simple to use", "description": "Authentication for PHP. Simple, lightweight and secure.",
"require": { "require": {
"php": ">=5.5.0" "php": ">=5.5.0"
}, },

View File

@@ -1,7 +1,7 @@
<?php <?php
/** /*
* Copyright 2015 delight.im <info@delight.im> * Copyright (c) delight.im <info@delight.im>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@ namespace Delight\Auth;
require __DIR__.'/Base64.php'; require __DIR__.'/Base64.php';
require __DIR__.'/Exceptions.php'; require __DIR__.'/Exceptions.php';
/** Secure authentication for PHP, once and for all, really simple to use */ /** Base class that provides all methods, properties and utilities for secure authentication */
class Auth { class Auth {
const SESSION_FIELD_LOGGED_IN = 'auth_logged_in'; const SESSION_FIELD_LOGGED_IN = 'auth_logged_in';
@@ -289,6 +289,12 @@ class Auth {
$userData = $stmt->fetch(\PDO::FETCH_ASSOC); $userData = $stmt->fetch(\PDO::FETCH_ASSOC);
if ($userData !== false) { if ($userData !== false) {
if (password_verify($password, $userData['password'])) { if (password_verify($password, $userData['password'])) {
// if the password needs to be re-hashed to keep up with improving password cracking techniques
if (password_needs_rehash($userData['password'], PASSWORD_DEFAULT)) {
// create a new hash from the password and update it in the database
$this->updatePassword($userData['id'], $password);
}
if ($userData['verified'] == 1) { if ($userData['verified'] == 1) {
$this->onLoginSuccessful($userData['id'], $email, $userData['username'], false); $this->onLoginSuccessful($userData['id'], $email, $userData['username'], false);
@@ -745,7 +751,7 @@ class Auth {
* @throws TooManyRequestsException if the number of allowed attempts/requests has been exceeded * @throws TooManyRequestsException if the number of allowed attempts/requests has been exceeded
* @throws AuthError if an internal problem occurred (do *not* catch) * @throws AuthError if an internal problem occurred (do *not* catch)
*/ */
public function throttle($actionType, $customSelector = null) { private function throttle($actionType, $customSelector = null) {
// if a custom selector has been provided (e.g. username, user ID or confirmation token) // if a custom selector has been provided (e.g. username, user ID or confirmation token)
if (isset($customSelector)) { if (isset($customSelector)) {
// use the provided selector for throttling // use the provided selector for throttling

View File

@@ -1,7 +1,7 @@
<?php <?php
/** /*
* Copyright 2015 delight.im <info@delight.im> * Copyright (c) delight.im <info@delight.im>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
<?php <?php
/** /*
* Copyright 2015 delight.im <info@delight.im> * Copyright (c) delight.im <info@delight.im>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
<?php <?php
/** /*
* Copyright 2015 delight.im <info@delight.im> * Copyright (c) delight.im <info@delight.im>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@ $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
require __DIR__.'/../src/Auth.php'; require __DIR__.'/../src/Auth.php';
$auth = new Delight\Auth\Auth($db); $auth = new \Delight\Auth\Auth($db);
$result = processRequestData($auth); $result = processRequestData($auth);
@@ -38,7 +38,7 @@ else {
showGuestUserForm(); showGuestUserForm();
} }
function processRequestData(Delight\Auth\Auth $auth) { function processRequestData(\Delight\Auth\Auth $auth) {
if (isset($_POST)) { if (isset($_POST)) {
if (isset($_POST['action'])) { if (isset($_POST['action'])) {
if ($_POST['action'] === 'login') { if ($_POST['action'] === 'login') {
@@ -47,16 +47,16 @@ function processRequestData(Delight\Auth\Auth $auth) {
return 'ok'; return 'ok';
} }
catch (Delight\Auth\InvalidEmailException $e) { catch (\Delight\Auth\InvalidEmailException $e) {
return 'wrong email address'; return 'wrong email address';
} }
catch (Delight\Auth\InvalidPasswordException $e) { catch (\Delight\Auth\InvalidPasswordException $e) {
return 'wrong password'; return 'wrong password';
} }
catch (Delight\Auth\EmailNotVerifiedException $e) { catch (\Delight\Auth\EmailNotVerifiedException $e) {
return 'email not verified'; return 'email not verified';
} }
catch (Delight\Auth\TooManyRequestsException $e) { catch (\Delight\Auth\TooManyRequestsException $e) {
return 'too many requests'; return 'too many requests';
} }
} }
@@ -83,16 +83,16 @@ function processRequestData(Delight\Auth\Auth $auth) {
return $auth->register($_POST['email'], $_POST['password'], $_POST['username'], $callback); return $auth->register($_POST['email'], $_POST['password'], $_POST['username'], $callback);
} }
catch (Delight\Auth\InvalidEmailException $e) { catch (\Delight\Auth\InvalidEmailException $e) {
return 'invalid email address'; return 'invalid email address';
} }
catch (Delight\Auth\InvalidPasswordException $e) { catch (\Delight\Auth\InvalidPasswordException $e) {
return 'invalid password'; return 'invalid password';
} }
catch (Delight\Auth\UserAlreadyExistsException $e) { catch (\Delight\Auth\UserAlreadyExistsException $e) {
return 'user already exists'; return 'user already exists';
} }
catch (Delight\Auth\TooManyRequestsException $e) { catch (\Delight\Auth\TooManyRequestsException $e) {
return 'too many requests'; return 'too many requests';
} }
} }
@@ -102,13 +102,13 @@ function processRequestData(Delight\Auth\Auth $auth) {
return 'ok'; return 'ok';
} }
catch (Delight\Auth\InvalidSelectorTokenPairException $e) { catch (\Delight\Auth\InvalidSelectorTokenPairException $e) {
return 'invalid token'; return 'invalid token';
} }
catch (Delight\Auth\TokenExpiredException $e) { catch (\Delight\Auth\TokenExpiredException $e) {
return 'token expired'; return 'token expired';
} }
catch (Delight\Auth\TooManyRequestsException $e) { catch (\Delight\Auth\TooManyRequestsException $e) {
return 'too many requests'; return 'too many requests';
} }
} }
@@ -118,10 +118,10 @@ function processRequestData(Delight\Auth\Auth $auth) {
return 'ok'; return 'ok';
} }
catch (Delight\Auth\NotLoggedInException $e) { catch (\Delight\Auth\NotLoggedInException $e) {
return 'not logged in'; return 'not logged in';
} }
catch (Delight\Auth\InvalidPasswordException $e) { catch (\Delight\Auth\InvalidPasswordException $e) {
return 'invalid password(s)'; return 'invalid password(s)';
} }
} }
@@ -139,7 +139,7 @@ function processRequestData(Delight\Auth\Auth $auth) {
return null; return null;
} }
function showDebugData(Delight\Auth\Auth $auth, $result) { function showDebugData(\Delight\Auth\Auth $auth, $result) {
echo '<pre>'; echo '<pre>';
echo 'Last operation'."\t\t\t\t"; echo 'Last operation'."\t\t\t\t";
@@ -171,9 +171,9 @@ function showDebugData(Delight\Auth\Auth $auth, $result) {
echo "\n"; echo "\n";
echo 'Auth::createRandomString()'."\t\t"; echo 'Auth::createRandomString()'."\t\t";
var_dump(Delight\Auth\Auth::createRandomString()); var_dump(\Delight\Auth\Auth::createRandomString());
echo 'Auth::createUuid()'."\t\t\t"; echo 'Auth::createUuid()'."\t\t\t";
var_dump(Delight\Auth\Auth::createUuid()); var_dump(\Delight\Auth\Auth::createUuid());
echo '</pre>'; echo '</pre>';
} }