Last active
September 21, 2019 14:53
-
-
Save eric1234/1887174 to your computer and use it in GitHub Desktop.
A authentication library that provides a login and forgot password function.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
# https://gist.github.com/eric1234/1887174 | |
/* | |
A PHP library that provides everything necessary for implementing | |
a login system. You need to create a few files for this to work: | |
* login.php | |
* logout.php | |
* password_reset.php | |
* update_password.php | |
* password_reset_email.php | |
The gist provides examples of all these files that can be used as | |
a starting place. | |
*/ | |
namespace Auth { | |
/* | |
* Use this to check if a user is logged in. Will redirect to $login_url | |
* if not. Will save the original destination for after login. | |
*/ | |
function authenticate() { | |
session_start(); | |
if( !isset($_SESSION['login_id']) ) { | |
$_SESSION['return_to'] = host().$_SERVER['REQUEST_URI']; | |
global $login_url; | |
redirect($login_url); | |
} | |
} | |
/* | |
* Will provide the functionality for a user to login. $sql_authenticate | |
* is used to verify credentials. An example SQL statements is: | |
* | |
* SELECT id FROM users | |
* WHERE | |
* username = ':username' AND | |
* password_hash = SHA1(CONCAT(':password', password_salt)) | |
* | |
* $after_url is where the user will go after login if no saved location | |
* is stored in the session. | |
*/ | |
function login($sql_authenticate, $after_url) { | |
session_start(); | |
if( isset($_GET['return_to']) ) | |
$_SESSION['return_to'] = $_GET['return_to']; | |
if( isset($_SESSION['return_to']) ) | |
$after_url = $_SESSION['return_to']; | |
# Already logged in. Just send them on. | |
if(isset($_SESSION['login_id'])) return redirect($after_url); | |
if(isset($_POST['username']) && isset($_POST['password'])) { | |
# Not logged in but trying. | |
$sql_authenticate = sql_bind($sql_authenticate, array( | |
'username' => $_POST['username'], | |
'password' => $_POST['password'], | |
)); | |
$result = query($sql_authenticate); | |
$id = fetch_value($result); | |
if($id) { | |
# Valid credentials. Setup session and redirect. | |
$_SESSION['login_id'] = $id; | |
unset($_SESSION['return_to']); # In case it was set | |
redirect($after_url); | |
} | |
} | |
} | |
/* | |
* Drop the user's session (all session variables) | |
*/ | |
function logout() { | |
session_start(); | |
session_unset(); | |
session_destroy(); | |
} | |
/* | |
* Provides "Forgot Password" functionality. $sql_token will set the | |
* token for the user's who password we are resetting and should look | |
* like: | |
* | |
* UPDATE users SET perishable_token = ':token' | |
* WHERE email = ':email' | |
* | |
* The return value may contain a message to be outputted to the user. | |
*/ | |
function password_reset($sql_token) { | |
if( !isset($_POST['email']) ) return; | |
$token = sha1(mt_rand()); | |
$sql_token = sql_bind($sql_token, array( | |
'token' => $token, | |
'email' => $_POST['email'], | |
)); | |
$result = query($sql_token); | |
if( update_happened($result) ) { | |
global $password_reset_template; | |
global $update_password_url; | |
$url = host().$update_password_url; | |
ob_start(); | |
require $password_reset_template; | |
$message = ob_get_contents(); | |
ob_end_clean(); | |
global $email_from_user; | |
$from = "$email_from_user@".domain(); | |
$to = $_POST['email']; | |
mail_to($to, "Password Reset", $message, "From: $from\r\n"); | |
return "Password reset email sent. Please check your email."; | |
} else { | |
return "Email address not found."; | |
} | |
} | |
/* | |
* Provide password update functions from token generated by forgot | |
* password function above. | |
* | |
* $sql_update should update a user's password and should look something | |
* like: | |
* | |
* UPDATE users | |
* SET | |
* password = SHA1(CONCAT(':password', password_salt)), | |
* perishable_token = ':new_token' | |
* WHERE perishable_token = ':token'; | |
* | |
* The return value may be a message to be outputted to the user. | |
*/ | |
function update_password($sql_update) { | |
if( ! (isset($_POST['password']) && isset($_POST['password_confirmation'])) ) return; | |
if(empty($_POST['password'])) { | |
return 'Password cannot be blank'; | |
} elseif( $_POST['password'] != $_POST['password_confirmation'] ) { | |
return 'Password confirmation must match'; | |
} else { | |
$sql_update = sql_bind($sql_update, array( | |
'password' => $_POST['password'], | |
'token' => $_GET['token'], | |
'new_token' => sha1(mt_rand()), | |
)); | |
$result = query($sql_update); | |
if( update_happened($result) ) { | |
return "Your password has been updated."; | |
} else { | |
global $login_url; | |
$start_url = host().$login_url; | |
return <<<ERROR | |
Your password was not updated because the link in the email you | |
received is no longer valid. <a href="$start_url">Click here</a> to get | |
a new email with a valid link. | |
ERROR; | |
} | |
} | |
} | |
######## PRIVATE ######## | |
function redirect($url) { | |
header("Location: $url"); | |
exit; | |
} | |
function domain() { | |
return str_replace('www.', '', $_SERVER['SERVER_NAME']); | |
} | |
function host() { | |
$protocol = 'http'; | |
$port = ':'.$_SERVER["SERVER_PORT"]; | |
if($_SERVER["HTTPS"] == 'on') $protocol .= 's'; | |
if($protocol == 'http' && $port == ':80') $port = ''; | |
if($protocol == 'https' && $port == ':443') $port = ''; | |
return "$protocol://$_SERVER[SERVER_NAME]$port"; | |
} | |
function sql_bind($sql, $data) { | |
foreach($data as $key => $value) | |
$sql = str_replace(":$key", db_escape($value), $sql); | |
return $sql; | |
} | |
} | |
# Pop back to main namespace so project and easily override | |
namespace { | |
# Assume MySQL but allow override by having calling code pre-define | |
if( !function_exists('query') ) { | |
function query($sql) { | |
$result = mysql_query($sql); | |
if(!$result) die(mysql_error()); | |
return $result; | |
} | |
} | |
if( !function_exists('update_happened') ) { | |
function update_happened($result) { | |
return mysql_affected_rows() > 0; | |
} | |
} | |
if( !function_exists('fetch_value') ) { | |
function fetch_value($result){ | |
if( mysql_num_rows($result) > 0 ) return mysql_result($result, 0); | |
} | |
} | |
if( !function_exists('db_escape') ) { | |
function db_escape($value) { | |
return mysql_real_escape_string($value); | |
} | |
} | |
# Mail hook so we can route elsewhere. By default just use built-in mail | |
if( !function_exists('mail_to') ) { | |
function mail_to($to, $subject, $body, $headers='', $additional='') { | |
return mail($to, $subject, $body, $headers, $additional); | |
} | |
} | |
## Global variables that can customize the behavior. | |
# Who the e-mail appears to come from. | |
$email_from_user = 'no-reply'; | |
# The URLs of the various pages | |
$login_url = '/login.php'; | |
$update_password_url = '/update_password.php'; | |
$password_reset_template = 'password_reset_email.php'; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
require 'auth.php'; | |
$sql = <<<SQL | |
SELECT id FROM users | |
WHERE | |
email = ':username' AND | |
password_hash = SHA1(CONCAT(':password', password_salt)) | |
SQL; | |
Auth\login($sql, '/after_login.php'); | |
?> | |
<form method="POST"> | |
<?php if($_SERVER['REQUEST_METHOD'] == 'POST') { ?> | |
<p class="error">E-mail/password invalid</p> | |
<?php } ?> | |
<p>Input your e-mail and password to login.</p> | |
<div class="input"> | |
<label for="username">E-mail</label> | |
<input type="email" name="username" maxlength="255" required | |
value="<?php echo $_POST['username'] ?>"> | |
</div> | |
<div class="input"> | |
<label for="password">Password</label> | |
<input type="password" name="password" required> | |
</div> | |
<nav> | |
<button type="submit">Login</button> | |
<a href="password_reset.php">Forgot Password?</a> | |
</nav> | |
</form> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
Auth\logout(); | |
header('Location: /login.php'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
$sql = "UPDATE users SET perishable_token = ':token' WHERE email = ':email'"; | |
$message = Auth\password_reset($sql); | |
if( $message ) { | |
echo $message; | |
exit; | |
} | |
?> | |
<form method="POST"> | |
<p>Please enter your email address.</p> | |
<div class="input"> | |
<label for="Email">Email</label> | |
<input type="email" name="email" | |
maxlength="255" required | |
value="<?php echo $_POST['email'] ?>"> | |
</div> | |
<nav> | |
<button type="submit">Send Reset Email</button> | |
<a href="login.php">Return to the Login Screen</a> | |
</nav> | |
</form> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
You requested a password reset. Please click the following link to set | |
a new password. | |
<?php echo $url ?>?token=<?php echo $token ?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
$sql = <<<SQL | |
UPDATE users SET | |
password_hash = SHA1(CONCAT(':password', password_salt)), | |
perishable_token = ':new_token' | |
WHERE perishable_token = ':token' | |
SQL; | |
$message = Auth\update_password($sql); | |
if( $message ) { | |
echo $message; | |
exit; | |
} | |
?> | |
<form method="POST"> | |
<p>Input a new password to reset your password.</p> | |
<div class="input"> | |
<label for="password">New Password</label> | |
<input type="password" name="password" required> | |
</div> | |
<div class="input"> | |
<label for="password_confirmation">Password Confirmation</label> | |
<input type="password" name="password_confirmation" required> | |
</div> | |
<nav> | |
<button type="submit">Update Password</button> | |
</nav> | |
</form> |
its waist of time without sql file
The design of the auth library is purposely to not require a specific schema. You provide the library with statements that are relevant for your schema. I included some example files that work for a schema in an app I worked on. You could mimic that or adjust to match your schema. The basic design is:
Table: users
Fields:
- email - string
- password_hash - string
- perishable_token - string
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Great work Eric!
Do you perhaps have a list of tables and fields needed to use your code?