Laravel 5 Authentication: make Controller decides which methods are public - laravel

This is a very long question for (I think) a very simple answer, but I am a total newbie in Laravel and I need to know if I am addressing it the right way.
What I did
I followed the Intermediate Tutorial and modified it just a little bit, and now I have a simple task list that uses authentication. I had this in my TaskController constructor:
public function __construct(TaskRepository $tasks)
{
$this->middleware('auth');
}
This checks if the user is logged in before launching any methods, so I just need to call them like this in my routes file:
Route::get('/home', 'TaskController#index');
Route::get('/tasks', 'TaskController#indexUser');
Then I wanted to remove the authentication requirement for the index method, so that all users can see the /home page (where I list all the tasks), and only authenticated users can see the /tasks page (where I list only the user tasks and allow to delete them). I managed to do it like this:
1) I removed $this->middleware('auth') from the TaskController constructor
2) I modified my routes file to look like this:
Route::get('/home', 'TaskController#index');
Route::get('/tasks', [
'middleware' => 'auth',
'uses' => 'TaskController#indexUser'
]);
Ok, this works, but:
What I want to achieve
I don't want to have this logic in my routes file, I want that my Controller decides which methods are public and which not. But I got stuck. I think it should be something like this:
class TaskController extends Controller
{
/**
* Display a list of all current tasks
*/
public function index()
{
return view('tasks.index', [
'tasks' => Task::orderBy('created_at', 'asc')->get()
]);
}
/**
* Display a list of all of the logged user's task.
*
*/
public function indexUser(Request $request)
{
if (Auth::check() {
return view('tasks.index_user', [
'tasks' => $this->tasks->forUser($request->user()),
]);
} else {
//This is what I don't know how to do:
redirect_to_login
}
}
}
How can I achieve this?

You can decide which controller method should execute the middleware:
public function __construct()
{
$this->middleware('auth', ['only' => ['indexUser', 'update'] ];
}
just add in only the method you want to protect.
When a user try to access a method will automatically redirect to the login page.
Here you can find the documentation: https://laravel.com/docs/5.1/controllers#controller-middleware

You can do the following if you want this logic in your controller:
if (!Auth::check()) {
return redirect('/path/to/login/page');
}
This way you don't have giant if else statement in your controller. (Handy if your controller contains more logic then the sample above)
I personally would go for the answer Christian Giupponi provided. Cause it makes much more sense to handle this logic in the construct function then in your controller.

Related

Laravel - Policy authorization on user and object that both belong to something

I want to authorize my API of my Laravel application.
My structure right now is like this:
Users belong to an organization, and the organization has many (lets say) objects.
Now I want that only users can view/edit/create/delete objects, that belong to the organization they are part of.
My API route for viewing the objects is:
Route::get('organizations/{id}/objects','ObjectController#indexOrganization')->middleware('auth:api');
I created the Models User, Organization and Object. They all have their own Controller.
I created the ObjectPolicy and tried this:
public function view(User $user, Object $object)
{
return $user->organization_id === $object->organization_id;
}
And then I added ->middleware('can:view,object'); to the route.
Unfortunately, it does not work and the Laravel documentation does not provide the information I need.
Can someone help?
Thanks!
EDIT
I have no idea what I'm doing wrong!! I Changed everything but I still get a 403 response.
Here is my code:
Route:
Route::get('organizations/{organization}/objects','ObjectController#index Organization')->middleware('auth:api', 'can:view, organization');
OrganizationPolicy:
public function view(User $user, Organization $organization)
{
return $user->organization_id === $organization->id;
}
ObjectController:
public function indexOrganization(Organization $organization)
{
$objects = $organization->objects;
return ObjectResource::collection($objects);
}
I also added this to my AuthServiceProvider:
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
App\Organization::class => App\Policies\OrganizationPolicy::class,
];
EDIT 2 / SOLUTION
The answer from newUserName02 works! The problem was inside the AuthServiceProvider. After I changed the code (see above in Edit) there to:
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
'App\Organization' => 'App\Policies\OrganizationPolicy',
];
it worked!
The policy method should to match the arguments you are passing to the controller. It looks like you are passing the id of the Organization in the route, but you are trying to check the Object on the policy.
https://laravel.com/docs/5.7/authorization#via-middleware
You can take advantage of Laravel's implicit model binding to inject the Organization into the controller like this:
Route:
Route::get('organizations/{organization}/objects','ObjectController#indexOrganization')->middleware('auth:api', 'can:view,organization');
Policy:
public function view(User $user, Organization $organization)
{
return $user->organization_id === $organization->id;
}
Controller:
public function indexOrganization(Organization $organization)
{
...
}
Notice that {organization} in the route matches organization in the ->middleware() call, which matches $organization in the policy and controller.

How do I handle a not found case when using Laravel Dependency Injection?

I am using a route /news/{news} and the controller public function show(News $news). When I supply the route with a valid ID, it all works fine and dandy. However, when the ID isn't found, Laravel returns a 404-page. I would rather handle that case myself, since I am using it as an API.
My question resembles this one, but I want to use Dependency Injection, if at all possible. I know I can do a normal News::where(), but that is not how I'd like to solve this. The question, therefor, is; How do I handle a not found case when using Laravel Dependency Injection?
I am building a private API so what I'd like to achieve is to return a JSON value when the news isn't found.
veNuker's option 2 is a way to go if you would like to customize the resolution logic of your Route Model Binding.
But for a 4th option, why not give Middlewares a try? Let's say you have a
class NewsIsExist
{
public function handle($request, Closure $next) {
$news_id = $request->route('news_id');
$news = News::find($news_id);
if($news) {
$request->merge(['news' => $news]);
return $next($request);
} else {
return response()->json([
'error' => "News not found."
], 404);
}
}
}
Tweek your routes to use the middleware
Route::get('news/{news_id}', [
'uses' => "NewsController#show",
'middleware' => 'news-exists'
]);
Then you can easily access your News instance in your controller
use Illuminate\Http\Request;
public function show(Request $request) {
$news = $request->get('news');
}
New answer
So after updating your question, now we know, you want to simply return JSON value when the news is not found. In this case, I would use route fallback method, which is executed when Laravel catches 404 exceptions. Good thing is, you need to implement this only once, and it will work for news/comments/users and all other things you have in your API. Add this code to your routes file:
Route::fallback(function(){
return response()->json(['message' => 'Not Found!'], 404);
});
More info - https://themsaid.com/laravel-55-better-404-response-20170921
===========================================================
Previous answer
Since you didn't specify what exactly you want to do, here are a couple of options:
Option 1 - custom error page
https://laravel.com/docs/5.5/errors#custom-http-error-pages
Option 2 - display default item
It will pass another object of News type to your controller
<?php
namespace App\Providers;
use App\News;
use Illuminate\Support\Facades\Route;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
// 'news' comes from your route param name
Route::bind('news', function ($value) {
$news = News::where('id', $value)->first();
if ( ! $news) {
//assuming here the first item is the default one
$news = News::first();
}
return $news;
});
}
}
Option 3 - catch all 404s
You can catch all 404 errors and return whatever you want as described here How do I catch exceptions / missing pages in Laravel 5?

Laravel - middleware on routes in controller

I have a route resource
Route::resource('campaign', 'CampaignController');
I want to restrict some of these routes to the users.
For example the index page lists all the campaigns and they should not see this only their own ones.
I have a custom middleware that simply checks the if the user is an admin
However I cannot apply this to individual methods.
public function index()
{
$this->middleware('checkuser');
}
Just the constructor
public function __construct()
{
$this->middleware('checkuser');
}
How do I get around this and apply to individual route in the controller
Sorry my mistake I should have read the docs you can add exceptions or allowed.
$this->middleware('auth');
$this->middleware('log', ['only' => ['fooAction', 'barAction']]);
$this->middleware('subscribed', ['except' => ['fooAction', 'barAction']]);
}

Set username as prefix in URIs Laravel

I want to set prefix in all of the authenticated URIs in Laravel 5.3 app. If I use Route::group(['prefix' => {username}]), then how can I get the username in web.php file.
Assuming you have defined routes like this:
Route::group(['prefix' => '/{user}'], function() {
Route::get('/profile', 'UserController#showProfile')->name('user.profile');
});
You can use Laravel's route-model binding to pass an User instance directly into your routes:
View Profile
Then in the controller , you can easily grab that model instance:
class UserController extends Controller
{
public function showProfile(User $user)
{
return view('user.profile.index', compact('user'));
}
}
Check out the documentation here.
EDIT: By default, Laravel uses the id column when retrieving a given model class. You can change that to any column, in your case username , easily by overriding the getRouteKeyName() method on your User model.
public function getRouteKeyName()
{
return 'username';
}
You can't and you shouldn't hardcore routes with usernames. If you have 100 users, do you plan to create 100 route groups for each user? Assuming each user has 2 routes that's 200 routes.
What you need to do is generate routes with username segment and detect the user on the fly based on the user name.
Route::group(['prefix' => '{username}'], function () {
Route::get('profile', 'UserController#profile');
Route::get('setting', 'UserController#setting');
});
The routes generated would be like
http://example.app/{username}/profile
http://example.app/{username}/setting
So a user with username adam with get the links.
http://example.app/adam/profile
http://example.app/adam/setting
Similarly you can have username based links for all your users and you need to detect them in your controller using route model binding or the standard way.
Edit
For closure based routes you can get the url segment like so
Route::get('{username}/profile', function ($username) {
return 'Username is '.$username;
});
With controllers you get them as the parameters.
public function profile($username)
{
return $username;
}

Laravel how to execute specific function every time a specific controller's functions are used

I am running laravel and i just implemented PHP Excel in order to export excels with data. For security reasons i want to redirect the user if his facebook id is no match with "my administrator facebook id".
My controller is named ExcelController and it has different functions inside it.
I do not want to use the below code inside every function so i am trying to find something to execute like __construct() - everytime this controller is accessed :
if(Auth::user()->facebook_id != env('facebook_id_admin_access')) {
return redirect()->route('home');
}
env('facebook_id_admin_access') is an .env variable(like a constant) with the facebook id i want to give access so it can use the specific controller.
I tried creating a public function __construct(){} in the controller and put the above code block but it doesn't get called.
Is this possible and how? Should i use middleware for that?
EDITED QUESTION
1) Created a middleware CheckAdminAccess
*/
public function handle($request, Closure $next)
{
if(Auth::user()->facebook_id != env('FACEBOOK_ID_ADMIN_ACCESS') || !Auth::check()) {
return redirect()->route('/');
}
return $next($request);
}
2) Updated in app\http\kernel.php to
protected $routeMiddleware = [
'admin_access' => 'App\Http\Middleware\CheckAdminAccess',
];
3) Updated in routes.php :
Route::get('/output/completed', 'ExcelController#completed')->middleware('admin_access');
But it doesn't seem to work, did i forgot anything?
After creating a middleware,
To make it work i changed in my routes.php from :
Route::get('/output/completed', 'ExcelController#completed')->middleware('admin_access');
To :
Route::group(['middleware' => ['auth','admin_access']], function() {
Route::get('/output/completed', 'ExcelController#completed');
});

Resources