Đăng ký Đăng nhập sử dụng laravel + vuejs

1. Giới thiệu

Xin chào ae mình lại có time quay lại để tiếp tục với seri Vuejs Spa + Laravel authentiaction jwt đây . Bắt đầu luôn nha .

2. Back end (Laravel)

Step 1

  • Tạo project Laravel composer create-project --prefer-dist laravel/laravel learning

run comand line :

  • composer install
  • php artisan key:generate
  • cp .env.example .env
  • composer require laravel/ui ( package laravel ui )
  • php artisan ui vue chạy xong thì bạn có thể thấy 1 folder component trong resoures/js
  • npm install hoặc yarn install để tả các package về .

Cài đặt package JWT

  • composer require tymon/jwt-auth
  • Tiếp theo mở config/app.php : thêm vào provider : Tymon\JWTAuth\Providers\LaravelServiceProvider::class, thêm vào aliases
    'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
    'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,

sau đó chúng ta publish nó : php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

Step 2

Đầu tiên tạo 1 controller :

php artisan make:controller api/SinglePageController Mục đích của Controller này là return về view sử dụng vueJs .

<?php

namespace App\Http\Controllers\api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class SinglePageController extends Controller
{
    public function index()
    {
        return view('app');
    }
}

Step 3

Một token sinh ra khi chung ta login sẽ trả về model User. Model User sẽ Implement JWTSubject và update lại model :

namespace App\Models;

use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    public function getJWTCustomClaims()
    {
        return [];
    }
}

Step 4 Update Authentication Middleware

Update them vào middeware Autenticate.php

<?php

namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;

class Authenticate extends Middleware
{
    // Override handle method
    public function handle($request, Closure $next, ...$guards)
    {
        if ($this->authenticate($request, $guards) === 'authentication_failed') {
            return response()->json(['error'=>'Unauthorized'],400);
        }
        return $next($request);
    }
    // Override authentication method
    protected function authenticate($request, array $guards)
    {
        if (empty($guards)) {
            $guards = [null];
        }
        foreach ($guards as $guard) {
            if ($this->auth->guard($guard)->check()) {
                return $this->auth->shouldUse($guard);
            }
        }
        return 'authentication_failed';
    }
}

Step 5

Tiếp theo ta tạo 1 controller để phục vụ cho việc view, create, update php artisan make:controller api/UserController

    <?php

namespace App\Http\Controllers\api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Models\User;

class UserController extends Controller
{
    public function __construct() {
        $this->middleware('auth:api', ['except' => ['login', 'register']]);
    }

    public function index()
    {
        return response()->json(auth('api')->user());
    }

    public function login()
    {
        $credentials = request(['email', 'password']);
        if (! $token = auth('api')->attempt($credentials)) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        return $this->respondWithToken($token);
    }

    public function logout()
    {
        auth('api')->logout();

        return response()->json(['message' => 'Successfully logged out']);
    }

    public function register(Request $request)
    {
        $this->validate($request, [
            'name' => 'required',
            'email' => 'required|email|unique:users',
            'password' => 'required|min:6',
        ]);

        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => $request->password,
        ]);

        return response()->json(['user' => $user], Response::HTTP_OK);
    }

    public function refresh()
    {
        return $this->respondWithToken(auth('api')->refresh());
    }

    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'user' => $this->guard(),
            'token_type' => 'bearer',
            'expires_in' => auth('api')->factory()->getTTL() * 60
        ]);
    }

    public function guard()
    {
        return Auth::Guard('api')->user();
    }
}

Step 6 - Blade

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">

        <title>Laravel</title>

        <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
        <link href="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.min.css" rel="stylesheet">

        <link href="{{ asset('css/app.css') }}" rel="stylesheet">
        <link href="{{ asset('css/custom.css') }}" rel="stylesheet">
    </head>
    <body>
        <div id="app"></div>
        <script src="{{ asset('js/app.js') }}"></script>
    </body>
</html>

Ném đoạn này vào resources/views/welcome.blade.php

3 Client (VueJs)

Step 1

Clien thì ta sử dụng 2 package là vue-routerwebsanova/vue-auth

npm install @websanova/vue-auth

npm install vue-router

Webpack Mix Mix file js và style vào folder public, mình cũng muốn sử dụng đường dẫn để truy cập đến các component Vue dễ dàng hơn nên mình sẽ dử dụng alias

const mix = require('laravel-mix');

mix.webpackConfig({
   resolve: {
      extensions: ['.js', '.vue'],
      alias: {
         '@': __dirname + '/resources/js',
      }
   }
})

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js('resources/js/app.js', 'public/js')
   .sass('resources/sass/app.scss', 'public/css')
   .sass('resources/sass/custom.scss', 'public/css');

Bây giờ truy cập các component vue trong thư mục resoure/js thì chỉ cần thay thế bởi đường dẫn @/ là xong

Step 2 - Router

import VueRouter from 'vue-router';
import Login from '@/components/auth/LoginComponent';
import Register from '@/components/auth/RegisterComponent';
import Dashboard from '@/components/DashboadComponent';

const routes = [
    // path for determined page
    {
        path: '/register',
        component: Register
    },
    {
        path: '/login',
        component: Login,
        name: 'login',
        meta: {
            auth: false
        }
    },
    {
        path: '/home',
        component: Dashboard,
        name: 'home',
        meta: {
            auth: true
        }
    }
];

const router = new VueRouter({
    mode: 'history',
    routes: routes
});

export default router;

Step 3 - Config vue-auth

Create 1 file user.js ở resources

    import bearer from '@websanova/vue-auth/drivers/auth/bearer';
    import axios from '@websanova/vue-auth/drivers/http/axios.1.x';
    import router from '@websanova/vue-auth/drivers/router/vue-router.2.x';

    const config = {
        auth: bearer,
        http: axios,
        router: router,
        tokenDefaultName: 'auth-token',
        tokenStore: ['cookie'],
        notFoundRedirect: {
            path: '/home'
        },
        registerData: {
            url: '/api/auth/register',
            method: 'POST',
            redirect: null,
        },
        loginData: {
            url: '/api/auth/login',
            method: 'POST',
            redirect: '/home',
            fetchUser: true,
        },
        logoutData: {
            url: '/api/auth/logout',
            method: 'POST',
            redirect: '/login',
            makeRequest: true
        },
        fetchData: {
            url: '/api/auth/user',  
            method: 'GET',
            enabled: true
        },
        parseUserData (data) {
            return data || {}
        },
    };

    export default config;

Step 4 - App

app.js ở đây nó sẽ là file sử dụng chung cho tất cả các component

    import './bootstrap';
    import Vue from 'vue';
    import router from './route';
    import App from '@/components/AppComponent'
    import VueAuth from '@websanova/vue-auth';
    import axios from 'axios';
    import VueAxios from 'vue-axios';
    import auth from './auth';
    import VueRouter from 'vue-router';

    Vue.use(VueAxios, axios);

    Vue.router = router;
    App.router = Vue.router;
    Vue.use(VueRouter);


    Vue.use(VueAuth, auth);

    new Vue(App).$mount('#app');

Step 5

Tạo 1 AppComponent.vue

<template>
  <div class="panel panel-default">
    <div class="panel-heading" v-if="$auth.check()">
      <top-menu></top-menu>
    </div>
    <div class="panel-body">
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
import TopMenu from '@/components/layouts/TopMenuComponent'

export default {
  components: {
    TopMenu
  }
}
</script>

Step 6

Cần thêm một cái component TopBar để xem người hiện tại đang đăng nhập và thực hiện phần đăng xuất. Component này chỉ có khi hệ thống đã được xác thực bằng hàm v-if="$auth.check()"

    <template>
      <nav class="navbar navbar-dark bg-dark">
        <div class="logo">
          <router-link :to="{}" class="navbar-brand">Learning Laravel - VueJs</router-link>
        </div>
        <div class="my-2 my-lg-0 dropdown navbar-right">
          <a class="nav-link dropdown-toggle"
            href="#" id="navbarDropdown"
            role="button"
            data-toggle="dropdown"
            aria-haspopup="true"
            aria-expanded="false">
            {{ $auth.user().name }}
          </a>
          <div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
            <a class="dropdown-item text-black" href="#" @click="logout">Logout</a>
          </div>
        </div>
      </nav>
    </template>

Step 7

Tạo mới 1 RegisterComponent.vue

<template>
  <div class="container">
    <div class="card card-default">
      <div class="card-header">Register</div>
      <div class="card-body">
        <div class="alert alert-danger" v-if="error && !success">
          <p>here was an error, unable to complete registration.</p>
        </div>
         <div class="alert alert-success" v-if="success">
          <p>Registration completed. You can now <router-link :to="{name:'login'}">sign in.</router-link></p>
        </div>
        <form autocomplete="off" @submit.prevent="register" v-if="!success" method="post">
          <div class="form-group" v-bind:class="{ 'has-error': error && errors.email }">
            <label for="name">Name</label>
            <input type="text" id="name" class="form-control" v-model="name">
            <span class="help-block" v-if="error && errors.name">{{ errors.name }}</span>
          </div>
          <div class="form-group" v-bind:class="{ 'has-error': error && errors.email }">
            <label for="email">Email</label>
            <input type="email" id="email" class="form-control" placeholder="[email protected]" v-model="email">
            <span class="help-block" v-if="error && errors.email">{{ errors.email }}</span>
          </div>
          <div class="form-group" v-bind:class="{ 'has-error': error && errors.password }">
            <label for="password">Password</label>
            <input type="password" id="password" class="form-control" v-model="password">
            <span class="help-block" v-if="error && errors.password">{{ errors.password }}</span>
          </div>
          <button type="submit" class="btn btn-default">Register</button>
        </form>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      name: '',
      email: '',
      password: '',
      error: false,
      errors: {},
      success: false
    }
  },
  methods: {
    register: function () {
      var app = this
      this.$auth.register({
        url: '/api/auth/register',
        params: {
          name: app.name,
          email: app.email,
          password: app.password
        },
        success: function () {
          app.success = true
        },
        error: function (resp) {
          app.error = true;
          app.errors = resp.response.data.errors;
        },
        redirect: null
      })
    }
  }
}
</script>

Step 8

Tiếp theo là LoginComponent.vue

    <template>
      <div class="container">
        <div class="card card-default">
          <div class="card-header">Sign In</div>
          <div class="card-body">
            <div class="alert alert-danger" v-if="error">
              <p>Sign in fail. Please try again!</p>
            </div>
            <form autocomplete="off" @submit.prevent="login" method="post">
              <div class="form-group">
                <label for="email">E-mail</label>
                <input type="email" id="email" class="form-control" placeholder="[email protected]" v-model="email" required>
              </div>
              <div class="form-group">
                <label for="password">Password</label>
                <input type="password" id="password" class="form-control" v-model="password" required>
              </div>
              <button type="submit" class="btn btn-default">Sign In</button>
            </form>
          </div>
        </div>
      </div>
    </template>

    <script>
    import Vue from 'vue'

    export default {
      data: function () {
        return {
          email: null,
          password: null,
          error: false
        }
      },
      methods: {
        login: function () {
          var app = this
          this.$auth.login({
            params: {
              email: app.email,
              password: app.password
            },
            success: function (response) {},
            error: function () {
              app.error = true
            },
          })
        }
      }
    }
    </script>

Step 9

Cuối cùng là HomeComponent. Khi login xong, website sẽ chuyển hướng đến trang này

<template>
  <div>
    <h1>Home</h1>
  </div>
</template>
<script>
export default {
    methods: {
        async created() {
            if (this.token) {
            const tokenType = this.token.token_type;
            const accessToken = this.token.access_token;

            let authorization = '';

            if (tokenType && accessToken) {
                authorization = `${tokenType  } ${  accessToken}`;
            }

            axios.defaults.headers.common['Authorization'] = authorization;
            }
        },
    },
}
</script>

4 Kết thúc

Hy vọng bài viết sẽ mang lại nhiều thứ cho bạn . Để tiếp thêm động lực hay like và subscribe ( Upvote cho mình nha) . Có gì thắc mắc xin hay comment ở dưới nha . Thank you for read .


All Rights Reserved