In part 5 we setup our admin area and created a login page that authenticates a user. Now we need to use this authentication to restrict access to our admin area and our API, and to do that we are going to use Laravel Passport. This won’t be too complicated because Laravel Passport already has a mechanism built in to allow access to a user authenticated via Laravel’s session, which is what we are interested in and we aren’t going to worry about scopes because our choice is binary – you’re either authenticated or you’re not, so let’s start by pulling in Laravel Passport from composer:

composer require laravel/passport

Once that’s installed we want to add the PassportServiceProvider to the Provider array in config/app.php like so:

Laravel\Passport\PassportServiceProvider::class,

Now we need to setup the database tables for our API tokens by doing:

php artisan migrate

Then we can install laravel passport like so:

php artisan passport:install

Then we need to add the Laravel\Passport\HasApiTokens trait to our User model in app\User.php:

<?php

namespace App;

use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
    // Other model methods
}

We might aswell setup the passport routes, even though we won’t be using them now, we may want to in future so let’s add the following to the boot method of the AuthServiceProvider in app\Providers\AuthServiceProviders.php:

<?php

namespace App\Providers;

use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Passport::routes();
    }
}

Then we just need to change the api driver from token to passport in the guards array in our auth config in config/auth.php like so:

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'passport',
            'provider' => 'users',
        ],
    ],

Now all we need to do is add the following to web middleware in app/Http/Middleware/kernel.php:

'web' => [
    // ..Other middleware...
    \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],

Done! Laravel passport is now setup and we can restrict access to our API, so let’s start with a controller that can access the logged in users profile information, which we will call ProfileController, to do this simply do:

php artisan make:controller Api\ProfileController

Now let’s add a function that returns the logged in user’s profile to the controller we just created in app/Http/Controllers/Api/ProfileController.php:

    function getUser(Request $request){
       return  json_encode($request->user);
    }

and let’s add that to our API routes in routes/api.php:

Route::middleware('auth:api')->get('/profile', 'Api\ProfileController@getUser');

Because we have added the api:auth middleware any request to the ProfileController requires the user to be logged in, behind the scenes Laravel is dealing with our tokens, so our API is now secured.

Securing the Admin Area

Our API might be secured but the admin area itself is still accessible; we don’t have any pages yet, so let’s quickly set up a Dashboard component that says “Hello” to our logged in user, so create a file called Dashboard.vue in resources/assets/admin/views with the following:

<template>
    <div>Hello {{name}}</div>
</template>

<script type="text/javascript">
export default {
    created() {
      this.getUser();
    },
    methods: {
        getUser() {
            axios.get('/api/profile').then(response => {
                this.name = response.data.name;
            })
        }
    },
    data() {
        return {
            name: ''
        }
    }
}
</script>

This simply makes a request to our api’s getUser() method (via the route we just set up) and set’s the “name” property, which we are then displaying on the page. Now we just want to add that to our routes in resources/assets/js/admin/admin.js:

import Login from './components/views/Login.vue'
import Dashboard from './components/views/Dashboard.vue'

const routes = [
    { path: '/', component: Dashboard },
    { path: '/login', component: Login },
]

Now if you go to the dashboard without logging in you will receive a “401 not authorised” error in the console because we haven’t logged in, so our authentication is working. That’s not really what we want though, we want to make sure that the user is logged in before we try to load the page which we can do with vue-router’s beforeEach() global guard. So let’s add the following to admin.js:

router.beforeEach((to, from, next) => {
    if (to.fullPath !== "/login") {
        axios.get('/api/profile').then(response => {
            next();
        }).catch(error => {
            router.push('/login');
        })
    }else{
    	next();
    }
})

This guard checks if we are on the login page, if we are not then it sends a request to our API to get the user profile, if our API returns a valid response (i.e. we are logged in) then it allows the router to continue to the protected route, otherwise it redirects us to the login page. So, now if you try to go the http://localhost:8000/admin you will be redirected to the login page. If you login, you will be redirected to the dashboard as an authenticated user which will personally greet you with a “Hello Yourname” message.

Adding a Logout

If we can Login, we also need to logout, so let’s adjust our admin NavMenu component in resources/assets/admin/components/core/NavMenu.vue to add a logout link like so:

<template>
    <div class="ui menu">
        <div class="ui container">
            <router-link to="/" class="item"><i class="dashboard icon"></i> Dashboard</router-link>
        </div>
        <div class="right menu">
            <a href="#" class="ui right aligned item" @click.prevent="logout">Logout</a>
        </div>
    </div>
</template>

<script type="text/javascript">
export default {
    methods: {
        logout() {
            axios.post('/logout').then(response => {
                location.reload();
            }).catch(error => {
                location.reload();
            });
        }
    }
}
</script>

Our logout needs to send a request to the logout route we added to our Laravel web routes in Part 5, once the request is complete we then reload the page, which fires our beforeEach guard in admin.js and redirects us to the login page. The reload is necessary because Laravel will now want a fresh csrf token after the logout, which can only be refreshed in our app via a page reload. You may notice that we are also reloading the page in the error handler, in this case the error will likely be a TokenMismatchException, which means we will have already logged out, in future we may deal with this scenario automatically but this is fine for now. Lastly, we are emitting a loading event from the NavBar component, which is being listened for in Admin.vue. This allows us to create a loader overlay while the logout request is being processed. In future we may bring in vuex to better handle global states.

That’s it, our admin area is now set up and only authenticated users can access it. Admittedly, part 5 and part 6 of the project have been pretty intense and there’s been a lot to get through, so if you’ve made it to this stage, well done! Next up we will finally get to building the blog itself.

Advertisements