Bài 17: Phân quyền với Laravel và VueJS
Mình đã cập nhật lại tất cả các bài với các thay đổi ở hiện tại ở năm 2024: Vue 3, Vite, Laravel 11x,...
Cập nhật gần nhất: 09/06/2024
Chào mừng các bạn quay trở lại với series học Laravel với VueJS của mình, ở bài này mình sẽ hướng dẫn các bạn các phân quyền bằng Laravel và VueJS mà không cần cài đặt thêm bất kì package hay library nào khác.
Ở bài này ta sẽ tiếp nối bài trước gọi Laravel với axios từ VueJS, và ta sẽ phát triển thêm chút, chỉ cho phép Admin được thêm sửa xoá, còn user thường thì chỉ được xem danh sách sản phẩm thôi.
Trong bài hôm nay ta sẽ sử dụng Laravel Gate để check phần phân quyền nhé, ta sẽ làm nó ở mức độ cơ bản để mọi người cung hiểu được nhé. Lên thuyền thôi nào 🚀🚀
Setup
Ở bước setup này mình sẽ làm qua thật nhanh các bước setup để ta có kết quả như bài trước, sau đó ta tập trung vào phần phân quyền nhé. Nếu có gì thắc mắc ở bước setup các bạn giúp mình xem lại bài trước hoặc comment ở bài này cũng được nha
Laravel
Đầu tiên ta tạo project mới:
composer create-project laravel/laravel laravel-rbac-simple
# hoặc
laravel new laravel-rbac-simple
Khi được hỏi thì ta cứ chọn mặc định nhé:
Tiếp đó ta cài phần setup cho VueJS nha:
composer require laravel/ui
php artisan ui vue --auth
npm install
Sau đó các bạn update lại database ở .env
cho đúng với của các bạn nha, ví dụ của mình:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3307
DB_DATABASE=laravel
DB_USERNAME=myuser
DB_PASSWORD=myuserpass
Nhớ tạo database tên là
laravel
trước nhé để tí ta còn migrate
Sau đó ta tạo model Product và migration cho nó:
php artisan make:model Product -m
Sau đó chúng ta vào database/migrations/create_products_table.php
và sửa lại hàm up()
như sau:
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->double('price');
$table->timestamps();
});
}
Tiếp theo ta vào database/migrations/create_users_table.php
và sửa lại hàm up()
như sau:
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->boolean('is_admin')->default(false); // Thêm trường này
$table->timestamps();
});
Ở trên ta thêm trường is_admin
để đánh dấu user này có phải admin hay không
Tiếp đó ở database/seeders/DatabaseSeeder.php
và sửa lại hàm run()
như sau:
public function run(): void
{
// User::factory(10)->create();
User::factory()->create([
'name' => 'Admin User',
'email' => 'admin@example.com',
'password' => bcrypt('12345678'),
'is_admin' => true,
]);
}
Ở đây tí nữa ta sẽ tạo sẵn một admin user vào DB lúc migrate
Bây giờ ta chạy migration nhé:
php artisan migrate --seed
Thấy in ra như sau là ổn rồi 😎:
Tiếp theo chúng ta vào app/Models/Product.php
và sửa lại như sau
...
class Product extends Model
{
use HasFactory;
protected $fillable = [
'name', 'price'
];
}
Ở bài này ta sẽ dùng Laravel Resource Controller cho tiện nha, các bạn chạy command sau:
php artisan make:controller ProductController --resource
Sau đó các bạn mở file routes/web.php
và sửa lại như sau:
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
})->middleware('auth');
Auth::routes();
Route::resource('products', App\Http\Controllers\ProductController::class)->middleware('auth');
Tiếp đó các bạn tạo cho mình file resources/views/products.blade.php
:
@extends('layouts.app')
Rất đơn giản không có gì mấy ở file này 😂😂
Ở file resources/views/layouts/app.blade.php
, các bạn sửa lại như sau:
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">
<!-- Scripts -->
@guest
@vite(['resources/sass/app.scss'])
@else
@vite(['resources/sass/app.scss', 'resources/js/app.js'])
@endguest
</head>
<body>
<div>
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav me-auto">
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ms-auto">
<!-- Authentication Links -->
@guest
@if (Route::has('login'))
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
@endif
@if (Route::has('register'))
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
</li>
@endif
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }}
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
</div>
</nav>
<main class="py-4" id="app">
@yield('content')
</main>
</div>
</body>
</html>
Ở trên mình thêm vào đoạn @vite(['resources/sass/app.scss'])
ở <head>
, lí do là vì tí nữa ta sẽ mount app VueJS từ file app.js, nếu ta không sửa thì tí nữa trang Login/Register cũng sẽ hiển thị component của VueJS
Tiếp tục ở file app/Http/Controllers/Auth/LoginController.php
và RegisterController.php
ta sửa như sau để sau khi login/register thành công nó sẽ trả về trang chủ nhé:
protected $redirectTo = '/';
Cuối cùng là ta update lại file app/Http/Controllers/ProductController.php
như bài trước:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Product;
class ProductController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
return Product::orderBy('created_at', 'desc')->paginate(5);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|min:5',
'price' => 'required|numeric|gt:0',
]);
$product = Product::create([
'name' => $request->input('name'),
'price' => $request->input('price'),
]);
return response([
'product' => $product
], 200);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
$validated = $request->validate([
'name' => 'required|min:5',
'price' => 'required|numeric|gt:0',
]);
$product = Product::find($id);
$product->name = $request->input('name');
$product->price = $request->input('price');
$product->save();
return response([
'product' => $product
], 200);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
$product = Product::find($id);
$product->delete();
return response([
'result' => 'success'
], 200);
}
}
Vue
Tiếp theo tới phần setup cho VueJS
Các bạn tạo cho mình file resources/js/components/ProductList.vue
:
<template>
<div class="api-calling container mt-5">
<h1>Create Product</h1>
<transition name="fade">
<div class="alert alert-danger alert-dismissible fade show" role="alert" v-if="error">
<b>{{ error.message }}</b>
<ul>
<li v-for="(errorName, index) in error.errors" :key="index">
{{ errorName[0] }}
</li>
</ul>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"
@click="error = null"></button>
</div>
</transition>
<div class="form-group">
<label>Name</label>
<input v-model="product.name" type="text" class="form-control" placeholder="Name...">
</div>
<div class="form-group">
<label>Price</label>
<input v-model="product.price" type="text" class="form-control" placeholder="Price...">
</div>
<button class="btn btn-primary mt-2" @click="createProduct">Create</button>
<hr>
<h1>List Products</h1>
<table class="table">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Name</th>
<th scope="col">Price</th>
<th scope="col">Actions</th>
</tr>
</thead>
<transition-group name="list" tag="tbody">
<tr v-for="(product, index) in listProducts.data" :key="product.id">
<th scope="row">{{ product.id }}</th>
<td v-if="!product.isEdit">
{{ product.name }}
</td>
<td v-else>
<input type="text" v-model="selectedProduct.name" class="form-control">
</td>
<td v-if="!product.isEdit">
{{ product.price }}
</td>
<td v-else>
<input type="text" v-model="selectedProduct.price" class="form-control">
</td>
<td v-if="!product.isEdit">
<button class="btn btn-primary" @click="selectProduct(product)">Edit</button>
<button class="btn btn-danger ms-2" @click="deleteProduct(product, index)">Delete</button>
</td>
<!-- ... -->
<td v-else>
<button class="btn btn-primary" @click="updateProduct(index)">Save</button>
<button class="btn btn-danger ms-2" @click="product.isEdit = false">Cancel</button>
</td>
</tr>
</transition-group>
</table>
<div>
{{ listProducts.from }} - {{ listProducts.to }} of {{ listProducts.total }}
</div>
<ul class="pagination">
<li class="page-item" :class="{ 'disabled': listProducts.prev_page_url === null }"
@click="listProducts.prev_page_url && getListProducts(listProducts.current_page - 1)">
<a class="page-link" href="#">Previous</a>
</li>
<li class="page-item" v-if="listProducts.prev_page_url" @click="getListProducts(listProducts.current_page - 1)">
<a class="page-link" href="#">{{ listProducts.current_page - 1 }}</a>
</li>
<li class="page-item active">
<a class="page-link" href="#">{{ listProducts.current_page }}</a>
</li>
<li class="page-item" v-if="listProducts.next_page_url" @click="getListProducts(listProducts.current_page + 1)">
<a class="page-link" href="#">{{ listProducts.current_page + 1 }}</a>
</li>
<li class="page-item" :class="{ 'disabled': listProducts.next_page_url === null }"
@click="listProducts.next_page_url && getListProducts(listProducts.current_page + 1)">
<a class="page-link" href="#">Next</a>
</li>
</ul>
</div>
</template>
<script setup>
import axios from 'axios'
import { onBeforeMount, ref } from 'vue';
const product = ref({
name: '',
price: 0
})
const listProducts = ref({})
const error = ref(null)
const selectedProduct = ref(null)
onBeforeMount(() => {
getListProducts()
})
const createProduct = async () => {
try {
error.value = null
const response = await axios.post('/products', {
name: product.value.name,
price: product.value.price
})
listProducts.value.data.unshift({
...response.data.product,
isEdit: false
})
// reset giá trị form về ban đầu
product.value = {
name: '',
price: 0
}
} catch (e) {
error.value = e.response.data
}
}
const getListProducts = async (page = 1) => {
try {
const response = await axios.get('/products?page=' + page)
listProducts.value = response.data
listProducts.value.data.forEach(item => {
item.isEdit = false
})
} catch (e) {
error.value = e.response.data
}
}
const selectProduct = (product) => {
product.isEdit = true
selectedProduct.value = { ...product }
}
const updateProduct = async (index) => {
try {
const response = await axios.put('/products/' + selectedProduct.value.id, {
name: selectedProduct.value.name,
price: selectedProduct.value.price
})
listProducts.value.data[index].name = response.data.product.name
listProducts.value.data[index].price = response.data.product.price
listProducts.value.data[index].isEdit = false
} catch (e) {
error.value = e.response.data
}
}
const deleteProduct = async (product, index) => {
try {
await axios.delete('/products/' + product.id)
listProducts.value.data.splice(index, 1)
} catch (e) {
error.value = e.response.data
}
}
</script>
<style lang="scss" scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
// ------ THÊM VÀO ĐOẠN BÊN DƯỚI
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
</style>
Ở file resources/js/app.js
ta sửa như sau:
import './bootstrap';
import { createApp } from 'vue';
import ProductList from './components/ProductList.vue';
const app = createApp(ProductList);
app.mount('#app');
Cuối cùng là ta start project lên thôi, ta chạy 2 command sau mỗi cái ở 1 terminal nhé:
php artisan serve
npm run dev
Sau đó truy cập từ trình duyệt ở địa chỉ http://localhost:8000
, thấy lên trang Login là ngon rồi:
Login với account admin@example.com/12345678
như khi nãy ta tạo ở Seeder sau đó ta sẽ vào được app nha:
Ở bước này các bạn chạy lại các thao tác thêm sửa xoá xem oke không nhé
Phân quyền
Ở bài này ta sẽ dùng Laravel Gate để làm phân quyền nhé
Đầu tiên ở file app/Providers/AppServiceProvider.php
ta tạo các quyền ta muốn:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Gate;
use App\Models\User;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Gate::define('create-products', function (User $user) {
return $user->is_admin;
});
Gate::define('update-products', function (User $user) {
return $user->is_admin;
});
Gate::define('delete-products', function (User $user) {
return $user->is_admin;
});
}
}
Ở trên ta có 3 quyền là create-products/update-products/delete-products
, cả 3 quyền đó đều yêu cầu user phải là admin is_admin=true
Sau đó ta sửa lại app/Http/Controllers/ProductController.php
như sau:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Product;
use Illuminate\Support\Facades\Gate;
class ProductController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
return Product::orderBy('created_at', 'desc')->paginate(5);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
if (!Gate::allows('create-products')) {
return response()->json(['message' => 'Forbidden'], 403);
}
$validated = $request->validate([
'name' => 'required|min:5',
'price' => 'required|numeric|gt:0',
]);
$product = Product::create([
'name' => $request->input('name'),
'price' => $request->input('price'),
]);
return response([
'product' => $product
], 200);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
if (!Gate::allows('update-products')) {
return response()->json(['message' => 'Forbidden'], 403);
}
$validated = $request->validate([
'name' => 'required|min:5',
'price' => 'required|numeric|gt:0',
]);
$product = Product::find($id);
$product->name = $request->input('name');
$product->price = $request->input('price');
$product->save();
return response([
'product' => $product
], 200);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
if (!Gate::allows('delete-products')) {
return response()->json(['message' => 'Forbidden'], 403);
}
$product = Product::find($id);
$product->delete();
return response([
'result' => 'success'
], 200);
}
}
Ở trên các bạn để ý rằng, ở mỗi method trong controller ta đều có bước đầu tiên là check quyền, dùng Gate::allows('...')
để check xem user hiện tại có quyền tương ứng hay không, nếu có thì mới thực hiện tiếp, còn không thì trả về 403
Giờ nếu ta quay lại trình duyệt, Logout ra ngoài, sau đó tạo 1 tài khoản bất kì, mặc định is_admin=false
(user thường), sau đó vào trong app Vue và thực hiện bất kì thao tác thêm sửa xoá nào sẽ thấy báo lỗi:
Bước tiếp theo là với user thường thì ta cần ẩn hết form tạo product, các nút Edit/Delete đi, để làm điều đó thì ta cần phải lấy được thông tin user hiện tại và truyền vào phía app VueJS để nó biết, có 2 cách:
- gọi API để lấy thông tin user
- dùng trực tiếp auth user của Laravel
Rõ ràng ta nên chọn cách số 2, không cần phải gọi API, ngay lập tức khi app Vue load lên thì user đã có ở đó rồi, vì Laravel render file blade, file blade load app.js
và tạo app Vue, mà ở file blade là ta có thể lấy được thông tin user rồi
Giờ ta sửa lại file resources/views/products.blade.php
như sau nhé:
@extends('layouts.app')
@section('content')
<script>
window.__user__ = @json($user);
</script>
@endsection
Tiếp đó ta sửa lại file routes/web.php
như sau:
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
$user = auth()->user();
return view('products', compact('user')); // -> ở đây
})->middleware('auth');
Auth::routes();
Route::resource('products', App\Http\Controllers\ProductController::class)->middleware('auth');
ở trên các bạn thấy, ở file route, thì ta đã lấy ra auth()->user()
và return nó về cùng với view, ở file blade thì ta serialize
nó thành json và gán nó vào biến global __user__
Giờ việc của ta đơn giản là vào ProductList.vue
lấy ra user lưu lại thôi:
<script setup>
...
const user = ref(window.__user__)
</script>
Sau đó F5 chạy app lên, mở Vue Devtool ta sẽ thấy thông tin user ở đó:
Giờ ta chỉ cần ẩn hết những phần UI để nếu user không phải admin thì sẽ không xem được, source code đầy đủ như sau:
<template>
<div class="api-calling container mt-5">
<template v-if="user.is_admin">
<h1>Create Product</h1>
<transition name="fade">
<div class="alert alert-danger alert-dismissible fade show" role="alert" v-if="error">
<b>{{ error.message }}</b>
<ul>
<li v-for="(errorName, index) in error.errors" :key="index">
{{ errorName[0] }}
</li>
</ul>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"
@click="error = null"></button>
</div>
</transition>
<div class="form-group">
<label>Name</label>
<input v-model="product.name" type="text" class="form-control" placeholder="Name...">
</div>
<div class="form-group">
<label>Price</label>
<input v-model="product.price" type="text" class="form-control" placeholder="Price...">
</div>
<button class="btn btn-primary mt-2" @click="createProduct">Create</button>
<hr>
</template>
<h1>List Products</h1>
<table class="table">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Name</th>
<th scope="col">Price</th>
<th scope="col" v-if="user.is_admin">Actions</th>
</tr>
</thead>
<transition-group name="list" tag="tbody">
<tr v-for="(product, index) in listProducts.data" :key="product.id">
<th scope="row">{{ product.id }}</th>
<td v-if="!product.isEdit">
{{ product.name }}
</td>
<td v-else>
<input type="text" v-model="selectedProduct.name" class="form-control">
</td>
<td v-if="!product.isEdit">
{{ product.price }}
</td>
<td v-else>
<input type="text" v-model="selectedProduct.price" class="form-control">
</td>
<template v-if="user.is_admin">
<td v-if="!product.isEdit">
<button class="btn btn-primary" @click="selectProduct(product)">Edit</button>
<button class="btn btn-danger ms-2" @click="deleteProduct(product, index)">Delete</button>
</td>
<!-- ... -->
<td v-else>
<button class="btn btn-primary" @click="updateProduct(index)">Save</button>
<button class="btn btn-danger ms-2" @click="product.isEdit = false">Cancel</button>
</td>
</template>
</tr>
</transition-group>
</table>
<div>
{{ listProducts.from }} - {{ listProducts.to }} of {{ listProducts.total }}
</div>
<ul class="pagination">
<li class="page-item" :class="{ 'disabled': listProducts.prev_page_url === null }"
@click="listProducts.prev_page_url && getListProducts(listProducts.current_page - 1)">
<a class="page-link" href="#">Previous</a>
</li>
<li class="page-item" v-if="listProducts.prev_page_url" @click="getListProducts(listProducts.current_page - 1)">
<a class="page-link" href="#">{{ listProducts.current_page - 1 }}</a>
</li>
<li class="page-item active">
<a class="page-link" href="#">{{ listProducts.current_page }}</a>
</li>
<li class="page-item" v-if="listProducts.next_page_url" @click="getListProducts(listProducts.current_page + 1)">
<a class="page-link" href="#">{{ listProducts.current_page + 1 }}</a>
</li>
<li class="page-item" :class="{ 'disabled': listProducts.next_page_url === null }"
@click="listProducts.next_page_url && getListProducts(listProducts.current_page + 1)">
<a class="page-link" href="#">Next</a>
</li>
</ul>
</div>
</template>
<script setup>
import axios from 'axios'
import { onBeforeMount, ref } from 'vue';
const product = ref({
name: '',
price: 0
})
const listProducts = ref({})
const error = ref(null)
const selectedProduct = ref(null)
const user = ref(window.__user__)
onBeforeMount(() => {
getListProducts()
})
const createProduct = async () => {
try {
error.value = null
const response = await axios.post('/products', {
name: product.value.name,
price: product.value.price
})
listProducts.value.data.unshift({
...response.data.product,
isEdit: false
})
// reset giá trị form về ban đầu
product.value = {
name: '',
price: 0
}
} catch (e) {
error.value = e.response.data
}
}
const getListProducts = async (page = 1) => {
try {
const response = await axios.get('/products?page=' + page)
listProducts.value = response.data
listProducts.value.data.forEach(item => {
item.isEdit = false
})
} catch (e) {
error.value = e.response.data
}
}
const selectProduct = (product) => {
product.isEdit = true
selectedProduct.value = { ...product }
}
const updateProduct = async (index) => {
try {
const response = await axios.put('/products/' + selectedProduct.value.id, {
name: selectedProduct.value.name,
price: selectedProduct.value.price
})
listProducts.value.data[index].name = response.data.product.name
listProducts.value.data[index].price = response.data.product.price
listProducts.value.data[index].isEdit = false
} catch (e) {
error.value = e.response.data
}
}
const deleteProduct = async (product, index) => {
try {
await axios.delete('/products/' + product.id)
listProducts.value.data.splice(index, 1)
} catch (e) {
error.value = e.response.data
}
}
</script>
<style lang="scss" scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
// ------ THÊM VÀO ĐOẠN BÊN DƯỚI
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
</style>
Ở trên nếu các bạn để ý thì mình có gom nhóm các phần UI mà chỉ admin truy cập được và đặt vào <template>
sau đó thêm v-if="user.is_admin"
cho nó là xong
Kết quả cuối cùng cho ta như sau:
Kết luận
Vậy là ta đã hoàn thành bài về phân quyền với Laravel và VueJS rồi, mặc dù phần logics trong bài này cũng khá là dễ, nhưng hi vọng nó đã cho các bạn được ý tưởng cách áp dụng vào thực tế như thế nào
Ở một bài khác mình sẽ hướng dẫn các bạn cách sử dụng Laravel Passport
dùng để authenticate ở mức chi tiết và mạnh mẽ hơn nhé.
Cám ơn các bạn đã theo dõi, có gì thắc mắc để lại dưới comment cho mình nhé ^^!
All rights reserved
Bình luận
Series rất hay. Đều tay nhé bạn
Anh ơi hàm index() thiếu request rồi ạ
Phần đó trong bài a ghi là để cho mn tự test rồi đó e, hiện mình mới chỉ authorize mỗi khi truy cập vào view
, e có thể authorizeRole ở trong hàm thêm user, nếu thoả mãn quyền nào đó thì cho thêm nhé 
home
thôi.Cho e hỏi 3 dòng này có mục đích gì trong hàm run() ạ..Tks a
oke e
mai đức trung best logic
Auth::user()->load('roles'); đây có phải từ model user() trỏ tới roles() phải k a
Đúng rồi bạn. Cái này là load relation của user này đấy bạn. Trong Eloquent thì hay sử dụng
with
đó. Kiểu User::with('roles') bạn ạ.tks a
Cảm ơn series của bạn. Cho mình hỏi vấn đề này. Khi mình vừa tạo component mới và đã thêm nó vào /resources/.../app.js và đã thêm vào file blade. Sau đó mình chạy lên npm run dev thì có lỗi " [Vue warn]: Unknown custom element..... " Mình đã gặp lỗi này 2 lần khi lần đầu tiên tạo mới component sau khi khởi động lại máy. Mình đã kiểm tra và vấn đề được sửa khi mình xóa Vue.component(....require()...) ở trong file public/js/app,js và chạy lại lênh npm run dev để file này tự update lại code mình vừa xóa đi.sau khi thử thì không thấy báo lỗi " [Vue warn]: Unknown custom element..... ". Cho mình hỏi vấn đề này xảy ra vì lí do gì.trong khi mình chỉ xóa cái được tạo trong public/js/app,js và chạy lại lệnh npm run dev Cảm ơn bạn
app.js
thì bạn cần thêmdefault
như sau nhé:Mình đã làm như vậy. Hoàn toàn đúng, và vấn đề chỉ được khắc phục khi mình vào file public/js/app,js và sửa linh tinh trong đấy và chạy lại npm run dev
bạn nhớ luôn chạy
. Dù sao chạy đc thì cũng tốt rồi 
npm run watch
thì mỗi thứ bạn sửa sẽ được compile lại ngayĐiều kiện này luôn ko thỏa mãn nên tôi ko thể check đc quyền, tôi đã console.log cái điều kiện đó để xem nhưng nó undefined

Nếu bạn vẫn bị lỗi này, mình khuyến khích bạn clone repo sau về: https://github.com/maitrungduc1410/viblo-repo Mình mới check + update lại code cho bài này. Bạn clone code về, Setup database. sau đó chạy:
Rồi login với các account được tạo sẵn ở file
databases/seeds/UserTableSeeder.php
nhéCách này khi kết hợp nhiều Router của Vue thì khi nhấn F5 lại sẽ mất Router của Vue đi .. nó sẽ chỉ ăn vào file routers/web.php phải không b ?
ngay tại thời điểm bạn F5 thì sẽ gọi vào route ở bên Laravel trước (ở `routes/web.php), vì lúc đó Vue còn chưa được load lên (và lúc này đương nhiên chưa có vue-router) -> trình duyệt cố gắng tìm route ở phía backend (laravel) trước.
Để fix chuyện này thì bạn dùng router::any nhé. Ở trên stackoverflow có rất nhiều solutions cho bạn
hình như chưa có phần router anh ơi. =))))
phần đó a tách ra 1 bài ở đây nhé e
ok a. thanks. ((((:
a ơi hàmm updatee() function trong UserController anh viết như thế nào ạ? E lam nhu the nay nhung no chi update len database nhung khi click save button e bi loi [Vue warn]: Error in v-on handler (Promise/async): "TypeError: Cannot read property 'data' of undefined"
@maitrungduc1410 ko biet em viet phan update the nay dung chua a
@herang0412 chưa đúng đâu em,
)))))) (a ko thấy e khai báo response ở đâu trong method
axios.put
nó trả về promise e cần phảiawait
nó hoặc gọi.then
, thêm vào nữa tự nhiênresponse
lấy ở đâu ra đó??????updateUser
cả)Code bên trên e sửa lại như sau:
em mới học php, cho em hỏi là return null !== là sao ạ? có phải kiểu return null if... else không ạ?
@maitrungduc1410 em thì cũng hiểu anh viết gì á mà tại em newbie ở php nên cũng hơi bối rối tí á anh. Em cảm ơn anh vì bài viết nha
@noobmaster69 okie e nhé
