Hashing API Tokens for improved security cover image

Hashing API Tokens for improved security

Rick Kuilman • May 6, 2019

laravel security

As of Laravel 5.8 you can easely hash the api tokens of users when using the token driver. Today I will explain why you should hash your api token and how to implement it in Laravel

API tokens

When you have setup Laravel's default API token driver your users will be given an API token after the user authenticates itself to the server. Once they retrieve the token they will be able to identify themselfes as that user in a stateless way. Instead of the server checking the username and password combination, it tries to match the supplied token with the api_token column on the users table.

App\User {
    id: 2,
    name: "David",
    email: "[email protected]",
    email_verified_at: "2019-05-06 13:24:17",
    api_token: "hVF4CVDlbuUg18MmRZBA4pDkzuXZi9Rzm5wYvSPtxvF8qa8CK9GiJqMXdAMv",
    updated_at: "2019-05-06 13:24:17",
    created_at: "2019-05-06 13:24:17",
}

Therefore the API token essentially becomes your password, which is not something we want to be storing as plain text in the database. When a (part of a) database is compromized, the attacker is able to login to your application without knowing the users's password.

Hashing

This problem can solved by hashing the API token before it's stored in the database, so let's to that. First, enable hashing on the token driver by setting hash to true.

// config/auth.php
'api' => [
    'driver' => 'token',
    'provider' => 'users',
    'hash' => true,
],

The API tokens will now first be hashed before it is validated against the database value. This is done in the TokenGuard as part of the user() method. It simply hashes the token supplied via the request if it's set.

// Illuminate\Auth\TokenGuard.php:85

$token = $this->getTokenForRequest();
[...]
$user = $this->provider->retrieveByCredentials([
    $this->storageKey => $this->hash ? hash('sha256', $token) : $token,
]);

If we take the API token of David and run it through the hash function we get a completely different key, which will not match with the API token anymore:

>>> hash('sha256', 'hVF4CVDlbuUg18MmRZBA4pDkzuXZi9Rzm5wYvSPtxvF8qa8CK9GiJqMXdAMv')
=> "963ff4843e6043062fd84fc8c52321e8fd2fc0e91edbf66bc92a0da68a0fbff0"

In order to make the login function working again, we have to run the hash function once on all the users. This can be done quickly by running the following command in php artisan tinker:

User::all()->each(function ($user) {
    $user->update(['api_token' => hash('sha256', $user->api_token)]);
});

From now on the user's API token is safely stored away in the database.

Generating hashed tokens

Instead of returning the API token of a user when logging in, the token must be regenerated each time a users logs in or refreshes/retrieves it through the application. Only then, one time, the API token is returned to the user and is to be used for further authorization.

An example of how this can be done is provided by the Laravel documentation:

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Str;
use Illuminate\Http\Request;

class ApiTokenController extends Controller
{
    /**
     * Update the authenticated user's API token.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function update(Request $request)
    {
        $token = Str::random(60);

        $request->user()->forceFill([
            'api_token' => hash('sha256', $token),
        ])->save();

        return ['token' => $token];
    }
}