#38-Laravel API Autentikasi menggunakan JWT

Persiapan

  1. Install Laravel & Buat Proyek
  2. Buat Database laravel_api (MySQL)
  3. Setting Konfigurasi Laravel

Install & Setting JWT Package

composer require tymon/jwt-auth:dev-develop --prefer-source

Lalu buka config/app.php

Di bagian Provider tambahkan baris kode seperti di bawah ini :

Tymon\JWTAuth\Providers\LaravelServiceProvider::class;

Di Bagian Aliases tambahkan 2 baris kode seperti di bawah ini :

'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class, 
'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,

Setelah itu publish JWT Packagenya dengan mengetik perintah :

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

Lalu buat jwt-auth secretnya dengan mengetik perintah

php artisan jwt:secret

Buat Model User

buka file Model User, lokasi filenya ada di app/User.php buatlah seperti di bawah ini :

<?php

namespace App;

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',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

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

Pada Laravel table user secara otomatis sudah ada dalam database migrate, kita langsung jalankan migrate, maka table users akan otomatis tercreate di database.

php artisan migrate

Buat User Controller

Ubah isi app/Http/Controllers/UserController.php

php artisan make:controller UserController
<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;

class UserController extends Controller
{
    public function login(Request $request)
    {
        $credentials = $request->only('email', 'password');
        try {
            if (! $token = JWTAuth::attempt($credentials)) {
                return response()->json(['error' => 'invalid_credentials'], 400);
            }
        } catch (JWTException $e) {
            return response()->json(['error' => 'could_not_create_token'], 500);
        }
        return response()->json(compact('token'));
    }

    public function register(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
        ]);

        if($validator->fails()){
            return response()->json($validator->errors()->toJson(), 400);
        }

        $user = User::create([
            'name' => $request->get('name'),
            'email' => $request->get('email'),
            'password' => Hash::make($request->get('password')),
        ]);
        $token = JWTAuth::fromUser($user);
        return response()->json(compact('user','token'),201);
    }

    public function getAuthenticatedUser()
    {
        try {
            if (! $user = JWTAuth::parseToken()->authenticate()) {
                return response()->json(['user_not_found'], 404);
            }
        } catch (Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
            return response()->json(['token_expired'], $e->getStatusCode());
        } catch (Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
            return response()->json(['token_invalid'], $e->getStatusCode());
        } catch (Tymon\JWTAuth\Exceptions\JWTException $e) {
            return response()->json(['token_absent'], $e->getStatusCode());
        }
        return response()->json(compact('user'));
    }
}

Buat Middleware

php artisan make:middleware JwtMiddleware

Ubah isi app/Http/Middleware/JwtMiddleware.php

<?php

namespace App\Http\Middleware;

use Closure;
use JWTAuth;
use Exception;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;

class JwtMiddleware extends BaseMiddleware
{

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        try {
            $user = JWTAuth::parseToken()->authenticate();
        } catch (Exception $e) {
            if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenInvalidException){
                return response()->json(['status' => 'Token is Invalid']);
            }else if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenExpiredException){
                return response()->json(['status' => 'Token is Expired']);
            }else{
                return response()->json(['status' => 'Authorization Token not found']);
            }
        }
        return $next($request);
    }
}

Tambahkan Konfigurasi Kernel

Setelah itu edit Kernel.php di app/http/Kernel.php di bagian $routeMiddleware tambahkan sebaris code berikut :

'jwt.verify' => \App\Http\Middleware\JwtMiddleware::class,

Route API

Tambahkan baris berikut pada routes/api.php

Route::post('register', 'UserController@register');
Route::post('login', 'UserController@login');
Route::get('book', 'BookController@book');

Route::get('bookall', 'BookController@bookAuth')->middleware('jwt.verify');
Route::get('user', 'UserController@getAuthenticatedUser')->middleware('jwt.verify');

Buat Book Controller

Book merupakan sebuah Page, yang berisi method book dan bookall, dimana book tidak memerlukan Autentikasi sementara bookall memerlukan autentikasi.

php artisan make:controller BookController

Berikut isi app/Http/Controllers/BookController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Auth;

class BookController extends Controller
{
    public function book() {
        $data = "Data All Book";
        return response()->json($data, 200);
    }

    public function bookAuth() {
        $data = "Welcome " . Auth::user()->name;
        return response()->json($data, 200);
    }
}

Implementasi

Sekarang kita akan mencoba API yang telah kita buat :

Jalankan proyek laravel

php artisan serve

Buka API Tools dari Chrome, disini saya menggunakan Advanced REST client. Buka kedua url di bawah tanpa melakukan login

Request URL : http://localhost:8000/api/book

/book no-login Allow

Request URL : http://localhost:8000/api/bookall

/bookall no-login Not Allow

Lalu lakukan registrasi dan login untuk mencoba akses localhost:8000/api/bookall dengan link registrasi localhost:8000/api/register

POST register

Setelah registrasi coba melakukan login dengan email dan password yang sudah di daftarkan dengan link localhost:8000/api/login, setelah itu kita akan mendapat token, token tersebut akan kita gunakan untuk mengakses localhost:8000/api/bookall

Login

Setelah itu coba akses localhost:8000/api/bookall dengan menambahkan Bearer dan token di dalam header. Copy token yang di dapat saat melakukan login

bookall with Authorization

Coba juga untuk mengakses localhost:8000/api/user untuk mendapatkan informasi data user yang sedang login

User with Authorization

Catatan :

Untuk mengakses API dari proyek lain kita perlu untuk mengenable CORS protection menggunakan https://github.com/barryvdh/laravel-cors


Buat CRUD ToDos API

Buat Model + Migrate

php artisan make:model Todo -m

Ubah schema table todos

Schema::create('todos', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->integer('user_id');
    $table->char('title');
    $table->boolean('isCompleted');
    $table->timestamps();
});
php artisan migrate

Edit Model

protected $fillable= ['user_id','title','isCompleted'];

Membuat Routes

Sebelum mengatur method pada controller, kita tambahkan route resource pada routes/api.php

Route::resource('todo', 'TodoController')->middleware('jwt.verify');

Buat Controller CRUD

php artisan make:controller TodoController --resource

app/Http/Controllers/TodoController.php

<?php

namespace App\Http\Controllers;

use App\Todo;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;

class TodoController extends Controller
{
    public function index()
    {
        try {
            if (! $user = JWTAuth::parseToken()->authenticate()) {
                return response()->json(['user_not_found'], 404);
            }
        } catch (Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
            return response()->json(['token_expired'], $e->getStatusCode());
        } catch (Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
            return response()->json(['token_invalid'], $e->getStatusCode());
        } catch (Tymon\JWTAuth\Exceptions\JWTException $e) {
            return response()->json(['token_absent'], $e->getStatusCode());
        }
        $data=Todo::all();
        return response()->json($data);
    }

    public function create()
    {
        //
    }

    public function store(Request $request)
    {
        try {
            if (! $user = JWTAuth::parseToken()->authenticate()) {
                return response()->json(['user_not_found'], 404);
            }
        } catch (Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
            return response()->json(['token_expired'], $e->getStatusCode());
        } catch (Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
            return response()->json(['token_invalid'], $e->getStatusCode());
        } catch (Tymon\JWTAuth\Exceptions\JWTException $e) {
            return response()->json(['token_absent'], $e->getStatusCode());
        }
        
        $this->validate($request,[
            'user_id' => 'required',
            'title' => 'required',
        ]);
  
        Todo::create([
            'user_id' => $request->user_id,
            'title' => $request->title,
            'isCompleted' => 0
        ]);
        
        return response()->json(array('message'=>'Input Success'));
    }

    public function show($id)
    {
        //
    }

    public function edit($id)
    {
        //
    }

    public function update(Request $request, $id)
    {
        try {
            if (! $user = JWTAuth::parseToken()->authenticate()) {
                return response()->json(['user_not_found'], 404);
            }
        } catch (Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
            return response()->json(['token_expired'], $e->getStatusCode());
        } catch (Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
            return response()->json(['token_invalid'], $e->getStatusCode());
        } catch (Tymon\JWTAuth\Exceptions\JWTException $e) {
            return response()->json(['token_absent'], $e->getStatusCode());
        }
        
        $this->validate($request,[
            'user_id' => 'required',
            'title' => 'required',
        ]);
        
        $row=Todo::find($id);
        $row->user_id = $request->user_id;
        $row->title = $request->title;
        $row->isCompleted = $request->isCompleted==1?1:0;
        $row->save();
        
        return response()->json(array('message'=>'Update Success'));
    }

    public function destroy($id)
    {
        try {
            if (! $user = JWTAuth::parseToken()->authenticate()) {
                return response()->json(['user_not_found'], 404);
            }
        } catch (Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
            return response()->json(['token_expired'], $e->getStatusCode());
        } catch (Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
            return response()->json(['token_invalid'], $e->getStatusCode());
        } catch (Tymon\JWTAuth\Exceptions\JWTException $e) {
            return response()->json(['token_absent'], $e->getStatusCode());
        }
        
        $row=Todo::find($id);
        $row->delete();
        
        return response()->json(array('message'=>'Delete Success'));
    }
}

Sumber :

  1. https://medium.com/@newrey9227/belajar-autentikasi-api-di-laravel-menggunakan-jwt-c8c52d82e9d4

JWT (JSON Web Token)

Apa Itu JWT (JSON Web Token),  JWT (JESON Web Token), merupakan sebuah token berbentuk JSON, yang ukurannya sangat padat dari segi ukurannya, maksudnya yaitu, token JWT dapat dikirim melalui URL, Parameter Http POST atau di dalam Header Http, dan karena ukuran dari JWT sangat kecil maka dapat di transmisikan atau di proses lebih cepat, pada token JWT dapat diverifikasi karena sudah di-design  secara digital, dan pada token JWT juga bisa di-design dengan menggunakan secret (Algoritma HMAC) atau public / private key (Algoritma RSA).

Lebih kurang struktur dari JWT (JSON Web Token) yaitu seperti dibawah ini :

<base64-encoded header>.<base64-encoded playload >.<base64-encoded signature>

Pada struktur JWT (JSON Web Token) yang terdapat diatas terdapat tiga bagian yaitu header berisi algoritma dan jenis token, Playload berisi data, dan signature yaitu verify signature

Pada JWT (JSON Web Token) mengandung sebuah informasi yang terdapat dari user yang dibutuhkan, sehingga kita tidak perlu query ke database lebih dari satu kali. Nah dibawah ini contoh token yang terdapat pada JWT.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Pada kode token yang terdapat diatas terdapat tiga bagian yaitu :

  1. Header (Algoritma dan Jenis Token)
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  1. Playload (Data)
    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
  1. Signature (Verify Signature)
    TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Nah untuk lebih jelas tentang token yang terdapat diatas teman-teman bisa lihat di https://jwt.io/#debugger

Cara kerja dari JWT (JSON Web Token) yaitu ketika user berhasil melakukan login, maka server akan mengembalikan token JWT dan akan disimpan kembali kedalam Local Storage atau Cookies Browser, selanjutnya pada setiap request atau permintaan yang akan mengakses API haruslah menyatakan token tersebut. Dan pada umumnya token tersebut akan disertakan pada Authorization Header dengan Bearer schema, untuk contohnya bisa teman-teman lihat seperti dibawah ini :

Authorization: Bearer <token>

Proses yang terdapat diatas disebut juga dengan mekanisme autentikasi yang stateless, karena state pada saat user login tidak disimpan kedalam memori server, jadi pada saat request atau melakukan permintaan kepada API yang terproteksi akan dilakukan pengecekan apakah token JWT yang ada pada Authorization header valid atau tidak, jika valid maka request atau permintaan akan diijinkan atau diproses.

Jadi kesimpulannya pada JWT (JSON Web Token) semua informasi yang dibutuhkan untuk proses pengecekan tidak perlu lagi menggunakan perintah query, hanya dengan menggunakan kode token seperti contoh diatas.

Oke teman-teman itu lah pembahasan kita tentang Mengenal Apa Itu JWT (JSON Web Token), sekian dulu pembahasan kita kali ini tentang Mengenal Apa Itu JWT (JSON Web Token).

Sumber:

  1. http://www.kursuswebsite.org/mengenal-apa-itu-jwt-json-web-token/

VueJs-#11 Vue Vuetify Login & Crud Auth JWT

Apa itu Vuetify?

Vuetify merupakan sebuah framework desain komponen material untuk UI/UX yang berasosiasi langsung dengan Vue.js. Mirip seperti BootstrapVue, kira-kira begitulah.

Persiapan

vue create vue-vuetify
Options

Setelah selesai kita masuk direktori vue-vuetify, lalu tambahkan vuetify pada proyek kita.

cd vue-vuetify
vue add vuetify

Lalu jalankan service…

npm run serve
Vuetify Home

Halaman utamanya akan berubah seperti gambar di atas.

Karena nantinya kita akan membutuhkan icon :

npm install --save material-design-icons-iconfont

Edit src/plugins/vuetify.js

import 'material-design-icons-iconfont/dist/material-design-icons.css'
import Vue from 'vue';
import Vuetify from 'vuetify/lib';

Vue.use(Vuetify);

export default new Vuetify({
  icons: {
    iconfont: 'mdi',
  },
});

Planing saya akan membuat 5 Page sbb:

  1. Login integration to Laravel Login JWT API
  2. Home
  3. About
  4. ToDos Crud integration to Laravel ToDos API
  5. Profile

Karena kita akan mengakses API, kita memerlukan axios

npm install --save axios

Router

Kita akan menggunakan Auth akses permission, sehingga router.js menjadi seperti berikut :

import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";
import Login from "./components/Login.vue";
import Register from "./components/Register.vue";
import Profile from "./components/Profile.vue";
import Todos from "@/components/Todos";
import store from "./store.js";

Vue.use(Router);

let router =  new Router({
    mode: "history",
    base: process.env.BASE_URL,
    routes: [
        {
            path: "/home",
            name: "Home",
            component: Home,
            meta: {
                auth: true
            }
        },
        {
            path: '/about',
            name: 'about',
            component: () => import('./views/About.vue'),
            meta: {
                auth: true
            }
        },
        {
            path: "/",
            name: "Login",
            component: Login,
            meta: {
                guest: true
            }
        },
        {
            path: "/register",
            name: "Register",
            component: Register,
            meta: {
                guest: true
            }
        },
        {
            path: "/profile",
            name: "Profile",
            component: Profile,
            meta: {
                auth: true
            }
        },
        {
            path: '/todo',
            name: 'Todos',
            component: Todos,
            meta: {
                auth: true
            }
        }
    ]
});
router.beforeEach((to, from, next) => {
    if (to.matched.some(record => record.meta.auth)) {    
        if (store.state.isLogin) {
            next();
        } else {
            next({
                path: "/",
            });
        }
    } else if (to.matched.some(record => record.meta.guest)) {
        if (store.state.isLogin) {
            next({
                path: "/profile",
            });
        } else {
            next();
        }
    } else {
        next();
    }
});
export default router;

Membuat Store

store.js

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
    state: {
        isLogin: false,
        token:null,
        user:null,
    },
    mutations: {
        resetState (state) {
            state.isLogin=false;
            state.token=null;
            state.user=null;
        }
    },
    actions: {
        clearState ({ commit }) {
            commit('resetState');
        },
    }
});

Membuat Component NavBar

src/component/NavBar.vue

<template>
    <section>
        <v-navigation-drawer v-model="drawer" app>
        <v-list-item>
            <v-list-item-content>
            <v-list-item-title class="title">
                Application Name
            </v-list-item-title>
            <v-list-item-subtitle>
                subtext
            </v-list-item-subtitle>
            </v-list-item-content>
        </v-list-item>
        <v-divider></v-divider>
        <v-list dense nav>
            <v-list-item to="/Home">
                <v-list-item-action>
                    <v-icon>mdi-home</v-icon>
                </v-list-item-action>
                <v-list-item-content>
                    <v-list-item-title>Home</v-list-item-title>
                </v-list-item-content>
            </v-list-item>
            <v-list-item  to="/About">
                <v-list-item-action>
                    <v-icon>mdi-help-circle</v-icon>
                </v-list-item-action>
                <v-list-item-content>
                    <v-list-item-title>About</v-list-item-title>
                </v-list-item-content>
            </v-list-item>
        </v-list>
        <v-divider></v-divider>
        <v-list dense nav>
            <v-list-item  to="/Messages">
                <v-list-item-action>
                    <v-badge>
                        <template v-slot:badge>0</template>
                        <v-icon>mdi-email</v-icon>
                    </v-badge>
                </v-list-item-action>
                <v-list-item-content>
                    <v-list-item-title>Messages</v-list-item-title>
                </v-list-item-content>
            </v-list-item>
        </v-list>
        <template v-slot:append>
            <div class="pa-2">
                <v-btn @click="signoutButtonPressed" color="red darken-4 white--text" block>Logout</v-btn>
            </div>
        </template>
        </v-navigation-drawer>

        <v-app-bar app color="blue darken-4" dark>
            <v-app-bar-nav-icon @click.stop="drawer = !drawer"></v-app-bar-nav-icon>
            <v-toolbar-title>Application</v-toolbar-title>
            <div class="flex-grow-1"></div>

            <v-btn icon>
                <v-icon>mdi-heart</v-icon>
            </v-btn>
            <v-menu left bottom>
                <template v-slot:activator="{ on }">
                <v-btn icon v-on="on">
                    <v-icon>mdi-dots-vertical</v-icon>
                </v-btn>
                </template>

                <v-list>
                    <v-list-item  to="/Profile">
                        <v-list-item-action>
                            <v-icon>mdi-account-circle</v-icon>
                        </v-list-item-action>
                        <v-list-item-content>
                            <v-list-item-title>Profile</v-list-item-title>
                        </v-list-item-content>
                    </v-list-item>
                    <v-list-item  to="/Setting">
                        <v-list-item-action>
                            <v-icon>mdi-settings</v-icon>
                        </v-list-item-action>
                        <v-list-item-content>
                            <v-list-item-title>Setting</v-list-item-title>
                        </v-list-item-content>
                    </v-list-item>
                </v-list>
            </v-menu>
        </v-app-bar>
    </section>
</template>
<script>
export default {
    props: {
      source: String,
    },
    data: () => ({
      drawer: null,
    }),
    methods: {
        signoutButtonPressed() {
            this.$store.dispatch('clearState');
            this.$router.push({ name: "Login" });
        }
    }
};
</script>

Membuat Layout

src/App.vue

<template>
  <v-app teal>
    <NavBar v-show="getStatusLogin"/>
    <v-content>
      <router-view />
    </v-content>
    <v-footer color="blue darken-4" app>
      <span class="white--text">© 2019</span>
    </v-footer>
  </v-app>
</template>

<script>
import NavBar from "./components/NavBar";

export default {
    name: "App",
    components: {
        NavBar
    },
    data: () => ({
        //
    }),
    computed: {
        getStatusLogin: function () {
            return this.$store.state.isLogin;
        }
    }
};
</script>

Membuat Halaman Login

src/component/Login.vue

<template>
    <section>
        <v-container class="fill-height" fluid>
            <v-row align="center" justify="center">
                <v-col cols="12" sm="8" md="4">
                    <v-card class="mx-auto">
                        <v-toolbar color="primary" dark flat>
                            <v-toolbar-title>Login form</v-toolbar-title>
                        </v-toolbar>
                        <v-card-text>
                            <v-form ref="form" v-model="valid">
                                <v-text-field
                                    label="Email"
                                    v-model="email"
                                    prepend-icon="mail"
                                    type="text"
                                    :rules="emailRules"
                                    required
                                ></v-text-field>

                                <v-text-field
                                    label="Password"
                                    v-model="password"
                                    prepend-icon="lock"
                                    type="password"
                                    :rules="passwordRules"
                                    required
                                ></v-text-field>
                            </v-form>
                        </v-card-text>
                        <v-card-actions>
                            <div class="flex-grow-1"></div>
                            <v-btn color="primary" :disabled="!valid" @click="doLogin">Login</v-btn>
                            <v-btn color="primary" link to="/Register">Register</v-btn>
                        </v-card-actions>
                    </v-card>
                    <v-snackbar v-model="snackbar" bottom>
                        {{error_message}}
                    </v-snackbar>
                </v-col>
            </v-row>
        </v-container>
    </section>
</template>

<script>
export default {
    props: {
        source: String
    },
    data: () => ({
        drawer: null,
        snackbar:false,
        valid: true,
        email: "",
        emailRules: [
            v => !!v || "E-mail is required",
            v => /.+@.+\..+/.test(v) || "E-mail must be valid"
        ],
        password: "",
        passwordRules: [
            v => !!v || "Password is required",
            v => (v &amp;&amp; v.length >= 6) || "Password must be larger than 6 characters"
        ],
        result:null,
        error_message:""
    }),
    methods: {
        reset() {
            this.$refs.form.reset();
        },
        doLogin() {
            const axios = require('axios');
            axios.post("http://localhost:8000/api/login", {
                email:this.email,
                password:this.password,
            })
            .then(response => {
                this.result = response.data;
                if(this.result.token){
                    axios.get("http://localhost:8000/api/user", {
                        headers: {
                            Authorization: 'Bearer ' + this.result.token,
                        }
                    })
                    .then(response => {
                        this.result = response.data;
                        if(this.result.user){
                            this.$store.state.user=this.result.user;
                            this.$store.state.isLogin=true;
                            this.$store.state.jwt_token=this.result.token;
                            this.$router.push({ name: "Profile" });
                        }else{
                            this.error_message=response.data.status;
                            this.snackbar=true;
                        }
                    })
                }
            })
            .catch(e => {   
                if (e.response &amp;&amp; e.response.status === 400) {
                    this.error_message=e.response.data.error;
                    this.snackbar=true;
                }
            });
        }
    },
};
</script>

src/components/Profile.vue

<template>
    <section>
        <v-container class="pa-2" fluid>
            <v-row>
                <v-col>
                    <v-alert v-if="errors" type="error" class="mt-4">
                        {{errors}}
                    </v-alert>
                    <v-card color="#385F73" dark>
                        <v-card-text class="white--text">
                        <div class="headline mb-2" v-if="user">{{user.name}}</div>
                            Listen to your favorite artists and albums whenever and wherever, online and offline.
                        </v-card-text>

                        <v-card-actions>
                            <v-btn text>Listen Now</v-btn>
                        </v-card-actions>
                    </v-card>
                </v-col>
            </v-row>
        </v-container>
    </section>
</template>

<script>
export default {
    data: () => ({
        user:null,
        result:null,
        errors:null
    }),
    mounted: function () {
        this.user=this.$store.state.user;
    }
}
</script>

Todo Crud

Membuat Module

src/modules/todo.js

const todo = {
    namespaced: true,
    state: {
        listData: [
            { id:1, user_id:1, title: 'Example', },
        ]
    },
    getters: {
        getRowById: (state) => (id) => {
            return state.listData.find((obj) => {
                return obj.id===id
            })
        }
    },
    mutations: {
        ADD_ROW: (state, payload) => {
            state.listData.push(payload.row);
        },
        EDIT_ROW(state, payload){
            var listData = state.listData
            listData.forEach((element, index) => {
                if(element.id === payload.id) {
                    listData[index] = payload;
                }
            });
            state.listData = listData;
        },
        REMOVE_ROW(state, payload){
            state.listData.splice(state.listData.indexOf(payload.row), 1);
        },
    },
    actions: {
        save_row({ commit, rootState }, payload) {
            rootState.isLoading = true;
            setTimeout(() => {
                commit('ADD_ROW', payload)
                rootState.isLoading = false
            }, 1000)
        },
        edit_row({commit}, payload){
            commit('EDIT_ROW', payload)
        },
        remove_row({commit}, payload){
            commit('REMOVE_ROW', payload)
        },
    }
}

export default todo;

Setelah itu tambahkan modules todo pada store.js

import Vue from "vue";
import Vuex from "vuex";
import todo from './modules/todo';

Vue.use(Vuex);

export default new Vuex.Store({
    state: {
        isLoading:false,
        isLogin: false,
        token:null,
        user:null,
    },
    mutations: {
        resetState (state) {
            state.isLoading=false;
            state.isLogin=false;
            state.token=null;
            state.user=null;
        }
    },
    actions: {
        clearState ({ commit }) {
            commit('resetState');
        },
    },
    modules: {
        todo,
    }
});

Membuat Page

src/components/Todos.vue

<template>
    <section>
        <v-form v-model="valid">
            <v-container>
                <v-row>
                    <v-col cols="8" md="8">
                    <v-text-field
                        v-model="title"
                        :counter="10"
                        label="Enter what todo"
                        required
                    ></v-text-field>
                    </v-col>

                    <v-col cols="4" md="4">
                        <v-btn class="ma-2 red white--text" @click="addItem">
                            Add
                            <v-icon right dark>mdi-cloud-upload</v-icon>
                        </v-btn>
                    </v-col>
                </v-row>
                <v-row>
                    <v-col cols="12" md="12">
                        <v-list dense>
                            <v-list-item-group color="primary">
                                <v-list-item v-for="todo in todos" :key="todo.id">
                                    <v-list-item-content>
                                        <v-list-item-subtitle>
                                            <v-row>
                                                <v-col cols="2" md="2">
                                                    <v-btn @click="removeItem(todo)">X</v-btn>
                                                </v-col>
                                                <v-col cols="8" md="8">
                                                    <v-chip color="red" text-color="white">
                                                    {{todo.title}}
                                                    </v-chip>
                                                </v-col>
                                                <v-col cols="2" md="2">
                                                    <v-switch value :input-value="todo.isCompleted==1?true:false" 
                                                        @change="updateItem(todo,$event)"></v-switch>
                                                </v-col>
                                            </v-row>
                                        </v-list-item-subtitle>
                                    </v-list-item-content>
                                </v-list-item>
                            </v-list-item-group>
                        </v-list>
                    </v-col>
                </v-row>
            </v-container>
        </v-form>
    </section>
</template>

<script>
import axios from 'axios';

export default {
    data: () => ({
        drawer: null,
        snackbar:false,
        valid: true,
        title: "",
        message: "",
        checkboxes:[]
    }),
    created: function () {
        axios.get("http://localhost:8000/api/todo", {
            headers: {
                Authorization: 'Bearer ' + this.$store.state.token
            }
        })
        .then(response => {
            this.$store.state.todo.listData=response.data;
        });
    },
    computed: {
        todos(){
            return this.$store.state.todo.listData;
        }
    },
    methods: {
        addItem() {
            axios.post("http://localhost:8000/api/todo", {
                title:this.title,
                user_id:this.$store.state.user.id
            },
            {
                headers: {
                    Authorization: 'Bearer ' + this.$store.state.token
                }
            })
            .then(response => {
                this.message = response.data.status;
                this.$store.dispatch('todo/save_row', {
                    row:response.data.row,
                });
            });
        },
        updateItem(row, value) {
            axios.put("http://localhost:8000/api/todo/"+row.id, {
                isCompleted:value?1:0,
            },
            {
                headers: {
                    Authorization: 'Bearer ' + this.$store.state.token
                }
            })
            .then(response => {
                this.message = response.data.message;
                this.$store.dispatch('todo/edit_row', {
                    row: response.data.row
                });
            });
        },
        removeItem(row) {
            axios.delete("http://localhost:8000/api/todo/"+row.id,
            {
                headers: {
                    Authorization: 'Bearer ' + this.$store.state.token
                }
            })
            .then(response => {
                this.message = response.data.message;
                this.$store.dispatch('todo/remove_row', {
                    row: row,
                });
            });
        },
    }
}
</script>

Sumber

  1. https://github.com/sudheerj/vuejs-todolist/blob/master/src/store.js