Released earlier this year, Laravel Sanctum (formerly Laravel Airlock), is a lightweight package to help make authentication in single-page or native mobile applications as easy as possible. Where before you had to choose between using the web middleware with sessions or an external package like Tymon's jwt-auth, you can now use Sanctum to accomplish both stateful and token-based authentication.
In this short walk-through, I'll show you how to get started with the former. We'll create a dummy API, authenticate a user from a Vue component, and get data associated with that signed-in user.
If you'd like to skip the written tutorial, you can check out this video I've made instead. You can also go directly to the finished source code, available in this repository on GitHub.
Alright, let's dive in!
Creating a test API
The first thing we'll need to do is create an API that we can get data from. I'm thinking of a super simplistic app that will retrieve a list of secrets associated with our user.
I've installed an out-of-the-box Laravel app, and have it and a MySQL database set up in a local environment using my Laravel Docker setup. The first thing that I'm going to create is a model and migration for our secret. Using the command line, this is easy with artisan.
php artisan make:model Secret --migration
Let's open up that migration file and add in our data columns needed for a secret. I think all we'll need from this (aside from the default ID and timestamps that Laravel provides) is a user_id integer to form the relationship to a user, and the actual secret.
Schema::create('secrets', function (Blueprint $table) { $table->id(); $table->integer('user_id'); $table->text('secret'); $table->timestamps();});
Okay, running the migration will bring up our users
and secrets
tables:
php artisan migrate
A couple of quick modifications will need to be made to both of our app's models enabling the relationships, so let's open them up and get started:
// User.php public function secrets(){ return $this->hasMany('App\Secret');}
// Secret.php public function user(){ return $this->belongsTo('App\Secret');}
The final piece of our API structure is the actual routes and controllers. We're only going to be accessing a single path, which will retrieve the secrets associated with our user. So, I've added the following to my app's routes/api.php
file:
Route::get('/secrets', 'SecretController@index');
The controller for this can be created easily using an Artisan command:
php artisan make:controller SecretController
Opening up that controller, let's create our index method and just return back all of our app's secrets for now. Since we have no way of getting an authenticated user, yet:
public function index(){ return App\Secret::all();}
Our dummy API is done for now, let's create some fake users and secrets.
Populating the database
You can easily go right into the database and populate it manually, create a controller and form for users to input their own data, or use Artisan tinker to semi-automate user creation. I'm going to skip these methods and instead use the built-in Laravel factories to generate fake data for our users and secrets.
Out of the box, Laravel comes with a UserFactory.php
class, to generate dummy users. We're going to create one for our secrets as well. In your terminal, run the following Artisan command:
php artisan make:factory SecretFactory --model=Secret
Open up the generated file, and we're going to need to populate each model with just two data points. Our user_id, and a secret:
$factory->define(Secret::class, function (Faker $faker) { return [ 'user_id' => 1, 'secret' => $faker->text ];});
You might be wondering why we're hard-coding in our user_id
in the above snippet. Instead of generating it randomly based on the amount of users, I want to have more control over it. I'll show you shortly how I can overwrite this when we start generating our secrets.
Let's start by creating a couple of fake users. Open up your tinker shell by running the php artisan tinker
command from your site root. Once it's open, we can create two users by running the global factory helper twice:
factory(App\User::class)->create(); //create saves our user in the database, unlike make
Now that we have them generated, let's create our secrets. I'm going to run the following in the tinker shell twice to create two for user_id 1
:
factory(App\Secret::class)->create();
But what about our second user with a different id? Overwriting any of the values in our factory class is easy, all we do is pass in an override array to the create()
method. So, we'll run the following twice to create two secrets for our second fake user:
factory(App\Secret::class)->create(['user_id' => 2]);
Our database is populated with enough fake data, let's move on to installing and prepping the Laravel Sanctum package.
Installing Laravel Sanctum
Installation is a breeze, and can be accomplished by running a few commands in our terminal. First, let's actually install the package using Composer:
composer require laravel/sanctum
Then we'll have to publish the migration files (and run the migration) with the following commands:
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"php artisan migrate
The last part of Sanctum's installation requires us modifying the app\Http\Kernel.php
file to include a middleware that will inject Laravel's session cookie into our app's frontend. This is what will ultimately enable us to pass and retrieve data as an authenticated user:
'api' => [ EnsureFrontendRequestsAreStateful::class, 'throttle:60,1']
Now, we can move onto our app's frontend!
Building the frontend
Starting with Laravel 7, the frontend and authentication boilerplates were stripped out of the main package and instead can be installed with a separate bundle. For the sake of this demonstration, we're going to use it and Vue to craft our frontend.
Running the following commands from our app's root will help us get that set up:
composer require laravel/uiphp artisan ui vue --authnpm install && npm run dev
These commands do three things:
-
Install the Laravel UI package with Composer
-
Generate the JS/UI files, auth boilerplate, and package.json modifications
-
Install the frontend dependencies and compile development JS/CSS assets
I'm going to copy over everything in the welcome.blade.php
file to an app.blade.php
file. In it, I'll be stripping out the interior content and adding an id="app" attribute to the outside div. This will be our Vue app's mount point, as detailed in the generated app.js file.
Let's create our Vue component that will hold our login form and display some secrets.
Creating a Vue component
When we generated our frontend code earlier using php artisan ui vue, an example component was generated under resources/js/components/ExampleComponent.vue
. Let's create a brand new component called SecretComponent.vue
, with a basic data structure:
<template> </template><script>export default { data() { return { secrets: [], formData: { email: '', password: '' } } }}</script>
Our secrets are kept in an array, and we also have a formData object that will be used to store the email address and password for our login form.
Speaking of which, let's get started building out the markup that will create that form. Expanding between the <template>
tags a bit, we'll add two divs. One for the login form, which will be hidden when there's secrets available, and another for the list of secrets, which will be hidden until there's secrets.
<template> <div> <div v-if="!secrets.length" class="row"> <form action="#" @submit.prevent="handleLogin"> <div class="form-row"> <input type="email" v-model="formData.email"> </div> <div class="form-row"> <input type="password" v-model="formData.password"> </div> <div class="form-row"> <button type="submit">Sign In</button> </div> </form> </div> </div></template>
Now, we should have a good-looking sign-in form:
In the code above, we're excluding an action from the form, and instead using Vue's submit handler to take care of the form submission. Let's create the method called handleLogin
, which will be fired whenever a user tries to sign in:
<script>export default { data() { return { secrets: [], formData: { email: '', password: '' } } }, methods: { handleLogin() { // handle user login } }}</script>
Finally, we can go into our resources/js/app.js
file and register our new component:
Vue.component('secret-component', require('./components/SecretComponent.vue).default);
Then going back into our app.blade.php
file, replacing handleLogin()
method to use and authenticate a user!
Authenticating a user
If we take a look at the Laravel Sanctum documentation for SPA authentication, it details that we first need to make a call to a route at /sanctum/csrf-cookie
, which will set the CSRF protection on our app and enable POST requests uninterrupted.
From there, we can proceed to send a request to the /login
route, provided by the authentication framework we added in earlier. All we have to do is pass in our user's email and password, and it will authenticate our user if the credentials match.
Let's try implementing that in our handleLogin()
method from earlier:
handleLogin() { axios.get('/sanctum/csrf-cookie').then(response => { axios.post('/login', this.formData).then(response => { console.log('User signed in!'); }).catch(error => console.log(error)); // credentials didn't match });}
Okay, if we save that and try to sign in with one of the dummy users in our database, everything should go through smoothly! The first request sets the CSRF protection in our frontend, and the second one sends over the formData object containing our user's email and password. No response data is sent back over, so we can just continue on using a Promise's resolve.
What's there to do now? Well, let's get our user's secrets!
Retrieving user data
In our Vue component, let's create a new method called getSecrets()
, which will make a call to our secrets API endpoint that we created earlier. If everything goes successfully, that call should return back an array of secrets, which we can use to replace the array in our component's data object.
It will need to be called after our user has successfully logged in, so the flow will look something like this:
handleLogin() { axios.get('/sanctum/csrf-cookie').then(response => { axios.post('/login', this.formData).then(response => { this.getSecrets(); }).catch(error => console.log(error)); // credentials didn't match });},getSecrets() { axios.get('/api/secrets').then(response => this.secrets = response.data);}
But wait, we're returning all of the app's secrets, remember? Not just the user's. We can change that with a single line though, thanks to Laravel Sanctum. Let's open up our SecretController and navigate to the index()
method, making a few changes:
public function index(Request $request){ return $request->user()->secrets;}
Included in each API call (after we've successfully logged our user in), is a laravel_session
cookie in the header of each request. Sanctum takes this cookie, and using the fact that our API endpoint is stateful due to the added-in middleware from earlier, and determines if a user is logged in.
This user in its entirety, can be retrieved using the $request
object. We can then chain the secrets relationship onto it, returning back the array of our user's secrets.
Finally, let's add in some boilerplate markup to render out these secrets to our user:
<template> <div> <div v-if="secrets.length" class="row"> <div class="secret" v-for="(secret, index) in secrets" :key="index"> <strong v-text="secret.secret"></strong> - created at <span v-text="secret.created_at"></span> </div> </div> </div></template>
💥 Now if we refresh our app and sign in using our fake user's credentials, we'll see a list of our user's secrets displayed out for us:
So, now what's left?
Conclusions and next steps
I've only just scratched the surface of what this incredibly powerful and easy-to-use package offers. If you're using the techniques above to authenticate a user in an SPA, the entire time they're on your app after being authenticated, you can make calls to your accompanying Laravel API as if you were a logged-in user in a traditional web app.
Additionally, you can use token-based authentication for standalone SPAs, native mobile applications, or something like ElectronJS desktop apps. Everything is handled in such an eloquent way, and the documentation around the package is incredibly robust.
I hope this article's helped you get started authenticating users into your Vue SPA with Laravel. As always, if you have any questions please don't hesitate to reach out to me in the comments below or on my Twitter.
My Newsletter
Read sampleSubscribe using the form below and about 1-2 times a month you'll receive an email containing helpful hints, new packages, and interesting articles I've found on PHP, JavaScript, Docker and more.