Secure RESTful APIs with JWT

3rd Jan 2018

Projects

RESTPHPJSON

RESTful APIs are stateless, this means that if access to a resource requires authentication then the client must authenticate with each request. This can cause significant slowdowns depending on the system and architecture if you were to have the client send a username and password with each request and having to verify each time.

One good alternative is to make use of JWT, JSON Web Tokens. The idea behind JWT is that the user authenticates with username/password once at the start of the session and once that is verified the server responds with a token that represents the authenticated user and is generated from a secret server-side key.

The user must then send all subsequent requests with that token which can be decoded server-side and verified a lot faster than most password verification techniques, especially if the hash is stored in a database. Another upside is that the client, beyond that initial authentication, never again has to send their plaintext password for that session, decreasing the chances of interception.

JWT libraries have been written for pretty much every language you could hope for and are incredibly easy to integrate into the system. I recently have been working on a cloud-synced notes application that makes calls to an API to upload/download updates to the notes, and in doing so the user must be authenticated.

Below is the PHP code I'm using on the server to start the session from the user's start_session call:

require_once 'jwt_helper.php';

function startSession($db, $data) {
    if (!isset($data['username']) || !isset($data['password'])) {
        header("HTTP/1.1 401 Authorisation Required");
        $response = array();
        $response['success'] = false;
        $response['message'] = 'No username/password specified';
        echo json_encode($response);
        die();
    }

    $username = $data['username'];
    $password = $data['password'];

    $sql = 'SELECT id, username, password
            FROM users
            WHERE username = :username';
    $sth = $db->prepare($sql);
    $sth->execute(array(':username' => $username));
    $results = $sth->fetchAll(PDO::FETCH_ASSOC);
    if (count($results) == 0) {
        header("HTTP/1.1 401 Authorisation Required");
        $response = array();
        $response['success'] = false;
        $response['message'] = 'Invalid username and/or password';
        echo json_encode($response);
        die();
    } 

    $db_pass = $results[0]['password'];

    if (!password_verify($password, $db_pass)) {
        header("HTTP/1.1 401 Authorisation Required");
        $response = array();
        $response['success'] = false;
        $response['message'] = 'Invalid username and/or password';
        echo json_encode($response);
        die();
    }

    $id = $results[0]['id'];
    $token = array();
    $token['id'] = $id;

    echo JWT::encode($token, SERVER_KEY);
}

The code works like so:

  • Check that username and password was supplied, if not then send 401 error
  • Grab hash and user id for the given user from the databse, if not found then send 401 error
  • Verify password with hash, if doesn't match then send 401 error
  • Create JWT from user id and secret server key
  • Send token as response

I'm using the PHP-JWT library developed by the people over at firebase.