Sanctum allows, among other things, to authenticate an SPA via cookies using the web
authentication guard. The following discussion shows how to create a single page application using Nuxt.js and VuetifyJs to connect it (sign up, sign in and logout a user) to a backend API through Laravel Sanctum. Laravel 8 is the version used below.
1. Backend
Below creates a new Laravel project named ‘server’:
|
|
Once the database settings are done in server/.env
file, migrations can be run:
|
|
At this point, Laravel Sanctum can be installed and set as per the official documentation:
|
|
More serious things start at this point:
In routes/api.php, auth:sanctum
should be used instead of the default auth:api
:
|
|
The above route api/user
is used to request the identity of the currently logged in user, i.d. after the authentication is successful.
In .env these 2 values have to be added:
|
|
Browsers do not allow HTTP requests between different domains. With CORS, a server can agree about which client it can receive requests. This is done in Laravel in config/cors.php file by setting the following key to true
:
|
|
It would be nice to create a user in the database at this stage. This can be done from the command line quite fast:
|
|
Next, 2 controllers can be created to login and to register a user:
1.1. LoginController
|
|
The below code is not perfect and does not comply with best practices but the goal is to just make things function:
|
|
1.2 RegisterController
It could be done this way:
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Auth;
class RegisterController extends Controller
{
public function register(Request $request)
{
// var_dump($request->name);
$request->validate([
'name' => ['required'],
'email' => ['required', 'email', 'unique:users'],
'password' => ['required', 'min:8', 'confirmed']
]);
User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password)
]);
}
}
The new functions can be reached out through routes/api.php:
|
|
2. Frontend
A SPA can be created using Nuxt.js. Below, VuetifyJs as well as axios are going to be used during the creation process of the client project:
|
|
The @nuxtjs/auth-next module can be installed with:
|
|
Both @nuxtjs/auth-next and @nuxtjs/axios should be declared in nuxt.config.js:
|
|
The settings for both modules are as follows:
|
|
And:
1auth: {
2 redirect: {
3 login: '/login',
4 logout: '/',
5 callback: '/login',
6 home: '/'
7 },
8 strategies: {
9 laravelSanctum: {
10 provider: 'laravel/sanctum',
11 url: 'http://localhost:8000',
12 endpoints: {
13 login: { url: '/api/login', method: 'post' }
14 },
15 user: {
16 property: false
17 },
18 tokenRequired: false,
19 tokenType: false
20 }
21 },
22 localStorage: false
23},
Lines 2 - 7 can be removed as that’s already the default behavior of nuxt auth package.
If the project is mainly admin focused with only few public pages, the authenticaion module can be set globally:
|
|
And later it can be disabled on indidual public pages (routes, to be exact) as follows:
|
|
2.1. Login component and route
Login component can be designed as follows:
1<template>
2 <v-container fill-height>
3 <v-row justify="center" align="center">
4 <v-col cols="12" sm="6">
5 <v-form ref="form">
6 <v-text-field
7 v-model="form.email"
8 :counter="10"
9 label="Email"
10 color="green"
11 required
12 >
13 <v-icon slot="prepend" color="grey">
14 email
15 </v-icon>
16 </v-text-field>
17
18 <v-text-field
19 v-model="form.password"
20 label="Password"
21 type="password"
22 color="green"
23 required
24 >
25 <v-icon slot="prepend" color="grey">
26 lock
27 </v-icon>
28 </v-text-field>
29
30 <v-btn color="blue-grey" class="ml-0" @click="login">
31 Login
32 </v-btn>
33 </v-form>
34 </v-col>
35 </v-row>
36 </v-container>
37</template>
38
39<script>
40export default {
41 name: 'Login',
42 data () {
43 return {
44 form: {
45 email: '',
46 password: ''
47 }
48 }
49 },
50
51 methods: {
52 async login () {
53 // this is managed automatically in the background
54 // await this.$axios.$get('/sanctum/csrf-cookie')
55 try {
56 let response = await this.$auth.loginWith('laravelSanctum', {
57 data: this.form
58 })
59 // console.log(response)
60 this.$router.push('/dashboard')
61 } catch (err) {
62 console.log(err)
63 }
64 }
65 }
66}
67</script>
Hence this component can be used in the login route:
|
|
The output should look as follows:
2.2. Register component and route
The component should provide a user interface to populate the user migration file of the backend server:
1<template>
2 <v-container fill-height>
3 <v-row justify="center" align="center">
4 <v-col cols="12" sm="6">
5 <v-form ref="form">
6 <v-text-field
7 v-model="form.name"
8 :counter="10"
9 label="Name"
10 color="green"
11 required
12 >
13 <v-icon slot="prepend" color="grey">
14 account_circle
15 </v-icon>
16 </v-text-field>
17
18 <v-text-field
19 v-model="form.email"
20 :counter="10"
21 label="Email"
22 color="green"
23 required
24 >
25 <v-icon slot="prepend" color="grey">
26 email
27 </v-icon>
28 </v-text-field>
29
30 <v-text-field
31 v-model="form.password"
32 label="Password"
33 type="password"
34 color="green"
35 required
36 >
37 <v-icon slot="prepend" color="grey">
38 lock
39 </v-icon>
40 </v-text-field>
41
42 <v-text-field
43 v-model="form.password_confirmation"
44 label="Confirm password"
45 type="password"
46 color="green"
47 required
48 >
49 <v-icon slot="prepend" color="grey">
50 lock
51 </v-icon>
52 </v-text-field>
53
54 <v-btn color="blue-grey" class="ml-0" @click="login">
55 Register
56 </v-btn>
57 </v-form>
58 </v-col>
59 </v-row>
60 </v-container>
61</template>
62
63<script>
64export default {
65 name: 'Register',
66 data () {
67 return {
68 form: {
69 name: '',
70 email: '',
71 password: '',
72 password_confirmation: ''
73 },
74 errors: {}
75 }
76 },
77
78 methods: {
79 login () {
80 this.$axios
81 .$post('/api/register', this.form)
82 .then(function (response) {
83 console.log(response)
84 })
85 .catch(function (error) {
86 console.log(error)
87 })
88 }
89 }
90}
91</script>
The register route can use the above component:
|
|
Hitting the register route would result in this user interface:
2.3. Authenticated user
For the authenticate user, a Dashboard component could be created.
1<template>
2 <v-container fill-height>
3 <v-row justify="center" align="center">
4 <v-col cols="12" sm="6">
5 Hello
6 <span v-if="user"> {{ user.name }}</span>
7 </v-col>
8 </v-row>
9 <v-row justify="center" align="center">
10 <v-col cols="12" sm="6">
11 <v-btn @click="logout">
12 Logout
13 </v-btn>
14 </v-col>
15 </v-row>
16 </v-container>
17</template>
18
19<script>
20export default {
21 name: 'Welcome',
22 data () {
23 return {
24 user: ''
25 }
26 },
27 created () {
28 this.getAuthenticatedUser()
29 },
30
31 methods: {
32 async getAuthenticatedUser () {
33 console.log('loggedIn : ' + this.$auth.loggedIn)
34 try {
35 let response = await this.$axios.$get('/api/user')
36 this.user = response
37 console.log(response.name)
38 } catch (err) {
39 console.log(err)
40 }
41 },
42 async logout () {
43 console.log('logout')
44 await this.$axios.$post('/api/logout')
45 this.$router.push('/')
46 }
47 }
48}
49</script>
Line 34 requests the /api/user
route in server/routes/api.php declared earlier and which is guarded by Sanctum.
This component displays the name of the user who is signed in successfully to the application, and offers a logout button to click and exit.
The corresponding dashboard route can use the above component as follows:
|
|