Skip to main content Accessibility Feedback

How to authenticate users with PHP and flat JSON files

Yesterday, I wrote about how to build dynamic web apps using a static site generator and a tiny PHP file.

In it, I noted…

I’m a huge fan of flat JSON files. In my experience, they’ve proven far more resilient, maintainable, and performant than using a proper database.

One reader asked me how I setup authentication with this technique, so today I wanted to share how that works. Let’s dig in!

Storing user data

User data lives in a private directory that can only be accessed on the server and cannot be reached with an HTTP request. You can configure this with an .htaccess file in apache.

I use the file_put_contents() function to write data to JSON files, and file_get_contents() to retrieve it. I also use the json_decode() and json_encode() functions to convert strings to JSON and back again.

Creating a new user

There was a time where you needed a library to hash and validate passwords with PHP. But the language now has built-in methods to do that: password_hash() and password_verify().

They actually use a stronger hashing algorithm than WordPress does.

When I create a new user, I use the password_hash() method to hash their provided password. I create an object with their hashed password, convert it to a string, and save it a JSON file with their username or email address as the file name.

It’s easy and fast, and way less confusing than writing a SQL query!

<?php 

// Create the user
$pw = 'the user provided password';
$user = array(
	'pw' => password_hash($pw, PASSWORD_DEFAULT)
);

// Save user to a flat JSON file
file_put_contents('/private-directory/users/AwesomeUsername.json', json_encode($user));

Logging in

To log a user in, I open the JSON file with their username and convert it to JSON. Then, I use the password_verify() function to check if the provided password matches the hashed version stored in the JSON file.

If the password isn’t correct, I can throw an error and let the user know.

<?php

$user = json_decode(file_get_contents('/private-directory/users/AwesomeUsername.json'));
$is_valid = password_verify($provided_pw, $user['pw']);

if (!$is_valid) {
	// throw an error
}

Storing sessions

If the password is valid, I generate a random string to act as a session ID, and set a cookie with that string as its value.

I set my sessions to last two weeks, but you can of course use any duration you want. I include the expiration date as part of the $token string. This makes clearing old sessions easier, as we’ll look at shortly.

For security reasons, I set the cookie so that it can only be read by the server. JavaScript cannot access it.

<?php
// Create a token details
$expires = time() + (60 * 60 * 24 * 14);
$token = '_' . $expires . '_' . bin2hex(openssl_random_pseudo_bytes(60));

// Set cookie
$cookie = setcookie('session_token', $token, $expires, '/', '', true, true);

After the cookie is set, I save a JSON file named after the $token with the user’s username and the expiration date.

<?php

$token_val = array(
	'user' => $username,
	'expires' => $expires,
);
file_put_contents('/private-directory/tokens/' . $token . '.json', json_encode($token_val));

Authenticating sessions

Whenever a page loads, I can now use the session_token cookie to check if the user is logged in and get their details.

I get the value of the session_token cookie, and use it to get the matching token file. That file gives me access to the current user’s username.

<?php

// Check for a session cookie
$token_id = isset($_COOKIE['session_token']) ? $_COOKIE['session_token'] : null;
if (!$token_id) {
	// the user is not logged in...
}

// Get the token details
$token = get_file_contents('/private-directory/tokens/' . $token_id . '.json');
if (empty($token)) {
	// the user is not logged in...
	// Remove the cookie, too
}

// Get the username
$username = $token['user'];

Once I have this info, I can do anything else I need to check in my app.

Sometimes just knowing they’re logged in is enough. Other time, I may want to show conditional content based on the user, which may require additional checks or API calls.

Cleaning up old sessions

My session_token cookies automatically expire, but the corresponding JSON files do not.

I have a cron task set on my server to automatically delete expired token JSON files each night. It is, once again, a PHP file that runs some tasks.

<?php

// Loop through tokens
foreach (new DirectoryIterator(dirname(__FILE__) . '/tokens') as $fileInfo) {
	
	// If a . file, skip
	if ($fileInfo->isDot()) continue;

	// Get expiration date
	$expires = intval(explode('_', $fileInfo->getFilename())[1]);

	// If token/key has expired, remove it
	if ($expires < time()) {
		unlink($fileInfo->getPathname());
	}
	
}