Custom Token Authentication for Laravel
Tags: laravel, php, api, tutorial
Published: 2019-12-21 15:36:21

With this tutorial I will skip the _“... a recent project I have been working on...”_ bit followed by an explanation of its details and things, I really think everybody is probably busy with some sort of side project and having a 'today I learned moment'. 🙃

---

# 🤔 What will be trying to achieve with this?

The idea is to be able to provide access tokens for users. Integrate with the default Laravel Auth system for API requests. And also log the requests that come through, whether successful or not.

> **Disclaimer at this point in time right now:** I am not fully sure myself if the methods used are 'secure' as I am not a security specialist, duh, I do know however that it works and is a good lesson in extending the Laravel auth sections.

### Things you will need to get this running.

- A LAMP stack on your development machine. I am using MAMP currently to get most of all the features I need to do dev work on my mac, but you are free to use any of the other setups if you got it running already.

- A computer to work on… that's a given. 

- Something like SequelPro or phpMyAdmin for working with your database.

- A fresh install of Laravel, can be 5.8.x (or an old project as this doesn’t touch mush old code base.

- A code editor, currently mine is VSCode. Sublime is also the other go to.

- ⏲ and ☕️

- *An API client tester app*:
    - [Google ARC](https://install.advancedrestclient.com/install)
    - [Postman](https://www.getpostman.com/postman)
    - or just cURL... :)

If you have your dev machine setup already with things, please skip to [Step 3](#step-3-creating-your-base-files-for-the-project)

---

## Step 1: Setup your Stack.

I would suggest that you look at the respective install directions for the choice you choose as I don’t think it is necessary to re-write that and make a mess in the transfer of information.

- [MAMP](https://www.mamp.info/en/mamp/)

- [Laragon](https://laragon.org/docs/install.html)

- [A local tut on setting up for Ubuntu](https://dev.to/andrewbrooks/setting-up-laravel-local-dev-environment-on-ubuntu-51kp)

> I would suggest that you get your PHP instance up and running as well as mysql before installing things like composer and Laravel. This way you going to have the least headaches.

_test the php by running `php --version` from your terminal._

For installing Laravel and getting it running please [follow the official docs](https://laravel.com/docs/5.8). Yes I know it points to 5.8, it has the stuff for setting up a local dev environment. So please use the latest requirements by laravel 6.x or 5.8 depending on your choice.


## Step 2: Install a new Laravel app and DB.

Or. If you already have a project, you can go ahead and open that up and move to [Begin and collect 20000](#step-3-creating-your-base-files-for-the-project).

If you have the Laravel installer on your machine, you can run:

```bash

laravel new token-api-app

```

If you are running composer only:

```bash
composer create-project --prefer-dist laravel/laravel token-api-app
```

Once you have got that running, open up the project in your code editor.

You also will want to create your database at this point.

You can use the default laravel .env settings or keep your current DB name.

```sql
CREATE DATABASE token_api_app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

---

## Step 3: Creating your base files for the project.

Because you will be saving tokens that are generated into the database and also their usage stats, we are going to start by making the migration files and the models.

Open up your terminal if you are using an external one. Or you can open up the integrated terminal in VSCode with `CMD+J`.

3.1 Create your models and migrations for the app

You will now make the the model classes and the migrations using the Laravel `artisan make:model` command. Using this you will be able to create your two main models that will be used in this project, `Token` and `TokenStatistic`.

We are using the `--migration` option on the command to create the migrations for the database at the same time. (you can also use the short version `-m`)

``` bash
php artisan make:model Token --migration
```
```bash
php artisan make:model TokenStatistic --migration
```

What would be the terminal output results you are looking for:
![Terminal output](https://thepracticaldev.s3.amazonaws.com/i/yk9woo79d2wp4npup83q.png)

And the resulting files from a `git status`
![Created Files](https://thepracticaldev.s3.amazonaws.com/i/pjsauwwk7u6rlx8pu81i.png)
3.2 Modify the created model classes.

You will find your two new model classes under the `app` directory.

Once you have those created you will replace the Model code with the following files.

### Model for the `Token` class:
{% gist https://gist.github.com/ReeceM/ee598f735eabc26c459ad34cdc63e406 %}

The Token model has relationships with the users table and also the token statistics table.


### Model code for the `TokenStatistic` class:
{% gist https://gist.github.com/ReeceM/6f397de5e3db36b9229c43055d666d24 %}

3.3 Editing the migration files:

Our next step is to change the automatically filled in schema. Our schema is going to be pretty basic to get the system working, but obviously have enough useable data.

For your `tokens` table, delete what is inside the `up` function, then put the following inside it:

```php
        Schema::create('tokens', function (Blueprint $table) {
            $table->bigIncrements('id');

            $table->unsignedBigInteger('user_id');

            $table->foreign('user_id')
                ->references('id')
                ->on('users');

            $table->string('name');
            $table->string('description')->default('API token');

            $table->string('api_token')->unique()->index();
            $table->integer('limit');

            $table->softDeletes();
            $table->timestamps();
        });
```

Next, we will change the contents of the `token_statistics` table, again you will delete the contents of the `up` function inside the migration file and replace it with the following:

```php
        Schema::create('token_statistics', function (Blueprint $table) {
            $table->unsignedBigInteger('date')->index();

            $table->unsignedBigInteger('token_id')->nullable();
            $table->string('ip_address');
            $table->boolean('success')->default(false);
            $table->json('request');
        });
```

---

## Step 4: Migrating tables. 💽

Great, you have made it this far 😅. But just before you hit continue; please ensure that inside your `.env` file you have your database setup correctly.

If you used the code to create your database from this tutorial, please ensure the env file is name correctly:

e.g.
```env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306 #or 8889 if using MAMP !
DB_DATABASE=token_api_app
DB_USERNAME=root
DB_PASSWORD=root
```

Now we are going to run `php artisan migrate` in our terminals to create the new tables we have just defined.

This will run the migrations for the default migrations that are provided with Laravel, we want this as it has the users table and also stuff for failed jobs from the queue. Once the migrations are completed we can now move onto the next section.

Should look something like below:
![migrating tables](https://thepracticaldev.s3.amazonaws.com/i/6s9bwrwmx8k30rnqpl9y.png)
<figcaption> note, i have an alias for php artisan -> artisan</figcaption>
---

## Step 5: Lets make our extensions.

We are now going to create our class that is going to extend the the Laravel `Illuminate\Auth\EloquentUserProvider`. This is the same class that is used as the user provider for the other Auth mechanisms in the Laravel framework.

Now, you are going to create a new file under the following directory:

> /app/Extensions/TokenUserProvider.php

Open that new file and place the following code in it

```php
<?php

namespace App\Extensions;

use App\Events\Auth\TokenFailed;
use App\Events\Auth\TokenAuthenticated;

use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Str;

class TokenUserProvider extends EloquentUserProvider
{
    use LogsToken;

    /**
     * Retrieve a user by the given credentials.
     *
     * @param  array  $credentials
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByCredentials(array $credentials)
    {
        if (
            empty($credentials) || (count($credentials) === 1 &&
                array_key_exists('password', $credentials))
        ) {
            return;
        }

        // First we will add each credential element to the query as a where clause.
        // Then we can execute the query and, if we found a user, return it in a
        // Eloquent User "model" that will be utilized by the Guard instances.
        $query = $this->newModelQuery();

        foreach ($credentials as $key => $value) {
            if (Str::contains($key, 'password')) {
                continue;
            }

            if (is_array($value) || $value instanceof Arrayable) {
                $query->whereIn($key, $value);
            } else {
                $query->where($key, $value);
            }
        }
        $token = $query->with('user')->first();

        if (!is_null($token)) {
            $this->logToken($token, request());
        } else {
            $this->logFailedToken($token, request());
        }

        return $token->user ?? null;
    }

    /**
     * Gets the structure for the log of the token
     *
     * @param \App\Models\Token $token
     * @param \Illuminate\Http\Request $request
     * @return void
     */
    protected function logToken($token, $request): void
    {
        event(new TokenAuthenticated($request->ip(), $token, [
            'parameters' => $this->cleanData($request->toArray()),
            'headers' => [
                'user-agent' => $request->userAgent(),
            ],
        ]));
    }

    /**
     * Logs the data for a failed query.
     *
     * @param \App\Models\Token|null $token
     * @param \Illuminate\Http\Request $request
     * @return void
     */
    protected function logFailedToken($token, $request): void
    {
        event(new TokenFailed($request->ip(), $token, [
            'parameters' => $this->cleanData($request->toArray()),
            'headers' => collect($request->headers)->toArray(),
        ]));
    }

    /**
     * Filter out the data to get only the desired values.
     *
     * @param array $parameters
     * @param array $skip
     * @return array
     */
    protected function cleanData($parameters, $skip = ['api_token']): array
    {
        return array_filter($parameters, function ($key) use ($skip) {
            if (array_search($key, $skip) !== false) {
                return false;
            }
            return true;
        }, ARRAY_FILTER_USE_KEY);
    }
}
```

Lets have a look at the file that we just created. Firstly, we are extending a default Laravel class that handles the normal user resolution when we are using the api auth or any of the other methods, e.g. 'remember_me' tokens.

We are only going to be overriding one of the functions from the parent class in this tutorial as we will do some more with it in future ones.

Our first function then is `retrieveByCredentials(array $credentials)`, there is nothing special about this except two things, we added logging for when the token is successful or fails.

Next we then return the `user` relationship from the token, and not the model/token as the normal user provider does.

The next two functions handle the login of the data, this can be customized to what you would like to be in there. Especially if you made modifications to the migration file for the tables structure.

The last function is just a simple one that cleans up any data that is in the request based on the keys. This is useful if you want to strip out the api_token that was sent and also to be able to remove any files that are uploaded as they cannot be serialized when the events are dispatched for logging.

---

## Step 6: Registering the services.

We are going to make a custom service provider for this feature as it gives a nice way for you to make adjustments and extend it further, knowing that it is specifically this Service Provider that handles things.

Run the following command from the terminal:

```bash
php artisan make:provider TokenAuthProvider
```

This will now give you your Service Provider stub default. 

We will want to import the following classes:

```php
// ... other use statements

use App\Extensions\TokenUserProvider;
use Illuminate\Support\Facades\Auth;

// ...
```

Now that we have those, we can extend our Auth providers. Replace the boot method with the following piece of code:

```php
   /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        Auth::provider('token-driver', function ($app, array $config) {
            // Return an instance of Illuminate\Contracts\Auth\UserProvider...
            return new TokenUserProvider($app['hash'], $config['model']);
        });
    }
```

Now this uses the `Auth` provider method to extend the list of provides that Laravel looks for when you define a provider in our `config/auth.php` file (more on this just now).

We are passing all the parameters from the closure directly to our new class we created in step 5.


Now, having this here is all very good and well, if Laravel's IoC new about it. As it currently is, none of this will do anything until we setup the config files.

First file we are going to edit is the `config/app.php` file, you will be adding `App\Providers\TokenAuthProvider::class` under the `providers` key:

```php
         // App\Providers\BroadcastServiceProvider::class,
         App\Providers\EventServiceProvider::class,
         App\Providers\RouteServiceProvider::class,
+        App\Providers\TokenAuthProvider::class,
 
     ],
```

Next, you are going to edit the `config/auth.php` file. 

Under the section `guards` change it to look like this:

```php
        'api' => [
            'driver' => 'token',
            'provider' => 'tokens',
            'hash' => true,
        ],

        'token' => [
            'driver' => 'token',
            'provider' => 'tokens',
            'hash' => true,
        ]
```
Explanation: Setting the provider for both of the guards means that Laravel will look at the providers list for one called `tokens`, that is where we define the driver that we have created as well as what the model is that we are looking at for the `api_token` column.

Then, add the following under the `providers`:

```php
        'tokens' => [
            'driver' => 'token-driver',
            'model' => \App\Token::class,
            // 'input_key' => '',
            // 'storage_key' => '',
        ],
```

The 'tokens.driver' value should match the name that you give when extending the Auth facade in the service provider.

This would complete the configuration needed to let Laravel know what is cutting with the modified auth system.

> **IMPORTANT** Word of caution here with changing the `api` guard to use the tokens provider, you will have to use the proper tokens generated on the tokens table, and not the normal way that Laravel looks for an `api_token` column on the `users` table.

---

## Step 7: Nearly there, lets make some useful additions.

We now really need a way to create tokens. Well, for this tutorial, we are going to use a console command to create the tokens we need. In the follow up articles, we are going to build a UI for managing the tokens and creating them.

To create our command we will run `php artisan make:command GenerateApiToken`.

In the created command under `app/Console/Commands` replace everything with the following into the new file:

```php
<?php

namespace App\Console\Commands;

use App\Token;
use Illuminate\Console\Command;
use Illuminate\Support\Str;

class GenerateApiToken extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'make:token  {name : name the token}
                                        {user : the user id of token owner}
                                        {description? : describe the token}
                                        {l? : the apis limit / min}
                            ';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Makes a new API Token';

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $this->comment('Creating a new Api Token');
        $token = (string)Str::uuid();

        Token::create([
            'user_id'       => $this->argument('user'),
            'name'          => $this->argument('name'),
            'description'   => $this->argument('description') ?? '',
            'api_token'     => hash('sha256', $token),
            'limit'         => $this->argument('l') ?? 60,
        ]);

        $this->info('The Token has been made');
        $this->line('Token is: '.$token);
        $this->error('This is the only time you will see this token, so keep it');
    }
}
```

Now this command is very simple and would require you to know the users id that the token must link to, but for our purpose now it is perfectly fine.

When we use this command we will only need to provide the user id and a name. If we want we can add a description and a rate limit (this is for future updates)

Now that we have a command to run in the console as the follows: 

![Create new API token from console](https://thepracticaldev.s3.amazonaws.com/i/euds1be9a1q6xzxqfilp.png)

So, we will need to make sure we have a user to create our token for, so fire up `php artisan tinker`

Inside there we are going to run the following code for a factory `factory(\App\User::class)->create()`

This will have the following type of result:
![create new user](https://thepracticaldev.s3.amazonaws.com/i/8xxf7abp5bi72bgcbojc.png)

Then we can go ahead and run our command to make a new token:
![Create token](https://thepracticaldev.s3.amazonaws.com/i/ay3bfhvkvdymjudvw1lj.png)
<figcaption>Result from the `make:token` command</figcaption>

Then in the database you will see something similar to this:
![Token in the database](https://thepracticaldev.s3.amazonaws.com/i/553hekaog0yly907k4ay.png)
<figcaption>The token and its hash in the db</figcaption>

---

## Step 9: create the events and listeners.

When logging your token success or failure stats and the headers for each request, we use events and the Laravel queue system to reduce the amount of things done in a request.

If you were to make any requests before creating your event listeners and events you will get a 500 error as it can't find any of the events or listeners.

First file you will want to create is the base class the events will extend as it gives a nice way to have separate event names, but a base class constructor.

Create a file under `app/Events/Auth` called TokenEvent.php

You will probably have to create that directory as it might not exist.

Inside that file you will place the following code that gets a token, request and ip as its constructor arguments.

```
<?php

namespace App\Events\Auth;

class TokenEvent 
{
    /**
     * The authenticated token.
     *
     * @var \App\Models\Token
     */
    public $token;

    /**
     * The data to persist to mem.
     *
     * @var array
     */
    public $toLog = [];

    /**
     * The IP address of the client
     * @var string $ip
     */
    public $ip;

    /**
     * Create a new event instance.
     *
     * @param  string  $ip
     * @param  \App\Models\Token $token
     * @param  array $toLog
     * @return void
     */
    public function __construct($ip, $token, $toLog)
    {
        $this->ip    = $ip;
        $this->token = $token;
        $this->toLog = $toLog;
    }
}
```

In your `EventServiceProvider` add the following to the listeners array:

```php

        \App\Events\Auth\TokenAuthenticated::class => [
            \App\Listeners\Auth\AuthenticatedTokenLog::class,
        ],
        \App\Events\Auth\TokenFailed::class => [
            \App\Listeners\Auth\FailedTokenLog::class,
        ],
```

then run `php artisan event:generate` to create the files automatically.

In each of these files, you will be basically replacing everything.

In the file `app/Events/Auth/TokenAuthenticated.php` replace everything with:

```php
<?php
// app/Events/Auth/TokenAuthenticated.php

namespace App\Events\Auth;

use Illuminate\Queue\SerializesModels;

class TokenAuthenticated extends TokenEvent
{
    use SerializesModels;

}
```

The same again for the event file `app/Events/Auth/TokenFailed.php`, replace everything with:

```php
<?php
// app/Events/Auth/TokenFailed.php

namespace App\Events\Auth;

use Illuminate\Queue\SerializesModels;

class TokenFailed extends TokenEvent
{
    use SerializesModels;

}
```

For our event listeners now, change the file `app/Listeners/Auth/AuthenticatedTokenLog.php`

```php
<?php
// app/Listeners/Auth/AuthenticatedTokenLog.php

namespace App\Listeners\Auth;

use App\Events\Auth\TokenAuthenticated;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

class AuthenticatedTokenLog implements ShouldQueue
{
    /**
     * Handle the event.
     *
     * @param  TokenAuthenticated  $event
     * @return void
     */
    public function handle(TokenAuthenticated $event)
    {
        $event->token->tokenStatistic()->create([
            'date'          => time(),
            'success'       => true,
            'ip_address'    => $event->ip,
            'request'       => $event->toLog,
        ]);
    }
}
```

Then for our file `app/Listeners/Auth/FailedTokenLog.php`:

```php
<?php
// app/Listeners/Auth/FailedTokenLog.php

namespace App\Listeners\Auth;

use App\Events\Auth\TokenFailed;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

class FailedTokenLog implements ShouldQueue
{
    /**
     * Handle the event.
     *
     * @param  TokenFailed  $event
     * @return void
     */
    public function handle(TokenFailed $event)
    {
        $event->token->tokenStatistic()->create([
            'date'          => time(),
            'success'       => false,
            'ip_address'    => $event->ip,
            'request'       => $event->toLog,
        ]);
    }
}
```

Both of the event listeners are very simple in that they just take the token that his based to the event and log it against a `TokenStatistic::class` relation.

For example then a use would be as follows: 
```php
event(new TokenAuthenticated($request->ip(), $token, [
    'parameters' => $this->cleanData($request->toArray()),
    'headers' => [
        'user-agent' => $request->userAgent(),
    ],
]));
```

A successful request would have the following in the table:
![token log](https://thepracticaldev.s3.amazonaws.com/i/wlukjbpjic2d5pzultou.png)

# 🏁 END: The really exciting part where we see things happen ‼️
---

To get our local dev server running, just type in to the terminal the following `php artisan serve`. This will get the php test server running. 

Now Laravel comes with default api url route `/api/user` that returns your currently authenticated user.

Open up your API testing app of choice and type the following into the url input:

```
http://localhost:8000/api/user
```

Well, that is going to be quite depressing immediately as you will get a *500* error, similar to this:
![Error from request](https://thepracticaldev.s3.amazonaws.com/i/zc7l1k35noyaluc3gsbo.png)

Well, now add your token that you got earlier under the following section:
![adding the token](https://thepracticaldev.s3.amazonaws.com/i/p4623az4dvf6680aeeg2.png)

Now, press, don't hit the send button.

If all is well in the world of computers you should have a JSON response in the response section of your API tester similar to the below image:
![response success](https://thepracticaldev.s3.amazonaws.com/i/t165yngfe58ivx06iylz.png)

We can also do the following with a cURL command from the terminal

Running `curl "http://localhost:8000/api/user"` You should see a whole load of info come back that means nothing.

Now run either of the following bits of code:

```bash
curl -X GET \
  http://localhost:8000/api/user \
  -H 'Authorization: Bearer YOUR_TOKEN_HERE'
```

or 

```bash
curl "http://localhost:8000/api/user?api_token=YOUR_TOKEN_HERE"
```

You will then see a JSON response similar to the Postman/ARC one:

```json
{"id":1,"name":"Valerie Huels DDS","email":"herman.orion@example.com","email_verified_at":"2019-12-20 23:07:17","created_at":"2019-12-20 23:07:17","updated_at":"2019-12-20 23:07:17"}
```

----

# Conclusion

So congratulations, you have made it this far and put up with my strange style of explaining things or maybe you are like me and skipped all the way to the end in search of a package or something. 

Well you can have a demo application that has all of this wonderfully functioning code running in it to pull a TL;DR on.

{% github ReeceM/laravel-token-demo no-readme %}

Thank you very much for putting up with my first post here and tutorial on Laravel. Please leave any suggestions in the comments or PRs on the repo if you feel so inclined.

There will be some follow up articles for this, so please keep an eye out for them.

--- 

I would really appreciate it if you enjoyed this article and feel generous to buying a coffee or something else. (I am trying to create a new application for the php community)

<a href="https://www.buymeacoffee.com/ReeceM" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>


Reece - o_0