laravel Sanctum vue

Authentication systems are a vital part of most modern applications, and should thus be appropriately implemented.

In this article, you will learn how to build an authentication system using Vue.js and Laravel Sanctum (former Airlock).

We are going to create separate projects for the front end, and for the back end, that will interact with one another through a REST API.

Let's dive in!

Back end (Laravel)

Step #1

For Laravel installation instructions, visit an Official Documentation page.

Create a new Laravel project by running in the terminal

laravel new my-app

or

composer create-project --prefer-dist laravel/laravel my-app

I'm using Laravel Valet, which automatically allows us to access our site at the http://my-app.test domain.

On your machine, it will be accessible according to your local development environmental settings.

Step #2

Create a new database called my-app and set DB_DATABASE=my-app in .env file in your app's directory.

Step #3

Install Laravel Sanctum.

composer require laravel/sanctum

Publish the Sanctum configuration and migration files using the vendor:publish Artisan command. The sanctum configuration file will be placed in your config directory:

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

Run your database migrations to create a database table in which to store API tokens:

php artisan migrate

Add the Sanctum's middleware to your api middleware group within your app/Http/Kernel.php

../app/Http/Kernel.php

use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;

...

    protected $middlewareGroups = [
        ...

        'api' => [
            EnsureFrontendRequestsAreStateful::class,
            'throttle:60,1',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

    ...
],

Step #4

To use tokens for users, we have to add HasApiTokens to the User model in app/User.php.

../app/User.php

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
}

Step #5

Let's create the seeder for the User model. We'll need that a bit later to test the login proccess.

php artisan make:seeder UsersTableSeeder

Now let's insert

DB::table('users')->insert([
    'name' => 'John Doe',
    'email' => 'john@doe.com',
    'password' => Hash::make('password')
]);

into the run() function in database/seeds/UsersTableSeeder.php

To seed users table with user, let's run:

php artisan db:seed --class=UsersTableSeeder

So now we have a new user in our database called John Doe with email john@doe.com and password password.

Step #6

Let's create a /login route in the routes/api.php file:

../routes/api.php

use App\User;
use Illuminate\Support\Facades\Hash;

Route::post('/login', function (Request $request) {
    $data = $request->validate([
        'email' => 'required|email',
        'password' => 'required'
    ]);

    $user = User::where('email', $request->email)->first();

    if (!$user || !Hash::check($request->password, $user->password)) {
        return response([
            'message' => ['These credentials do not match our records.']
        ], 404);
    }

    $token = $user->createToken('my-app-token')->plainTextToken;

    $response = [
        'user' => $user,
        'token' => $token
    ];

    return response($response, 201);
});

Step #7

Let's send a POST request with email john@doe.com and password password as parameters to the http://my-app.test/api/login route. You can use the Postman or Insomnia software packages to accomplish this.

If everything is working well, we will receive a JSON object as response to our request:

{
    "user": {
        "id": 1,
        "name": "John Doe",
        "email": "john@doe.com",
        "email_verified_at": null,
        "created_at": null,
        "updated_at": null
    },
    "token": "AbQzDgXa..."
}

Step #8

Next, we need to change some middleware. We do this in the /routes/api.php file by replacing auth:api with auth:sanctum:

../routes/api.php

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Step #9

Before we continue to the front end, we have to setup cross-origin requests CORS handling.

../config/cors.php

    'paths' => ['api/*', 'login', 'logout'],

    'allowed_methods' => ['*'],

    'allowed_origins' => ['*'],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 0,

    'supports_credentials' => true,
../.env

SANCTUM_STATEFUL_DOMAINS=127.0.0.1

Front end (Vue.js)

We'll use Vuex for state management, Vue Router for routing and axios to make HTTP requests.

Step #1

We are going to use Vue CLI to create a new Vue project. If you are not familiar with this standard tooling for Vue.js development, please, read this guide.

In the directory we are using for our projects, let's run the following command:

vue create my-vue-app

Choose to Manually select features and then opt for Router and Vuex

 

After successfully creating the my-vue-app project, run the following commands:

cd my-vue-app
npm run serve

Now our app should be available at the http://localhost:8080/ domain.

Step #2

Let's create a new file for a Login view.

..src/views/Login.vue

<template>
  <div>
    <h1>Login</h1>
    <form @submit.prevent="login">
      <input type="email" name="email" v-model="email">
      <input type="password" name="password" v-model="password">
      <button type="submit">Login</button>
    </form>
  </div>
</template>

<script>
export default {
  data () {
    return {
      email: '',
      password: ''
    }
  },

  methods: {
    login () {
      this.$store
        .dispatch('login', {
          email: this.email,
          password: this.password
        })
        .then(() => {
          this.$router.push({ name: 'About' })
        })
        .catch(err => {
          console.log(err)
        })
    }
  }
}
</script>

In the Vue Router, we have to implement a route for the Login view.

../src/router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import(/* webpackChunkName: "login" */ '../views/Login.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

Now, if we navigate to http://localhost:8080/login in a browser

 

Step #3

We have to install axios in our frontend directory to make HTTP requests:

npm install axios

Step #4

Let's implement some user authentication actions (login/logout) in Vuex.

../src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

axios.defaults.baseURL = 'http://app-backend.test/api'

export default new Vuex.Store({
  state: {
    user: null
  },

  mutations: {
    setUserData (state, userData) {
      state.user = userData
      localStorage.setItem('user', JSON.stringify(userData))
      axios.defaults.headers.common.Authorization = `Bearer ${userData.token}`
    },

    clearUserData () {
      localStorage.removeItem('user')
      location.reload()
    }
  },

  actions: {
    login ({ commit }, credentials) {
      return axios
        .post('/login', credentials)
        .then(({ data }) => {
          commit('setUserData', data)
        })
    },

    logout ({ commit }) {
      commit('clearUserData')
    }
  },

  getters : {
    isLogged: state => !!state.user
  }
})

After successfully logging in, we are going to store some user data in the user variable and the localStorage.

Step #5

Let's define routes for authenticated and unauthenticated pages.

We can make an About page accessible for authenticated users only.

For this purpose let's add the meta field to the About route.

Let's use Vue Router's beforeEach method to check if the user is logged in. If the user is not authenticated, we'll redirect them back to the login page.

../src/router.index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    meta: {
      auth: true
    },
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import(/* webpackChunkName: "login" */ '../views/Login.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

router.beforeEach((to, from, next) => {
  const loggedIn = localStorage.getItem('user')

  if (to.matched.some(record => record.meta.auth) && !loggedIn) {
    next('/login')
    return
  }
  next()
})

export default router

Step #6

What if the user refreshes a page? Should we ask him to log in again?

Of course not!

Let's add a created() method to the Vue instance to handle that scenario.

created () {
  const userInfo = localStorage.getItem('user')
  if (userInfo) {
    const userData = JSON.parse(userInfo)
    this.$store.commit('setUserData', userData)
  }
}

Step #7

We also need to handle cases when a token is expired or if the user is unauthorized.

Let's do that in the created() method by using interceptors.

So our updated main.js file looks like this:

../src/main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  created () {
    const userInfo = localStorage.getItem('user')
    if (userInfo) {
      const userData = JSON.parse(userInfo)
      this.$store.commit('setUserData', userData)
    }
    axios.interceptors.response.use(
      response => response,
      error => {
        if (error.response.status === 401) {
          this.$store.dispatch('logout')
        }
        return Promise.reject(error)
      }
    )
  },
  render: h => h(App)
}).$mount('#app')

Step #8

We haven't implemented a Logout feature yet. Let's do that in the App.vue file.

Also, let's show the About and Logout buttons only when a user is logged in.

../src/App.vue

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about" v-if="isLogged">About</router-link>
      <router-link to="/login" v-else>Login</router-link>
      <button type="button" @click="logout()" v-if="isLogged">
        Logout
      </button>
    </div>
    <router-view/>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters([
      'isLogged'
    ])
  },

  methods: {
    logout () {
      this.$store.dispatch('logout')
    }
  }
}
</script>

Ok, our tutorial is done.

I hope you found this info helpful!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值