Laravel - Middleware triggered while it should not? - laravel

I'm using Laravel 5.4 and Vuejs 2.3 along with VueRouter
I have a website that consists of two parts
example.com
example.com/tools
Problem
If I am at example.com/tools and if I reload the page to the same address example.com/tools the middleware checkUsersPublic is triggered whereas it is only linked to /.
Edit
If I remove { path: '/tools', component: Stats } from VueRouter.js the middleware is not triggered anymore
Question
What should I do change so the middleware is not triggered ?
Routes in Laravel
Route::get('/', 'HomeController#home')->middleware('checkUserPublic')
Route::get('/tools', 'StatsController#home')->middleware('checkUserStats')
Vue router
const routes = [
{ path: '/', component: App },
{ path: '/tools', component: Stats },
];
Middleware checkUsersPublic
class checkUsersPublic
{
public function handle($request, Closure $next)
{
if(auth()->check() && auth()->user()->public_only) {
return $next($request);
}
if(Auth::check()) {Auth::logout();}
return $next($request);
}
}

Instead of doing the middleware in Laravel I did it within vue-router in the beforeEnter guard

Related

Prevent users to access some routes in Laravel and Vue

I'm using building an SPA using Laravel and Vue and I don't want users to access the /products/create route I've tried using Laravel middlewares but it didn't help
Here's my App.vue component
<template>
<div>
<Navbar :name="user.name"/>
<router-view></router-view>
</div>
</template>
<script>
import Navbar from "./Navbar";
export default {
name: "App",
props: [
'user'
],
components: {
Navbar,
},
created() {
window.axios.interceptors.request.use(config => {
config.headers.common['Authorization'] = 'Bearer ' + this.user.api_token;
return config;
});
},
}
</script>
IsAdmin.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class IsAdmin
{
public function handle($request, Closure $next)
{
if (! Auth::user()->isAdmin) {
return response()->json(['error' => 'Unauthorized'], 403);
}
return $next($request);
}
}
How do I redirect non-authorized users to a 404 page?
For vue routes protection
To protect vue routes you can use navigation guards which are a specific feature within Vue Router that provide additional functionality pertaining to how routes get resolved.
You must be using vue-router package to use routes in vuejs app
In src/router/index.js , you can add route guard as following
import Vue from "vue";
import Router from "vue-router";
import Main from "#/components/Main";
import Products from "#/components/Products";
import Create from "#/components/Create";
import Show from "#/components/Show";
import Unauthorised from "#/components/Unauthorised";
//use vue router
Vue.use(Router);
//init Router and define some routes
export default new Router({
routes: [
{
path: '/',
name: 'Main',
component: Main
},
{
path: '/products',
name: 'Products',
component: Products
},
{
path: '/create',
name: 'Create',
component: Create
},
{
path: '/show',
name: 'Show',
component: Show
},
{
path: '/unauthorised',
name: 'Unauthorised',
component: Unauthorised
}
]
})
//apply route guard
router.beforeEach((to, from, next) => {
//list of blocked routes
const protectedRoutes = ['/products', '/create'];
//the route user is trying to access is in blocked routes list
if (protectedRoutes.includes(to.path)) {
//redirect to route having unauhorised message page
return next('/unauthorised');
}
)
else
{
// otherwise allow user to access route
return next();
}
In this example there are five routes i.e / , /products , /create , /show and last one /unauthorised to show error. Here, if any user tries to access routes listed in $protectedRoutes then they will be redirected to /unauthorised route otherwise allowed to access other routes
you can learn more about vue router guard here and about vue-router here.Additionally, you can guard your routes based on user roles or any other conditions.I recommend you to use vuex to manage user state and manage routes access based on role stored on user state
You're not providing enough information, but the way I do it is using Laravel policies.
I would setup a policy for Products like this:
namespace App\Policies;
use App\Product;
use App\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class ProductPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can create products.
*
* #param \App\User $user
* #return mixed
*/
public function create(User $user)
{
return $user->hasPermissionTo('create products');
}
}
Register your policies in the App\Providers\AuthServiceProvider.php
protected $policies = [
'App\Product' => 'App\Policies\ProductPolicy',
];
Then in your Product controller you will need to add this in order to got through the authorization process:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Product;
class ProductController extends Controller
{
public function store(Request $request)
{
$this->authorize('create', Product::class)
// The current user is authorized to make this request.
}
}
You will probably like to prevent unauthorized users to even have access your vue route where you create products. In order to do so you would have to pass the users permissions into your vue app.
return [
'name' => $user->name,
'permissions' => [
'createProducts' => $user->can('create', \App\Product::class)
]
}
Then in your vue app:
<router-link v-if="user.permissions.createProducts" to="/products/create">
New Product
</router-link>

Laravel VueJs SPA : should VueJS router routes match with Laravel routes

The question is pretty simple.
in order to build a Simple Page Application (SPA) with Laravel and VueJS , should all the paths present in router.js ( the file defining paths for SPA) be also present in web.php of Laravel ?
For example I am using the source code from here :
router.js inside resources/assets/js/frontend has :
export default [
{ path: '/create', component: SubmitTicketFormComponent },
{ path: '/view/:ticketId', name: 'client-view-ticket', component: ViewTicketComponent, props: true }
];
But the web.php inside routes folder has
Route::get('/', function () {
return view('welcome');
});
Route::get('/admin', function () {
return view('admin');
});
Route::post('/login', 'Auth\LoginController#login');
Route::post('/logout', 'Auth\LoginController#logout')->name('logout');
//Auth::routes();
Route::get('/home', 'HomeController#index')->name('home');
And the api.php inside routes folder has :
Route::middleware('auth:api')->group(function() {
Route::get('/profile', 'ProfileController#index');
Route::apiResource('/admin/tickets', 'Admin\TicketsController');
});
Route::apiResource('tickets', 'TicketsController');
So what are the rules regarding the path or route declaration for both VueJs and Laravel ?
If you want to have a SPA and keep some of the Laravel pages you need to have a route like this:
Route::get('/{any}', 'VueController#index')->where('any', '.*');
This can be at the end of your routing file, so if it does not match the existing Laravel routes and it will open the Vue SPA and the routing will be handled from there.

Why Auth::user() return null in routes of a custom Service Provider?

I'm making a new Service called Factures in App\Services\Factures.
I created the \App\Services\Factures\FacturesServiceProvider:
public function register() {
$this->app->bind('factures', function ($app) {
return new Facture;
});
}
public function boot() {
// laod Routes
$this->loadRoutesFrom(__DIR__ .'/Http/routes.php');
// load Views
$this->loadViewsFrom(__DIR__ . '/views', 'factures');
}
I registered my provider everything works fine expect the Auth::user() in returns me  null in the views and the routes.php.
How can I get access to the Auth() in custom service?
This post resolved my problem: User Auth not persisting within Laravel package
I figure out that Laravel  apply to the default routes/web.php file a middleware called 'web' And doesn't apply this group to external package routes loaded via service provider's.
So my routes in the custom file should be in web middleware:
Route::group(['middleware' => ['web']], function () {
Route::get('testing-services', function(){
dd(Auth::user());
// output is valid
});
});

Laravel Broadcast - Combining multiple middleware (web, auth:api)

I am using Laravel Event Broadcast and Pusher to utilize websockets both on my API and Web. If I try them individually, both work fine. What I mean is:
Broadcast::routes(['middleware' => 'web']); // this works for my Laravel website
Broadcast::routes(['middleware' => 'auth:api']); // this works for my api
However, if I want to use both at the same time like this:
Broadcast::routes(['middleware' => ['auth:api', 'web']]); // doesn't work
... it crashes for both, which I suspect that it is assuming I am trying to enable for both auth:api && web middlewares.
Is there a way to use an OR kind of statement for this (auth::api || 'web')? What if I want to use both at the same time and if it passes one middleware, it bypasses the middleware.
Please note that I am using Laravel Passport for my api.
Or is there a way to combine and creating a mixed middleware for both (which will essentially check for either api or web)? So I can use something like this maybe:
Broadcast::routes(['middleware' => 'broadcast']); // or auth:broadcast
Update:
As far as I understand, if I create a new Middleware called broadcast, I can do:
class BroadcastMiddleware() {
public function handle() {
$web = Auth::guard('web')->user();
if ($web) {
return response()->json($web);
}
$api = Auth::guard('api')->user();
if ($api) {
return response()->json($api);
}
return response()->json('Unauthorized.', 500);
}
}
But then how do I change /broadcasting/auth route? If I try this:
Route::post('/realtime/auth', function(){
return true;
})->middleware('broadcast');
This returns the user object info, however instead, it should return something like: auth:"374f7ff42b7da877aa35:c29addedec281b418331a61dc3cfc74da8b108222565fa4924a8..."
Why not just use something like this in the BroadcastServiceProvider? This creates two separate endpoints with separate middleware assigned.
Broadcast::routes(['middleware' => 'web']);
Broadcast::routes(['prefix' => 'api', 'middleware' => 'api']);
I finally figured out how to do it.
I am not sure if it is the best way of achieving this, and I'd highly appreciate any improvements.
How I achieved is created a new middleware for 'web' and left the other one as it it. Here are the steps.
1) In 'BroadcastServiceProvider', left only auth:api guard for Broadcast::routes(['middleware' => 'auth:api']);.
This way, Laravel's auth:api method for authenticating broadcasting works as expected.
2) Created a middleware called "Broadcast" and mapped it in Kernel.php like so:
'broadcast' => \App\Http\Middleware\Broadcast::class
and the Broadcast.php middleware looks like this:
public function handle($request, Closure $next)
{
$web = Auth::guard('web')->user();
if ($web) {
return response()->json(\Illuminate\Support\Facades\Broadcast::auth($request));
}
return response()->json('Unauthorized.', 500);
}
3) Created a unique route other than Laravel's /broadcasting/auth in my routes>web.php
Route::post('/guard/broadcast/auth', function(\Illuminate\Support\Facades\Request $req){
return true;
})->middleware('broadcast');
4) And then only on my blade, I use it like so:
<script>
let pusher = new Pusher("{{ env('PUSHER_APP_KEY') }}", {
cluster: 'us2',
encrypted: true,
auth: {
headers: {
'X-CSRF-TOKEN': "{{ csrf_token() }}"
}
},
authEndpoint: '{{ env('APP_URL') }}' + '/guard/broadcast/auth',
});
let channel = pusher.subscribe('private-channel.{{ Auth::user()->id }}');
channel.bind('my-event', addMessage);
function addMessage(data) {
console.log(data);
}
</script>
I'm preferable just using middleware that extends to both auth:api and web middlewares.
like what I posted in here: https://github.com/tlaverdure/laravel-echo-server/issues/266#issuecomment-365599129. So, I just maintenance 1 middleware if I wanted to change it in the future
BroadcastServiceProvider
if (request()->hasHeader('authorization')){
Broadcast::routes(['middleware' => 'auth:api']);
} else {
Broadcast::routes();
}
It is better to use prefix approach for achieve multiple authorization types.
If you will use a middleware - it is just redundant middleware.
If you will use if block (as on a code snippet below): you will face problem with routes caching, it will return 403 error becuase Laravel should cache a route with a set of middlewares.
if (request()->hasHeader('authorization')){
Broadcast::routes(['middleware' => 'auth:api']);
} else {
Broadcast::routes();
}
You even may register separate service providers for web and api to split responsibilities and it will work.
For Web
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Broadcast::routes(['middleware' => ['web']);
require base_path('routes/channels.php');
}
}
And for Api
class ApiBroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Broadcast::routes(['prefix' => 'api', 'middleware' => ['auth:api']]);
require base_path('routes/dam-channels.php');
}
}

URL route from Laravel to Vue Router

I'm using Laravel 5.4 and vue-js 2.4
Laravel routes
Route::get('/', 'HomeController#home');
HomeController
public function home(Request $request) {
return view('app');
}
Vue router
const routes = [
{ path: '/', name: 'home', component: App},
{ path: '/about', component: About },
];
When I click on this, about component is well displayed and the url is example.com/about
<router-link to="/about">About</router-link>
Issue
When I enter example.com/about, I'm redirected to the home page of my vue app which is example.com.
What should I do so the about can reach vue-router ?
Route::get('/{component?}', 'HomeController#home');
Seems to be working
Add a route to your about page on laravel just like / route:
Route::get('/', 'HomeController#home');
Route::get('/about', 'HomeController#home');

Resources