Dealing with authentication in single-page applications (SPA) can be tricky. Often, developers simply use local storage to save users tokens. However, local storage isn’t very secure, so it’s generally recommended to use something that offers more protection, such as cookies.
In this tutorial, we’ll show you how to implement authentication in a Nuxt.js SPA using Laravel Sanctum. To demonstrate how this works, we’ll walk through the process of building a simple Nuxt.js app with authentication powered by a Laravel API.
We’ll cover the following in detail and with examples:
- What is Laravel Sanctum?
- Creating a Laravel app
- Setting up Laravel Sanctum
- Building a Laravel API
- Creating a Nuxt.js application
- Creating a login page
- Updating the homepage
- Restricting access
To follow along with this demonstration, you should have basic knowledge of Laravel and Nuxt.js.
What is Laravel Sanctum?
Laravel Sanctum is a Laravel package for authentication of SPAs, mobile applications, and basic, token-based APIs. Depending on what you’re building, Laravel Sanctum can be used to generate API tokens for users or authenticate users with a Laravel session.
Creating a Laravel app
Let’s start our demo by creating a new Laravel application.
To create a new Laravel app, use the Laravel Installer:
laravel new laravel-sanctum-nuxtjs-api
Once that’s done, run the following command to start the application:
cd laravel-sanctum-nuxtjs-api php artisan serve
The application should now be running on http://127.0.0.1:8000. We’re going to leave it running for the rest of the tutorial.
Setting up Laravel Sanctum
To set up Sanctum, first install it:
composer require laravel/sanctum
Once it’s installed, you can publish Sanctum vendor files:
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
This creates a sanctum.php
file inside the config
directory, which is used to configure Sanctum. It will also create a migration file for a personal_access_tokens
table, which is used to store access tokens.
Before we run the migrations, let’s set up the database for our application. To keep things simple, we’ll use SQLite.
Create a database.sqlite
file:
touch database/database.sqlite
Update the .env
file to reflect this:
// .env DB_CONNECTION=sqlite DB_DATABASE=/absolute/path/to/database.sqlite
Now, we run the database migrations:
php artisan migrate
For Sanctum to generate access tokens for users, the User
model need to use the HasApiTokens
trait:
// app/Models/User.php use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; }
One of the benefits of using Sanctum is that it uses the normal Laravel’s session cookies for authentication in an SPA.
To configure the domains from which our SPA will make request, go into the sanctum.php
file and update the stateful
key accordingly:
// config/sanctum.php 'stateful' => explode(',', env( 'SANCTUM_STATEFUL_DOMAINS', 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1' )),
Instead of updating the file directly, we’ll use the environment variables:
// .env SESSION_DRIVER=cookie SANCTUM_STATEFUL_DOMAINS=localhost:3000 SESSION_DOMAIN=.localhost
Typically, the domains should include your local and production domains, which access your API via a SPA. I set it to just localhost:3000
because that’s where the SPA will be running. In addition to the stateful domains, we also set the session driver and domain.
Next, we need to register Sanctum’s middleware in the api
middleware group inside the app/Http/Kernel.php
file:
// app/Http/Kernel.php use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; 'api' => [ EnsureFrontendRequestsAreStateful::class, ... ]
This middleware will ensure that incoming requests from our SPA can authenticate using Laravel’s session cookies.
Lastly, let’s ensure that our application’s CORS configuration is returning the Access-Control-Allow-Credentials
header with a value of True
. We can do that by updating the cors.php
as follows:
// config/cors.php 'supports_credentials' => true
Building a Laravel API
With all the set up out of the way, let’s start building our Laravel API. To keep things simple, the API will only contain endpoints for authenticating users, fetching authenticated users, and logging out users.
Of course, users needs to be registered before they can perform authentication. So let’s seed the database with a dummy user that we can use to test the authentication system. Do that directly inside DatabaseSeeder.php
:
// database/seeders/DatabaseSeeder.php use App\Models\User; User::create([ 'name' => 'John Doe', 'email' => 'johndoe@example.com', 'password' => bcrypt('password'), ]);
Next, run the seeder:
php artisan db:seed
Create the /login
endpoint inside routes/web.php
:
// routes/web.php Route::post('login', 'AuthController@login');
Then, create the AuthController
:
php artisan make:controller AuthController
Let’s add the login
method:
// app/Http/Controllers/AuthController.php use Illuminate\Support\Facades\Auth; public function login(Request $request) { if (Auth::attempt($request->only('email', 'password'))) { $request->session()->regenerate(); } return response()->json([ 'message' => 'Invalid login details' ], 401); }
Here, we attempt to authenticate the user with the supplied details. If no match is found, we simply return an appropriate JSON response. Otherwise, a session is started for the user.
Inside routes/web.php
, create the /logout
endpoint:
// routes/web.php Route::post('logout', 'AuthController@logout');
To add the logout functionality:
// app/Http/Controllers/AuthController.php public function logout(Request $request) { Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); }
logout()
removes the user’s details from the session. Then, we invalidate the user’s session and, lastly, regenerate the CSRF token.
Since we’re going to be making requests to these routes from a different domain — hat is, from the SPA — let’s make sure cross-origin requests are allowed to /login
and /logout
by adding them to the paths
array inside config/cors.php
:
// config/cors.php 'paths' => [ ..., 'login', 'logout', ],
To add the implementation for fetching an authenticated user, create the /api/user
endpoint inside routes/api.php
:
// routes/api.php Route::get('user', 'AuthController@me');
Next, add the me
method:
// app/Http/Controllers/AuthController.php public function me(Request $request) { return response()->json([ 'data' => $request->user(), ]); }
Here, we simply return a JSON response containing the currently authenticated user.
As you might have guessed, the /auth/user
endpoint will be accessible to only authenticated users. So let’s make sure of that by making use of the sanctum
authenticated guard.
Update the route as follows:
// routes/api.php Route::get('/auth/user', 'AuthController@me')->middleware('auth:sanctum');
This will ensure requests to the endpoint contain an authorization header with a valid token.
Lastly, let’s uncomment the line below inside RouteServiceProvider.php
:
// app/Providers/RouteServiceProvider.php protected $namespace = 'App\\Http\\Controllers';
Creating a Nuxt.js application
Now, let’s move on to the SPA itself. We’ll start by creating a new Nuxt.js application.
To create a Nuxt.js application, simply use the command below:
npx create-nuxt-app laravel-sanctum-nuxtjs-app
When prompted, select the options that makes sense to you. Here’s what I selected:

Once everything is done installing, start the application:
cd laravel-sanctum-nuxtjs-app npm run dev
For authentication, we’ll use the nuxt/auth module.
To install the nuxt/auth module:
npm install --save-exact @nuxtjs/auth-next
Next, add @nuxtjs/auth-next
to the modules
array of nuxt.config.js
:
// nuxt.config.js { modules: [ ..., '@nuxtjs/auth-next', ] }
Finally, update axios
object as shown below:
// nuxt.config.js axios: { credentials: true, },
Creating a login page
To style our login page, we’ll use Bulma CSS, which we installed when creating the Nuxt.js application.
Let’s create the login page. Inside the pages
directory, create a login.vue
file and add the following code:
// pages/login.vue <template> <section class="section"> <div class="container"> <div class="columns is-centered"> <div class="column is-one-third"> <h2 class="title has-text-centered">Login</h2> <form method="post" @submit.prevent="login"> <div class="field"> <label class="label">Email</label> <div class="control"> <input type="email" class="input" v-model="email" required /> </div> </div> <div class="field"> <label class="label">Password</label> <div class="control"> <input type="password" class="input" v-model="password" required /> </div> </div> <div class="control"> <button type="submit" class="button is-dark is-fullwidth"> Login </button> </div> </form> </div> </div> </div> </section> </template>
Here, we have a basic login form, which, when submitted, calls a login
method:
Before we create the login
method, let’s configure nuxt-auth
to make use of Laravel Sanctum. We can do that by adding the snippet below inside nuxt.config.js
:
// nuxt.config.js auth: { strategies: { laravelSanctum: { provider: 'laravel/sanctum', url: 'http://localhost:8000', }, }, },
Note: The domain set as the url
has to be the same as the SPA. Since the SPA is running on http://localhost:3000
, the url
is set to http://localhost:8000
.
We set the Laravel Sanctum provider as the strategy the nuxt-auth
module will use for authentication. Under the hood, the Laravel Sanctum provider makes requests to:
/sanctum/csrf-cookie
, which issues aXSRF-TOKEN
cookie as a header/login
, the endpoint we created insideroutes/web.php
, when logging in- The
/api/user
route in our Laravel application when fetching the authenticated user.
Now, we can add the functionality for the login
method inside login.vue
:
// pages/login.vue <script> export default { data() { return { email: '', password: '', } }, methods: { async login() { await this.$auth.loginWith('laravelSanctum', { data: { email: this.email, password: this.password, }, }) this.$router.push('/') }, }, } </script>
First, we define some data properties. Then we have the login
method, where we are logging using the Laravel Sanctum provider.
Under the hood, the provider first makes a request to /sanctum/csrf-cookie
to grab a CSRF token and set it as a XSRF-TOKEN
cookie, which is used in subsequent requests. Then, it makes a POST request to the /l``ogin
endpoint. Upon successful login, the user is redirected to the homepage.
Updating the homepage
For now, the homepage contains the default content from when we created the Nuxt.js app. Let’s update it to display the authenticated user’s name and a way to log out.
Replace the content of pages/index.vue
with the following:
// pages/index.vue <template> <section class="section"> <div class="container"> <h1 class="title">Dashboard</h1> <p>Hi {{ user.name }}</p> <a href="#" @click.prevent="logout">Logout</a> </div> </section> </template> <script> export default { data() { return { user: this.$auth.user.data, } }, methods: { async logout() { await this.$auth.logout() this.$router.push('/login') }, }, } </script>
Under the hood, the Laravel Sanctum provider makes a request to the /api/user
endpoint to fetch the authenticated user. We can get the user’s details through this.$auth.user
, which we simply assign to a user
data property.
To log out, we simply call the logout
method, then redirect to the login page.
Upon a successful login, we should get something like this:
Restricting access
The homepage is serving as the profile page, so let’s make sure only authenticated users can access it. We can do that by making use of the auth
middleware provided by the nuxt-auth
.
Add the following code inside nuxt.config.js
:
// nuxt.config.js router: { middleware: ['auth'] }, >
Now, when authenticated users try to access the homepage, they’ll be redirected to the login page.
Conclusion
In this tutorial, we showed you how to use Laravel Sanctum to implement authentication in a Nuxt.js SPA.
To learn more about Laravel Sanctum, check out the Laravel Sanctum docs.
You can get the complete source code for our demo API and SPA on GitHub.
LogRocket: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
Try it for free.