I write ebooks. Some of them I publish. I like to improve my stories by getting feedback from beta readers before sending them to the publisher. Sharing on a web site is an easy answer, but obviously I don't want to share with the entire world.
Previously, I relied on a simple Apache .htaccess file to restrict a directory to only those who knew the shared password. Then, Nginx came along with it's “we don't do distributed configuration” attitude.
Rather than leaving my files flapping in the breeze and hoping no one would guess the URL, I quickly looked for an alternative solution. I found it with PHP Sessions.
Even though SDF is using Apache again, I still think this approach of securing files has merit.
I didn't just sit down one day and pull PHP Sessions out of my backside. I did what anybody would do and took to the internet for examples. I found mine on Stack Overflow.
https://stackoverflow.com/questions/1243150/php-sessions-to-authenticate-user-on-login-form#1244097
This gave me the framework for what was possible. What I came up with for my solution, I will detail below.
For the impatient, I will first drop the code on you and explain later.
This is where users get sent to authenticate and it's the first page they see.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Secure Files</title> <style> body { background: #333333; color: #CCCCCC; } fieldset { line-height: 150%; margin-left: auto; margin-right: auto; margin-top: 30vh; text-align: center; width: 12em; } input { background: #CCCCCC; color: #000000; </style> </head> <body> <form action="login.php" method="post"> <fieldset> <legend>Secure Files</legend> Username:<br> <input name="username" type="text"><br> Password:<br> <input name="password" type="password"><br> <input type="submit" value="Log In"> </fieldset> </form> </body> </html>
The important part here is the stuff between the BODY tags. Most everything else is styling to make it look good on desktop or mobile, and to give it a moody, dark color scheme.
The user is presented with an HTML form that asks for credentials. There's no encryption or hashing of the name and password by this form, so it's advisable to use SSL for the connection if you're concerned with clear text over the wire.
Once the user clicks/taps Log In, control is passed to a PHP script to verify the credentials.
No, this is not a repeat of login.html, this is where validation occurs and the session is started.
<?php // Username and password in sha1 encoded hashes. $username = '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'; $password = '62cdb7020ff920e5aa642c3d4066950dd1f01f4d'; session_start(); // Check if credentials were passed. Redirect to login form if not. if (isset($_POST['username']) && isset($_POST['password'])) { // Attempt to validate against the stored hashes. if (sha1($_POST['username']) == $username && sha1($_POST['password']) == $password) { $_SESSION['authenticated'] = true; header('Location: index.php'); } else { sleep(3); header('Location: login.html'); } } else { header('Location: login.html'); } ?>
This short little snippet of PHP is simple comparing the credentials passed via post variables with the credential stored at the top as a SHA1 hash. If things match, a session variable of authenticated = true
is set.
Is this secure? No.
SHA1 has been compromised. Setting a simple and easily guessed session variable is pretty lame as well. But, PHP does not have functions for SHA256 and up. And, I'm not protecting top secret nuclear launch codes here. So, is it good enough for short term sharing of a few files? Probably.
You can get your username and password hashes by using the sha1
command-line tool.
sha1 -s foo SHA1 ("foo") = 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33
If you've ever read a C++ book or a Unix tutorial, I'll bet you can guess what the password is.
Logging out is easy. This is a session variable, so it goes away when the user closes their browser. If you want to offer a “logout” button, just use this script.
<?php // Cancel authentication. session_start(); unset($_SESSION['authenticated']); header('Location: login.html'); ?>
This bit of PHP removes the session variable and redirects the user back to the HTML login form.
Now that we can set a PHP session variable, we need to be diligent about checking for it. Since it's a PHP session variable, it makes sense that it's only accessible from PHP code. But, PHP can be slipped into HTML files with ease. Just put it between the <?php
and ?>
tags as show in all the examples above.
To protect an HTML file, simply slip this in at the top.
<?php // Check if autheticated and redirect to login page if not. session_start(); if(!isset($_SESSION['authenticated'])) { http_response_code(403); header("Content-type: text/plain"); header("Cache-Control: no-store, no-cache"); echo "403: Not Authorized"; exit(); } ?>
This will leave the user staring at a blank page with a 403: Not Authorized error at the top. If you wanted to be nice, you could do an HTTP redirect to the login page.
Good, you're paying attention. I mentioned from the start that my intention was secure ebooks from prying eyes. So far I've only managed to secure index.html at best.
For the rest, I rely on a download.php script that can read the contents of any file from any directory it has permission to read from. This includes directories outside of the ~/html hierarchy. All I have to do is add the snippet of PHP code that checks for a valid session and the download.php script becomes password protected as well. And, since it's the only way I've provided to gain access to a file outside of ~/html, files can't be downloaded by a direct link.
You can find it here: A Simple PHP/SQLite Download Counter
This is just a simple example of protecting your files. There is a lot of room for improvement, but in terms of getting the job done quickly and easily, it's a good start.
Concerning PHP Session Security: https://stackoverflow.com/questions/10165424/how-secure-are-php-sessions#10165602