Skip to content

Instantly share code, notes, and snippets.

@eric1234
Last active September 21, 2019 14:53
Show Gist options
  • Save eric1234/1887174 to your computer and use it in GitHub Desktop.
Save eric1234/1887174 to your computer and use it in GitHub Desktop.
A authentication library that provides a login and forgot password function.
<?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';
}
<?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>
<?php
Auth\logout();
header('Location: /login.php');
<?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>
You requested a password reset. Please click the following link to set
a new password.
<?php echo $url ?>?token=<?php echo $token ?>
<?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>
@LeifLinder-zz
Copy link

Let me give this a try. Thanks man.
Leif OSLO

@fritzcoetzee
Copy link

Great work Eric!

Do you perhaps have a list of tables and fields needed to use your code?

@keaikitse
Copy link

its waist of time without sql file

@eric1234
Copy link
Author

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