Laravel 7: How can I restrict URL by user? - laravel

I'm a newbie who is learning Laravel 7. I have developed a small web application with Laravel 7. But today I noticed one problem. That all the URLs are global, means all users can access all the URLs of my website. Suppose User A created data and it shows in a table where the edit and delete buttons exist with every row. The edit URL is like: localhost/records/edit/5. The problem is, that other logged-in users can access this edit page also. Like this, all the URLs are accessible by any logged-in users which is very bad.
I hope you understand what I'm saying. I have almost 250+ web routes. Is there any easy way to restrict the routes?
User can access their own data only. How can I do that?
Thanks

You'll have to register policies and ensure users cannot access parts of the website without the correct authorization.
See the docs on how to write policies and implement them.
Sample code:
Policy:
class RecordPolicy
{
public function delete(User $user, Record $record)
{
return $user->id === $record->user_id;
}
}
Controller
class RecordController
{
public function destroy(Record $record)
{
// Authorize the delete action before actually deleting the record
$this->authorize('delete', $record);
$record->delete();
}
}
Records index
#foreach($records as $record)
<div>
{{ $record->name }}
{{-- Only show delete button if the authorized user can actually delete the record --}}
#can('delete', $record)
<form action="{{ route('records.destroy', compact('record') }}" method="POST">
#csrf
#method('DELETE')
<button type="submit">Delete record</button>
</form>
#endcan
</div>
#endforeach

store user_id when new record added > Add created_by field in user_table DB
when user run URL > get logged-in user user_id from session and check in DB for their record > if record not found then redirect to home page with message otherwise continue.

If i understand you correctly you want to restrict routes to specific user.
Create a roles table
Columns (id, name)
(1 = Super Admin, 2 = Admin, 3 = User)
Assign Roles To User While Creating new User
i.e add role_id to users table.
$user = User::create([
'name' => 'First Admin',
'email' => 'admin#admin.com',
'password' => Hash::make('Admin#1234'),
'role_id' => 2 // For admin role
]);
Then Create Middlewares for each role and restrict routes for specific users.
Admin Middleware: AdminMiddleware.php
public function handle(Request $request, Closure $next)
{
$allowedRoles = [2];
if (!in_array(Auth::user()->role_id, $allowedRoles))
{
return redirect()->back()->with('error',__('Sorry, you are not authorized to access that location.'));
}
return $next($request);
}
In Kernel.php
'admin' => \App\Http\Middleware\AdminMiddleware::class,
Route::group(['middleware' => 'admin'], function(){
// All admin Routes
});
You Can also Use Spatie package for this.
https://spatie.be/docs/laravel-permission/v5/basic-usage/middleware
Just Check If Role is allowed to use that route:
Route::group(['middleware' => ['auth', 'role:admin']], function () {
// All routes available for admin
});

Related

laravel resource controller store method not working for some users

I m using laravel 8 auth middleware on a group route and under that group route there are multiple resource routes.
Store method is not working for some users.
I have checked all possibility like csrf and trailing slash etc.
its posting data to index method for some users but for other users its working fine.
web.php
Route::name('')->middleware(['auth','CheckRole'])->group(function () {
//role routes
Route::resource('role', RoleController::class);
//role dumpsites
Route::resource('dumpsite', DumpsiteController::class);
});
controller --it large i cant add whole
public function store(Request $request){
$o = new Dumpsite;
$o->remarks = $request->post('remarks');
$o->save();
return redirect()->route('dumpsite.edit', ['dumpsite' => $o->id])->with('success', 'Submitted successfully');
}
view
<form enctype="multipart/form-data" action="{{route('dumpsite.store')}}" method="POST">

if else condition in routes laravel

I want only user with same name with the url id can access using if condition
Example
User logged on with name jer
He should only access url with /User-Profile/jer
And not access other page /User-Profile/abc that are not equal to his
name
Doing something like Example:
if{id}!=={{auth->name}}
{
Route::get('NoPermission', 'Restriction#index');
}
else
{
Route::get('/User-Profile/{name}/', 'AccountController#index');
}
How can I compare {name} from url to {auth->name} ?
Route
Route::get('/User-Profile/{name}/', 'AccountController#index');
Blade
<a href="/dashboard/User-Profile/{{ Auth::user()->name }}">{{ Auth::user()->name
}}</a>
You can't access Auth like that in your routes, compare it in your AccountController instead:
public function index($name){
if($name != Auth::user->name()) abort(403);
else...
}
In a service provider (Doesn't really matter which one, but it would be clearer if done in the RouteServiceProvider), add a route binding in the boot method as documented in https://laravel.com/docs/6.x/routing#explicit-binding
public function boot()
{
// Declare binding 'name'.
Route::bind('name', function ($name) {
return App\User::where('name', $name)->first() ?? abort(404);
});
}
Then, use that binding in your routes file
// Use binding name and middleware auth to make sure this route can't be accessed by guest users.
Route::get('/User-Profile/{name}/', 'AccountController#index')->middleware('auth')->name('account_profile');
In your blade file, you can do the following
{{-- Make sure the link is only visible for authenticated users https://laravel.com/docs/6.x/blade#if-statements --}}
#auth
<a href="{{ route('account_profile', ['name' => auth()->user()->name]) }}</a>
#endauth
Allow acces to the page , but before showing content ,check if the url path is == to the id name .
Actually, you can check in your routes like this:
Route::get('/profile/{name}', function(String $name) {
if (!Auth::check() || $name !== Auth::user()->name) {
abort(404);
}
return view("view.auth.profile", ['profile => App\Profile::where('user_id', '=', Auth::id())->first()]);
});
However if you use
Route::get('/profile', 'AuthController#profile')->middleware('auth');
and use Auth::user() in your controller to select the correct profile.
The benefit here is that any unauthenticated users will be automatically redirected to your login page, and there's no need to include the name on your profile link.

Redirect users to the login if they are not authenticated and to the homepage if authenticated when accessing a url that they should not have access

After a user creates a conference he is redirected to a page "http://proj.test/conference/manage/2" to manage that specific conference.
A user that creates a conference with id "2" for example should be allowed to access "http://proj.test/conference/manage/2". But only this user that created this conference with id "2" should be allowed to access this conference.
The other users if they access "http://proj.test/conference/manage/2" should be redirected to the login page if are not authenticated and to the homepage if they are authenticated.
I was trying to do this with the code below, particularly with the code in the manage method, but when the user is redirected to an url for example "http://proj.test/conference/manage/2" it appears always:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'conference.user_id' in 'where clause' (SQL: select * from `conferences` where `conferences`.`user_id` = 1 and `conferences`.`user_id` is not null and `conferences`.`id` = 7 limit 1)
The issue should be because in the conferences table I have a field "conference_creator_id" instead of "user_id". But it's not possible to maintain the "conference creator_id"? But also, I dont know if this approach in the manage method is ok for this context and if with this approach is possible to do that thing of redirect a user, that accesses "http://layout.test/conference/manage/2", and he didn't create this conference with id 2, to the login page if is not authenticated and redirect to the homepage if he is authenticated. Do you know a proper approach to do that?
Store method:
public function store(Request $request)
{
$this->validate($request, [
'conference_name' => 'required|max:255|string',
...
]);
$conference = Conference::create([
'name' => $request->conference_name,
...
]);
return redirect()->route('conference.manage', ['id' => $conference->id]);
}
Manage method:
public function manage($id){
$conference = Auth::user()->conferences()->findOrFail($id);
return view('conferences.manage')>with('myconference',$conferene);
}
Routes:
Route::group(['prefix' => '', 'middleware' => 'auth'], function(){
Route::post('/conference/store', [
'uses' => 'ConferenceController#store',
'as' => 'conference.store'
]);
Route::get('/conference/create', [
'uses' => 'ConferenceController#create',
'as' => 'conference.create'
]);
Route::get('conference/manage/{id}', [ 'uses' => 'ConferenceController#manage', 'as'=>'conference.manage']);
});
Add the custom foreign key to the conferences relationship definition:
public function conferences()
{
return $this->hasMany(Conference::class, 'conference_creator_id');
}
But it's better to follow Laravel naming conventions described in my repo and change the column name to user_id.
And you need to use policies for authorization purposes.
You could add to your controller class:
public function __construct()
{
$this->middleware('auth');
}
This way it'll check whether the user is signed in or as a guest and deal with them accordingly depending on the result.
The alternative would be to wrap a Route group around anything that required 'auth' (as my answer states in your previous question).
A simple way will be to check the id of the current user
public function __construct()
{
//User should be logged in before accessing the `manage` method
$this->middleware('auth')->only(['manage']);
}
public function manage(Conference $conference){
//Redirect the user to the homepage when its ID is different of the ID of the user who created the conference
if ($conference->user_id != Auth::id()){
return redirect(url('/'));
}
return view('conferences.manage')>with('myconference',$conferene);
}

Laravel: always adding user/{id} to routes or different method

I have a Laravel application. In the routes file, I have
Route::group(['prefix' => 'user'], function () {
Route::group(['middleware' => ['auth', 'roles'], 'roles' => ['buyer']], function() {
Route::get('dashboard/buyer', ['as' => 'buyer_dashboard', 'uses' => 'User\Buyer\DashboardController#index']);
});
Route::group(['middleware' => ['auth', 'roles'], 'roles' => ['seller']], function() {
Route::get('dashboard/seller', ['as' => 'seller_dashboard', 'uses' => 'User\Seller\DashboardController#index']);
});
});
I have a middleware that basically checks if the id as supplied in the route, is the same as the current logged in user. If this is not the case, I return an error page. The reason for having this is that I want to prevent that a user can access the dashboard of another user. I also want to prevent that a user can place a bid for someone else (by changing the id in the http request)
The issue is that in the first route, the id is referring to the user. In the second route, the id is referring to the lot id.
Am I obliged to change the second route to:
Route::get('{id}/lot/{lot}/bid/create', ['as' => 'buyer_lot_bid_create', 'uses' => 'User\Buyer\BidsController#create']);
where the first id refers to the user so that I can check the id of the user?
Or is there another way to prevent users from accessing other users pages without explicitly passing the user/{id} in the route?
Doing this in a middleware sounds like a bad idea. You've already come up against one exception to your middleware rule, and you'll certaily come across more.
There's two ways to do this:
Using laravel's built in authorisation tools: https://laravel.com/docs/5.1/authorization
If the check is that the "id as supplied in the route, is the same as the current logged in user" then there's no need to pass the user's id in via the route at all. The user can just visit eg. /dashboard/buyer with no params in the route. Who's dashboard are they visiting? The one of the logged in user, of course. So there's no way for a user to even try to visit another user's dashboard. Likewise with bidding. You can make your bid endpoint so that the bidder's id is not passed in via a route - it's just set to the id of the logged in user in your controller method. So again, there's no way to even try to bid on behalf of another user.
class AuthServiceProvider extends ServiceProvider
{
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
$gate->define('see-profile', function ($user, $profile) {
return $user->id === $profile->user_id;
});
}
Controller:
public function profile($id)
{
$post = Profile::findOrFail($id);
if (Gate::denies('see-profile', $profile)) {
abort(403);
}
}

Compare route name in blade

I am using Laravel 5.2 and Blade templating, currently I am using this code to send the user to their own profile
href="{{ route('profile.index', ['username' => Auth::user()->username]) }}
This code is in an #if statement in blade, I was wondering how I would be able to check to make sure the user is on their own profile before I show them elements they should only be able to see on their own profile?
Just use check similar to this in your controller:
if (Auth::check()) // Checks if user authenticated
{
$userId = Auth::user()->id; // Gets user ID
// Do some stuff
}
return view('profile', compact('profileInfo'));
In this case any user will see only he's own profile.

Resources