PHP and JWT Tutorial Make a Two-Factor Authentication System

The need for data security is at an all time high given the many vulnerabilities in web applications in the past decade. Let me make that easy for you to comprehend, in 2013 alone, 740 million online records were exposed, 552 million user identities were exposed through breaches. And a year before that, 117 million LinkedIn email password combinations were stolen and has been posted on the Internet.

Most users are not aware of this and now, it is up to us developers to save the day yet again. Without much ado, let us dive into the tutorial.

Application Logic

  • User logs into account using username/email and password combination.
  • Validate user and if combination exists, generate a JWT (JSON Web Token) that contains expiration and send to user’s email.
  • User clicks on link in email, if JWT is valid, log user in, else return to login page.

File Structure

File Structure

2fa
|--index.php
|--handler.php
|--inc
| |--2fa.php

Getting Started

Right before we get started, make sure you have composer installed, navigate to the inc directory and then run this command in your terminal:

php-jwt installation via terminal

composer require firebase/php-jwt

Building The Backend

Now, let’s start writing in our php files. First we need to include an instance of JWT library that was initially downloaded into our application. To do so, open the 2fa.php file inside the inc directory in your favorite code editor and include below code

inc/2fa.php

<?php

// require the autoloader that composer created
require_once 'vendor/autoload.php';

// use the firebase JWT Library previous installed using composer
use \Firebase\JWT\JWT;

// create a class for easy code structure and use
class userAuth {

}

?>

For the purpose of this tutorial, we will use a dummy user account, so we don’t have to create a database connection. You can always edit the code to include a database connection.

So, our dummy user will be a private array that is a part of our userAuth class. Creating our user like so:

inc/2fa.php — Dummy User Account

// create a dummy user for the tutorial
private $user = array(
"id" => 768,
"email" => "code.wolf@ghostffco.de",
"password" => "ThisIsAPassword"
);

Now, let’s include a signing key for our JWT token based Authentication system. It should not change that often because, any token generated by one signing key cannot be used by another. Also, an empty id and emailvariable that will hold the user id if the user exists in our database. Include our signing key and id variable:

inc/2fa.php — Class Variables And Constants

// create an empty id variable to hold the user id
private $id;

// key for JWT signing and validation, shouldn't be changed
private $key = "secretSignKey";

Awesome, so good so far. Now, let us start adding methods to our class that will help us validate user, generate JWT, send email and validate user JWT from email.

To validate a user, we will use the validUser method. It checks if the user exists in our user array, returns true if user exists or false if not:

inc/2fa.php — Valid User Method

// Checks if the user exists in the database
private function validUser($email, $password) {
// doing a user exists check with minimal to no validation on user input
if ($email == $this->user['email'] && $password == $this->user['password']) {
// Add user email and id to empty email and id variable and return true
$this->id = $this->user['id'];

$this->email = $this->user['email'];

return true;
} else {

return false;
}
}

Now to the method that will generate our JSON Web Token. If you don’t know what JWT is, I suggest you take a look at this link. What our method will do, is take the values of the id and email variables that has been set up once the user account exists in our application and create an array of key value pair and in turn use the encode method of the JWT library to create a token with our secret key.

That was a mouthful so less words, more codes, here it is:

inc/2fa.php — genJWT Method

/ Generates and signs a JWT for User
private function genJWT() {
// Make an array for the JWT Payload
$payload = array(
"id" => $this->id,
"email" => $this->email,
"exp" => time() + (60 * 60)
);

// encode the payload using our secretkey and return the token
return JWT::encode($payload, $this->key);
}

The "exp" => time() + (60 * 60) key value pair in the payload tells our application when the token will expire, in this case, that is an hour from when it was created.

If you have noticed, all these previous methods are all private, it’s finally time to create our first public methods that puts the previous ones together. We want to send an email to the user with the token as a link for verification:

inc/2fa.php — mailUser Method

// sends signed token in email to user if the user exists
public function mailUser($email, $password) {
// check if the user exists
if ($this->validUser($email, $password)) {
// generate JSON web token and store as variable
$token = $this->genJWT();
// create email
$message = 'http://ghostffco.de/index.php?token='.$token;

$mail = mail($this->email,"Authentication From ghostffco.de",$message);

// if the email is successful, send feedback to user
if ($mail) {

return 'We Just Sent You An Email With Your Login Link';
} else {

return 'An Error Occurred While Sending The Email';
}
} else {

return 'We Couldn\'t Find You In Our Database. Maybe Wrong Email/Password Combination';
}

}

The above method, checks if the user exists, using the validUser method. If true, generates a token and sends it to the user’s email. Else, it returns an error message.

Let’s create the code that will actually handle the user input and pass it down to our class methods. Let’s put our handler.php file to use:

handler.php

<?php

// require our class file
require_once 'inc/2fa.php';

// create a class instance
$auth = new userAuth();

// check if form was submitted
if (!empty($_POST)) {
$email = $_POST['email'];
$pswd = $_POST['password'];

//.......
// do your data santization and validation here
// Like check if values are empty or contain invalid characters
//.......

// If everything is valid, send the email
$msg = $auth->mailUser($email, $pswd);

}

?>

Given the above code, we can be able to sanitize user input and pass our email and password variables to the user…. but we can’t see any error or success messages from our class. That takes us to our index.php file. Let’s include our handler file and then echo $msg if any:

index.php — Error and success message handler

<?php

include 'handler.php';

if (isset($msg)) {
echo $msg;
}

?>

Now, if our $msg variable is set, we can see the error messages.

To test it out so far, upload to a live server or if your development server can send an email, go ahead and use postman. It is a great app for sending different requests to an API server.

Let’s complete our code for token validation. When the user click on the link. We need to handle the token. Back to our 2fa.php file, let’s add another method to handle this

inc/2fa.php — User token management

// Validates a given JWT from the user email
private function validJWT($token) {
$res = array(false, '');
// using a try and catch to verify
try {
//$decoded = JWT::decode($token, $this->key, array('HS256'));
$decoded = JWT::decode($token, $this->key, array('HS256'));
} catch (Exception $e) {
return $res;
}
$res['0'] = true;
$res['1'] = (array) $decoded;

return $res;
}


public function validMail($token) {
// checks if an email is valid
$tokenVal = $this->validJWT($token);

// check if the first array value is true
if ($tokenVal['0']) {
// create user session and all that good stuff
return "Everything went well, time to serve you what you need.";
} else {
return "There was an error validating your email. Send another link";
}
}

Let’s explain the code

our first method validJWT() takes one argument, which is our token, validates it and returns it. We used a try and catch for Exception handling, because else, if an error occurs JWT prints out our signing key to the user and we don’t want that to happen.

The second method validMail() uses the validJWT method, checks if the returned array first value is set to true and the proceeds to log user in or return a success message.

To make use of these methods in our class file, let’s edit and add a few more lines of code to our handler.php file that gets token from the url if it is set (when the user clicks the link in the email). Our handler.php file should look like this:

handler.php — Updated code to get token from URL

<?php

require_once 'inc/2fa.php';

$auth = new userAuth();

if (!empty($_POST)) {
$email = $_POST['email'];
$pswd = $_POST['password'];
//.......
// do your data santization and validation here
// Like check if values are empty or contain invalid characters
//.......

// If everything is valid, send the email
$msg = $auth->mailUser($email, $pswd);

} else if (!empty($_GET['token'])) {
$token = $_GET['token'];
//.......
// do your data santization here
//.......

// pass along to be validated
$msg = $auth->validMail($token);
}

?>

In my local machine, everything worked out well and this was what I got.

Summary

This post focused more on the use of JWT in a PHP application than user authentication. It is a great library and its use is limitless. You can use it for authenticating the API endpoints in your applications.

NOTE:

Never store any sensitive information in a JWT token. The payloads are simply accessible from the jwt.io webpage. The signing key are just for token validation purposes, not authentication.

Having said that, do you have any projects that uses JWT or do you plan to use it in any of your upcoming projects? Did you successful recreate or integrate these code samples in any of your projects? Let’s talk about it in the comment section below.

Source via: ghostffco.de

Suggest

Learning PHP 7: From the Basics to Application Development

The Complete PHP 7 Guide for Web Developers

Up to Speed with PHP 7

Learn PHP 7 This Way to Rise Above & Beyond Competion!

PHP MySQL Database Connections

Like what you read? Give Rogers Kristen a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.