Authentication

Fourth Lesson in the Vue-Todo Series

Posted on July 27, 2016 in Laravel PHP Framework, Vue-Todo Series, vue.js

Adding the required packages

First, let's pull in the two required packages and get them set up before we continue.

tymondesigns / jwt-auth

Now open your composer.json file and add the following to the end of your "require" section:

"tymon/jwt-auth": "0.5.*"

You will now want to run composer update from your terminal. Once it has finished downloading the required packages open config/app.php and add the following to the bottom of the 'providers' section:

Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,

Also, add the following to the 'aliases' section:

'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,

This alias allows us to access the functions within the JWT package easily from within our classes. I will show you this shortly. From the command line run the following

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"

The above command, like with Dingo API, creates a new file within our config folder called jwt.php have a quick read through this file, but we will be leaving it as the default. If you now run the following command:

php artisan jwt:generate

This creates a new key which is used to sign the generated token. Never share this with anyone. If you do create a new one. You will want to move the generated key from jwt.php and put it into your .env file.

The last thing to do for the moment is to add the route middleware we will be using from the JWT package. Open your app/Http/Kernel.php file and replace the contents of protected $routeMiddleware with the following:

protected $routeMiddleware = [
    'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'jwt.auth' => \Tymon\JWTAuth\Middleware\GetUserFromToken::class,
    'jwt.refresh' => \Tymon\JWTAuth\Middleware\RefreshToken::class,
]

We have removed the built in auth middleware as we won't be using this and add in the JWT middleware. So what is middleware? Depending on how it is set up it can run before any route is served or before any defined routes. So in our case, for instance, the jwt.auth middleware will run before routes we want protecting and will require a valid JWT Token to access the content.

Barryvdh / laravel-cors

Add the following to your composer.json file:

"barryvdh/laravel-cors": "^0.8.1"

Again run composer update. Once it has finished downloading the files add the following to your config/app.php file:

'providers' => [
    ******** omitted for ease of reading,
    Barryvdh\Cors\ServiceProvider::class,
]

Because we want all our routes to be run through the CORS middleware, we will add it to the global middleware variable. Open app/Http/Kernel.php and add the following to the bottom of the protected $middleware variable:

\Barryvdh\Cors\HandleCors::class,

We will also want to publish the config file so run the following command from the terminal:

php artisan vendor:publish --provider="Barryvdh\Cors\ServiceProvider"

Open the newly created config file config/cors.php and update to the following:

<?php

return [
    'supportsCredentials' => true,
    'allowedOrigins' => ['*'],
    'allowedHeaders' => ['Content-Type', 'Accept', 'Authorization', 'X-Requested-With', 'Origin'],
    'allowedMethods' => ['GET', 'POST', 'PUT', 'DELETE'],
    'exposedHeaders' => ['Authorization'],
    'maxAge' => 0,
    'hosts' => [],
]

I'm not going to go into detail on what the above means but I would suggest reading up about CORS. Essentially what we are doing to telling our API what headers and methods we accept on our API. If anyone tries to use any that are not defined, they will get an error.

Starting Authentication

Let's first create a BaseController for all out API classes as suggested within the Dingo API. This means we won't have to keep importing the helpers everytime we create a new class. Create the following file app/Api/Controllers/BaseController.php and add the following:

<?php

namespace Api\Controllers;

use Dingo\Api\Routing\Helpers;
use Illuminate\Routing\Controller;

class BaseController extends Controller
{
    use Helpers;
}

Now create a new file app/Api/Controllers/AuthController.php and add the following:

<?php

namespace Api\Controllers;

use App\User;
use Dingo\Api\Facade\API;
use Illuminate\Http\Request;
use Api\Requests\UserRequest;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;

class AuthController extends BaseController
{
    public function authenticatedUser(Request $request)
    {
        return JWTAuth::parseToken()->authenticate();
    }

    public function authenticate(Request $request)
    {
        $credentials = $request->only('email', 'password');

        try {
            if (!$token = JWTAuth::attempt($credentials)) {
                return response()->json(['error' => 'invalid_credentials'], 401);
            }
        } catch (JWTException $e) {
            return response()->json(['error' => 'could_not_create_token'], 500);
        }

        return response()->json(compact('token'));
    }

    public function validateToken()
    {
        return API::response()->array(['status' => 'success'])->statusCode(200);
    }

    public function register(UserRequest $request)
    {
        $newUser = [
            'first_name' => $request->get('first_name'),
            'last_name' => $request->get('last_name'),
            'email' => $request->get('email'),
            'password' => bcrypt($request->get('password'))
        ];
        $user = User::create($newUser);
        $token = JWTAuth::fromUser($user);

        return response()->json(compact('token'));
    }
}

Most of the above code is from the documentation for the JWT package. Most of it you don't need to worry about but have a read through it is fairly self-explanatory. You may have noticed UserRequest $request as the argument for the register function. We need to create this request now. What this file will do is check the given credentials from the request and ensure they meet our validation and if they do allow the function to continue otherwise return with an error. Create the following file app/Api/Requests/UserRequest.php and add the following:

<?php

namespace Api\Requests;

use Dingo\Api\Http\FormRequest;

class UserRequest extends FormRequest
{
    /**
    * Determine if the user is authorized to make this request
    *
    * @return bool
    */
    public function authorize()
    {
        return true;
    }

    /**
    * Get the validation rules that apply to the request.
    *
    * @return array
    */
    public function rules()
    {
        return [
            'first_name' => 'required|max:255',
            'last_name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|confirmed|min:4',
        ];
    }
}

Above we are saying the user who is making the request is authorised to do so, and then we are setting out our validation rules. You can read more about these rules here where you will see other ways to implement and also the pre-defined rules.

Before we can start to make use of the above functions we first need to create our database and update the given User database migration. Laravel comes with a lot of options for database integration out of the box. The option we will be using in this tutorial is going to be the SQLite option as this is the easiest to set up. If you want to use MySQL or any other options, have a look at the documentation.

From the root of your app, run the following command from your terminal:

touch database/database.sqlite

This creates our new SQLite database. Now open your .env file and remove the entries which are prepended by DB_* and add the following:

DB_CONNECTION=sqlite

If you were choosing to use MySQL, for instance, all you would need to do is update the DB_* to the correct values. Now before we migrate our database let's quickly edit the initial user table migration. Open database/migrations/2014_10_12_000000_create_users_table.php and update it to the following:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    /**
    * Run the migrations.
    *
    * @return void
    */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('first_name');
            $table->string('last_name');
            $table->string('email')->unique();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
    * Reverse the migrations.
    *
    * @return void
    */
    public function down()
    {
        Schema::drop('users');
    }
}

If you do not know about Laravel's database migration system, then you have been missing out. It is an easy way to manage your database schema and acts like a version control also. When we migrate our database shortly, it will create three tables initially. One being the migrations table. This table contains which tables were set up within each migration. So if you decide to rollback your database, it will only drop the tables from the previous migration. Or if you decide you wanted to reset your database you can recreate it easily. More about this can be found in the documentation.

With the new changes run the following from your terminal:

php artisan migrate

You should see the following

Image of Example Error

Brilliant, we now have out database all set up ready. How easy was that? One last step before we can create our endpoints is that we need to update our User.php class which is located within the app directory. We need to update the $fillable variable to the following:

protected $fillable = [
    'first_name', 'last_name', 'email', 'password',
]

What is this and why do we need it? Laravel out of the box protects us from mass-assignment vulnerability. I won't try and re-explain this vulnerability as Laravel's documentation does a good job at explaining it here.

API Endpoints (Finally)

Open up your app/Http/routes.php and remove $api->get('test', 'TestController@test'); as we no longer need this endpoint. Update your routes.php file and update it with the following:

<?php

Route::get('/', function() {
        return view('welcome');
});

$api = app('Dingo\Api\Routing\Router');

$api->version('v1', function ($api) {
    $api->group(['namespace' => 'Api\Controllers'], function ($api) {

        $api->post('login', 'AuthController@authenticate');
        $api->post('register', 'AuthController@register');

        $api->group(['middleware' => 'jwt.auth'], function ($api) {
            $api->get('users/details', 'AuthController@authenticatedUser');
        });
    });
});

Now to test these routes, I would recommend installing Postman which is a chrome extension. It allows you to be able to make any HTTP request with ease. Install Postman and open it up. Let's first test our /api/users/details URI and see if our middleware is working. Within Postman make sure that the GET request is selected and then enter http://vue-todo-series.dev/api/users/details within the address bar and click send. You should see:

Image of Token Error

Great, this is telling us that we have not provided a token. Now under the Headers tab add two new headers.

1. Accept => application/json
2. Authorization => Bearer labdaldjwbalbdaldjwbalbdalwjbde

Now the above token is not valid so we are hoping to see an appropriate response from our API.

Image of Invalid Token

Great, let's now create a new user and try login them in. Change the request to POST and change the address bar to http://vue-todo-series.dev/api/register. Now click on the Body tab and add the following:

1. email => test@test.com
2. password => password
3. password_confirmation => password
4. first_name => Test
5. last_name => User

Now click Send and you should receive a token back. Copy the token and add it to your Authorization header so it looks like this Bearer copied token here. Now try the GET http://vue-todo-series.dev/api/users/details endpoint and you should see the following:

Image of Invalid Token

Great our endpoints seem to be working. I will leave you to test your login endpoint which is the same as the register but only requires your email and password.

Next Time

I am going to finish this post here as I think it is a good place. In the next part of the tutorial, we will look at transformers and how they can help us in handling changes and protecting our database structure.


comments powered by Disqus