I am having some problems with Laravel 6 policies. I get 403 unauthorized all the time even though it should be a non-authenticated request.
Files:
api.php
Route::prefix('v2')
->group(function () {
Route::prefix('agencies')->group(function () {
Route::post('/', 'Api\AgencyController#store');
});
}
AgencyController.php
<?php
namespace App\Http\Controllers\Api;
use App\Entities\Agency;
class AgencyController extends Controller {
public function store(AgencyRequest $request)
{
$this->authorize('create', Agency::class);
// Code that is never executed
}
}
AgencyPolicy.php
class AgencyPolicy
{
public function create(User $user)
{
\Log::info('hello?'); // This Log is never executed
return true;
}
}
AuthServiceProvider.php
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
\App\Entities\Agency::class => \App\Policies\AgencyPolicy::class,
// Other policies
];
public function boot()
{
$this->registerPolicies();
Gate::before(function ($user) {
if ($user->hasRole(SuperAdmin::ROLE_NAME)) {
return true;
}
});
Passport::routes(null, ['prefix' => 'api/oauth']);
}
}
My code is identical to the documentation but nonetheless I keep getting 403 unauthorized, and for the life of me I cannot understand what is going on. All help will be appreciated.
As lagbox thankfully replied, the answer is in the documentation, that states:
By default, all gates and policies automatically return false if the incoming HTTP request was not initiated by an authenticated user. However, you may allow these authorization checks to pass through to your gates and policies by declaring an "optional" type-hint or supplying a null default value for the user argument definition:
Thus, my problem would be solved by using ?User in AgencyPolicy.php:
class AgencyPolicy
{
public function create(?User $user)
{
return true;
}
}
This solves the problem.
Related
I implemented passport authentication in Laravel and the basic auth.
I have UserController and inside it, I have the constructor methode:
public function __construct()
{
$this->middleware('auth.basic.once')->except(['index', 'show']);
$this->middleware('auth:api')->except(['index', 'show']);
}
The OnceBasic middleware:
public function handle($request, Closure $next)
{
if(Auth::guard('api')->check())
return $next($request);
else
return Auth::onceBasic() ?: $next($request);
}
In the OnceBasic middleware, I'm able to check if the user authenticated using the auth:api then I prevent the authentication from trying to use the onceBasic, So it worked correctly when using the access token. But it fails when trying to authenticate using the onceBasic(email, password) because the auth:api trying to authenticate too and it fails(trying to call the redirectTo() methods inside the default \App\Http\Middleware\Authenticate.php )
My question is there a way to use both of these middlewares, to only successfully authenticate one and prevent the other from working?
My approach to using the same controller for two guards required pointing two separate groups of routes to the controllers. I provided an example in this answer to a similar question, here is the example code again:
<?php
Route::middleware(['auth:admin_api'])->group(function () {
Route::prefix('admin')->group(function () {
Route::name('api.admin.')->group(function () {
////////////////////////////////////////////////////////////
/// PLACE ADMIN API ROUTES HERE ////////////////////////////
////////////////////////////////////////////////////////////
Route::apiResource('test','App\Http\Controllers\API\MyController');
////////////////////////////////////////////////////////////
});
});
});
Route::middleware(['auth:api'])->group(function () {
Route::name('api.')->group(function () {
////////////////////////////////////////////////////////////
/// PLACE PUBLIC API ROUTES HERE ///////////////////////////
////////////////////////////////////////////////////////////
Route::apiResource('test', 'App\Http\Controllers\API\MyController');
////////////////////////////////////////////////////////////
});
});
So when an admin user goes to admin/test, it uses the admin auth guard, and when a normal user goes to /test it uses the standard auth guard. Both of these use the same controller.
I then created a base controller for my app. Here is how I determined with guard is being used to access the route in the constructor:
<?php
use Illuminate\Http\Response;
use App\Http\Controllers\Controller;
class BaseController extends Controller
{
protected $user;
protected $isAdmin = false;
public function __construct()
{
if(Auth::guard('admin_api')->check()) {
$this->user = Auth::guard('admin_api')->user();
$this->isAdmin = true;
} elseif(Auth::guard('api')->check()) {
$this->user = Auth::guard('api')->user();
$this->isAdmin = false;
} else {
return response()->json([
'message' => 'Not Authorized',
], 401);
}
}
i'm trying to create a website based on laravel framework. I'm stuck in permission control with Policy. This is my code:
+ Policies Register in App\Providers\AuthServiceProvider:
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
User::class => UserPolicy::class,
Category::class => CategoryPolicy::class
];
public function boot()
{
$this->registerPolicies();
try {
Permission::all()->each(function($permission) {
Gate::define($permission->name, function ($user) use($permission) {
return $user->hasPermission($permission->name);
});
});
} catch (\Exception $e) {
Log::notice('Unable to register gates. Either no database connection or no permissions table exists.');
}
}
}
User Policy in App\Policies\UserPolicy
class UserPolicy
{
use HandlesAuthorization;
public function viewAny(User $user)
{
return true;
}
public function view(User $user, User $target_user)
{
return $user->id === $target_user->id;
}
}
Api Route in routes/api.php
Route::get('/users', 'Api\UserController#getUsers')->middleware('can:view-users');
Route::get('/users/{user_id}', 'Api\UserController#getUser')->middleware('can:view-users');
Route::put('/users/{user_id}', 'Api\UserController#updateUser')->middleware('can:edit-users');
User Controller in App\Http\Controllers\Api\UserController
public function getUsers(UserRequest $request) {
$users = $this->userRepository->getAll();
$this->authorize('view', $user);
return response($users, 200);
}
public function getUser(UserRequest $request, $user_id) {
$user = $this->userRepository->find($user_id);
$this->authorize('view', $user);
return response($user, 200);
}
When I try to get data by using 2 url above, it returned error 500 Internal Server Error even when user is authenticated :
{
"error": "This action is unauthorized."
}
I tried to log result and found that error come from middleware can or Illuminate\Auth\Middleware\Authorize.
Code line:
$this->gate->authorize($ability, $this->getGateArguments($request, $models)); in handle() function throw error above.
After hours searching, I could not figure out solution.
Anyone can point out my mistake?
Solved. After hours reading source code in library, I figured out problem. I used api guard for my app without passport module. Thus, variable $userResolver in Illuminate\Auth\Access\Gate class could not be set to authenticated user and got null value. To solve this problem, I created my own Authorize middleware instead of laravel middleware Authorize and use Auth::guard('api')->user() to get authenticated user.
I want to make some entries of Analysis publicly available. I tried to implement it with Policies but failed. I think it's because the AuthServiceProvider fails with AccessDeniedHttpException every time I try to access without an authorized user.
AuthServiceProvider
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
Analysis::class => AnalysisPolicy::class
];
public function boot()
{
$this->registerPolicies();
}
}
AnalysisPolicy
public function view(User $user, Analysis $analysis)
{
if($analysis->demo === true){
return true;
}
return $user->id === $analysis->user_id;
}
AnalysisController
public function show(int $analysis)
{
$ana = Analysis::find($analysis);
$this->authorize('view', $ana);
...
}
I tried to just create a new Service Provider, but that didn't work either as I cannot call the registerPolicies function without extending from AuthServiceProvider.
Basically, all I want is to now check for anything if the demo Attribute is true.
Edit:
My Quick-Fix form now is just checking in the controller if it's a demo. But that's not a great solution in my opinion as I think the goal with Policies should be that I don't have Access Management in the Controller. So I'd love to find a better solution.
if(!$ana->demo){
$this->authorize('view', $ana);
}
Ok so it turns out you should always read the release notes of the latest Laravel version before asking a question.
As of Laravel 5.7 there is a proper solution for this:
public function update(?User $user, Post $post)
{
return $user->id === $post->user_id;
}
(https://laravel.com/docs/5.7/authorization#writing-policies)
By declaring $user optional, it's null for guest users and can be handled in the policy.
I have
(1/1) HttpException
This action is unauthorized.
I think all should work fine and I have done it right but maybe not.
My controller method:
public function update(Request $request, Users $uzytkownik)
{
$this->authorize('update', $uzytkownik);
return 1;
}
UsersPolicy that is in App\Policies\:
<?php
namespace App\Policies;
use App\Models\Users;
use Illuminate\Auth\Access\HandlesAuthorization;
class UsersPolicy
{
use HandlesAuthorization;
public function update(Users $user)
{
return true;
// return $user->login === auth()->login;
}
}
And in AuthServiceProvider:
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
'App\Models\Users' => 'App\Policies\UsersPolicy',
];
My Users model lays in App\Models\
When I cut $this->authorize('update', $uzytkownik); this line from controller everything works fine and I see '1', when I add it again HttpException.
What do I have wrong here? Thinking and Thinking, looking, I don't see anything bad here.
please make sure that your route is under auth middlware like this :
Route::group(['middleware' => 'auth'], function () {
// ur update route here
});
or in ur controller constructor like this :
public function __construct()
{
$this->middleware('auth');
}
and also like #Laerte said your update policy method should have another parameter of type user which is the user you want to edit, like this :
public function update(Users $userLoggedIn, Users $uzytkownik)
{
return true;
}
In your Policy, you have to add two parameters: The first one is the user logged in, and the second is the actual parameter. Try this in the Policy:
public function update(Users $userLoggedIn, $user)
{
return true;
}
I've been reading the documentation up and down now, still not sure what I'm doing wrong. In my opinion the documentation is very difficult to understand for a beginner.
Anyway, I'm trying to make something akin to the Auth::user() method, where it returns additional data about a logged in user that I will be needing for this application.
I have this helper class here:
namespace App\Helpers;
use Auth;
use Illuminate\Http\Request;
use App\Models\Grouping\User;
use App\Models\Grouping\Client;
use App\Models\Grouping\Rank;
class ClientUser {
public function __construct($request) {
$this->request = $request;
}
public function client() {
return Client::find($this->request->session()->get('client_id'));
}
public function auth() {
if (Auth::check()) {
// Get the client
$client = $this->client();
// Get the client's user
$user = $client->users()->find(Auth::user()['id']);
// Get the rank of the logged in user
$rank = Rank::find($user->pivot->rank_id);
return [
'user' => $user,
'rank' => $rank,
'client' => $client
];
}
return null;
}
}
This is responsible for doing what I described, returning additional data that I can't get through Auth::user(). Now I'm trying to register this class in the AuthServiceProvider
public function register()
{
// Register client auth
$request = $this->app->request;
$this->app->singleton(ClientUser::class, function ($app) {
return new ClientUser($request);
});
}
Now what I don't understand is how I'm supposed to make this globally accessible throughout my app like Auth::user() is.
The problem with just making "importing" it is that it needs the request object, which is why I'm passing it through the service container.
Now here's where I'm stuck. I'm not able to access app in my controller or anywhere, and I can't define a Facade because a Facade expects you to return a string of the bound service that it should "alias?"
Change your service provider like this :
$this->app->bind('client.user', function ($app) {
return new ClientUser($app->request);
});
Create another class extended from Illuminate\Support\Facades\Facade.
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class ClientUserFacade extends Facade {
public static function getFacadeAccessor(){
return "client.user";
}
}
Add 'ClientUser => ClientUserFacade::class in alias key of app.php