Unable to find answers anywhere
I am using Laravel 5.5 policy to restrict a user from listing properties that are not registered by the user (authenticated via API).
I have two classes User and HostProperty. Additionally, I have registered a policy for the user to access their hosted property list by ID.
Here are my models.
The Main Problem is not able to call on controller method - which throws above error:
$authUser = auth('api')->user();
if ($authUser->can('access', $property)) {
return response()->json(['success' => 'success']);
} else {
return response()->json(['error' => 'error']);
}
User.php
namespace App;
use Illuminate\Notifications\Notifiable;
use Cartalyst\Sentinel\Users\EloquentUser;
use Illuminate\Database\Eloquent\SoftDeletes;
use Laravel\Passport\HasApiTokens;
use Illuminate\Auth\Authenticatable as AuthenticableTrait;
use Illuminate\Contracts\Auth\Authenticatable;
class User extends EloquentUser implements Authenticatable
{
use HasApiTokens, Notifiable;
use SoftDeletes;
use AuthenticableTrait;
protected $guarded=[];
protected $dates = ['deleted_at'];
protected $hidden = [
'password', 'remember_token',
];
//hosts relation
public function hostproperty()
{
return $this->hasMany('App\Models\Hosts\HostProperty','user_id');
}
}
HostProperty.php
namespace App\Models\Hosts;
use Illuminate\Database\Eloquent\Model;
class HostProperty extends Model
{
public $timestamps = true;
protected $guarded=[];
protected $hidden = [
'user_id',
];
public function user()
{
return $this->belongsTo('App\User','user_id');
}
}
HostPropertyPolicy
namespace App\Policies\Host;
use App\User;
use App\Models\Hosts\HostProperty;
use Illuminate\Auth\Access\HandlesAuthorization;
class HostPropertyPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
//
}
public function access(User $user, HostProperty $HostProperty)
{return TRUE;
//return $user->id === $HostProperty->user_id;
}
}
AuthServiceProvider
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\Passport;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use App\Models\Hosts\HostProperty;
use App\Policies\Host\HostPropertyPolicy;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
HostProperty::class=>HostPropertyPolicy::class,
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
}
HostPropertyController
use App\User;
use App\Models\Hosts\HostProperty;
use App\Http\Controllers\Controller;
class HostPropertyController extends Controller
{
public function listOneProperty($propertyId)
{
$authUser = auth('api')->user();
$property=HostProperty::with('user')->find($propertyId);
if ($authUser->can('access', $property)) {
return response()->json(['success' => 'success']);
} else {
return response()->json(['error' => 'error']);
}
}
}
Route
Route::get('listOneProperty/{propertyId}', array('as' => 'listOneProperty.get', 'uses' => 'HostPropertyController#listOneProperty'));
Please note: I am calling from API - the above route is for API, I am not able to use the policy on the API routes. I keep getting the above error while calling this route.
I tried
$this->authorize('access', $property);
However, since API doesn't store login session the above could not be completed so I again tried with
$authUser = auth('api')->user();
$authUser->authorize('access', $property);
Does not work either. I have tried all I can but still, I cannot get it done right.
If someone has an example of using Laravel policy in API authenticated by Passport it would be helpful for anybody looking to get this done right.
With regards
Looks like
$authUser = auth('api')->user();
is returning a query instead of a User model.
Please make sure that $authUser is a User model before calling ->can()
However, since API doesn't store login session the above could not be completed so I again tried with
Authentication is typically handled by middleware in Laravel. If you're using Passport, you should be able to use the auth:api middleware to authenticate requests, as long as you're sending the correct Authorization header and token.
Try changing this
$authUser = auth('api')->user();
to this
$authUser = auth()->user();
And add the auth middleware to your route:
Route::get('listOneProperty/{propertyId}', 'HostPropertyController#listOneProperty')
->name('listOneProperty.get')
->middleware('auth:api');
If you're consuming the api from your own web views and you want it to work with the current session, check out the Consuming Your API With JavaScript section of the Passport docs:
https://laravel.com/docs/5.7/passport#consuming-your-api-with-javascript
Related
I have the following policy called UserPolicy.
I want only admin users to access/edit the users data, even though I have set the return value to true(for testing) I still get 403 response no matter what.
namespace App\Policies;
use App\Models\Auth\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class UserPolicy
{
use HandlesAuthorization;
public function viewAny(User $user)
{
// return $user->admin();
return true;
}
}
I have registered the policy as follows
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use App\Models\Auth\User;
use App\Policies\UserPolicy;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
User::class => UserPolicy::class,
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}
The following code is how I use it in the controller
if (Gate::denies('viewAny')) {
return response('Not Authorized!', 403);
}
You should use authorize() method in your controller and pass User class as the second parameter. It will point the request to the targeted policy.
authorize() method is by default provided by Illuminate\Foundation\Auth\Access\AuthorizesRequests trait in your base controller.
Your controller could be like below:
try {
$this->authorize('viewAny', User::class);
} catch (AuthorizationException $e) {
return response('Not Authorized!', 403);
}
I'm trying to generate a token to authenticate users in my Controller the following way:
namespace App\Http\Controllers\API;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use App\Models\User;
class AuthController extends Controller
{
public function login()
{
if (Auth::attempt(['email' => request('email'), 'password' => request('password')])) {
$user = Auth::user();
$success['token'] = $user->createToken('myApp')->accessToken;
dd($success['token']);
}
}
Currently, I'm just trying to print out the token. And this is my User's model:
<?php
namespace App\Models;
use Illuminate\Notifications\Notifiable;
//use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Facades\Hash;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
const USER_FIRST_NAME_FIELD = "first_name";
const USER_LAST_NAME_FIELD = "last_name";
const USER_PREFERRED_NAME_FIELD = "preferred_name";
const USER_EMAIL_FIELD = "email";
const USER_EMAIL_VERIFIED_AT_FIELD = "email_verified_at";
const USER_PASSWORD_FIELD = "password";
const USER_REMEMBER_TOKEN_FIELD = "remember_token";
const USER_RECEIVE_NEWSLETTER_FIELD= "receive_newsletter";
const USER_ACTIVE_FIELD = "active";
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
self::USER_FIRST_NAME_FIELD,
self::USER_LAST_NAME_FIELD,
self::USER_PREFERRED_NAME_FIELD,
self::USER_EMAIL_FIELD,
self::USER_PASSWORD_FIELD,
self::USER_RECEIVE_NEWSLETTER_FIELD,
self::USER_ACTIVE_FIELD,
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
self::USER_PASSWORD_FIELD,
self::USER_REMEMBER_TOKEN_FIELD
];
/**
* Automatically creates password hash when password is submitted
*
* #param string $password
* #return void
*/
public function setPasswordAttribute(string $password) : void
{
$this->attributes['password'] = Hash::make($password);
}
}
As you can see I'm using HasApiTokens, Notifiable traits and nonetheless I'm getting an error from my controller saying:
Call to undefined method App\User::createToken()
Passport is installed and configured correctly.
Here's something weird:
When registering an user (I'm using a separate controller and also using a service) a token is created successfully:
Here's my controller:
<?php
namespace App\Http\Controllers\API;
use App\Services\UserService;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
use App\Http\Requests\RegisterUserRequest;
class UserController extends Controller
{
private $user;
public function __construct(UserService $user)
{
$this->user = $user;
}
public function store(RegisterUserRequest $request) : JsonResponse
{
// TODO: verify message on error
$user = $this->user->register($request->validated());
$token = $user->createToken('MyApp')->accessToken;
dd($token);
return response()->json(['status' => 201, 'user_id' => $user->id]);
}
}
Here's my service:
<?php
namespace App\Services;
use App\Models\User;
use App\Services\BaseServiceInterface;
class UserService implements BaseServiceInterface
{
public function register(array $formValues) : User
{
// 'terms and conditions' should not be saved into the db, hence it's removed
unset($formValues['terms_conditions']);
return User::create($formValues);
}
}
and here's my model again:
<?php
namespace App\Models;
use Illuminate\Notifications\Notifiable;
//use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Facades\Hash;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
const USER_FIRST_NAME_FIELD = "first_name";
const USER_LAST_NAME_FIELD = "last_name";
const USER_PREFERRED_NAME_FIELD = "preferred_name";
const USER_EMAIL_FIELD = "email";
const USER_EMAIL_VERIFIED_AT_FIELD = "email_verified_at";
const USER_PASSWORD_FIELD = "password";
const USER_REMEMBER_TOKEN_FIELD = "remember_token";
const USER_RECEIVE_NEWSLETTER_FIELD= "receive_newsletter";
const USER_ACTIVE_FIELD = "active";
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
self::USER_FIRST_NAME_FIELD,
self::USER_LAST_NAME_FIELD,
self::USER_PREFERRED_NAME_FIELD,
self::USER_EMAIL_FIELD,
self::USER_PASSWORD_FIELD,
self::USER_RECEIVE_NEWSLETTER_FIELD,
self::USER_ACTIVE_FIELD,
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
self::USER_PASSWORD_FIELD,
self::USER_REMEMBER_TOKEN_FIELD
];
As I told you, when creating a user the token is being generated correctly.
I'd say that Auth::user() is not calling my Model directly, but I don't know for sure that's what is happening.
Any idea why?
Thanks
Since your guard is returning the wrong User model, App\User, you should check your auth configuration, 'config/auth.php'. In the providers array adjust any provider, usually users, that is using the App\User model to App\Models\User instead.
'providers' => [
'users' => [
'driver' => 'eloquent',
// 'model' => App\User::class,
'model' => App\Models\User::class,
],
...
],
in my case, i missed to use Trait HasApiTokens
thats why laravel was unable to create tokens.
just open User.php
afetr name space include
use Laravel\Passport\HasApiTokens;
then inside class
use HasApiTokens
Pls note : I am using laravel 7.
So, this is not the right way to do it but it's working at the moment:
<?php
namespace App\Http\Controllers\API;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\API\BaseController;
class AuthController extends BaseController
{
public function login()
{
if (Auth::attempt(['email' => request('email'), 'password' => request('password')])) {
$authenticated_user = \Auth::user();
$user = User::find($authenticated_user->id);
dd($user->createToken('myApp')->accessToken);
}
dd('here');
}
}
Now I'm seeing the token.
I wanna do it the right way so I still would appreciate if any one could help me.
Thanks
you can let the auth.basic middleware do the authentication for you, by calling it in the construct method:
public function __construct()
{
$this->middleware('auth.basic');
}
Then generate the access token for the currently authenticated user, and return the user information along with the access token:
public function login()
{
$Accesstoken = Auth::user()->createToken('Access Token')->accessToken;
return Response(['User' => Auth::user(), 'Access Token' => $Accesstoken]);
}
Now the Controller will look like this:
<?php
namespace App\Http\Controllers\API;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\API\BaseController;
class AuthController extends BaseController
{
/**
* Instantiate a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth.basic');
}
public function login()
{
$Accesstoken = Auth::user()->createToken('Access Token')->accessToken;
return Response(['User' => Auth::user(), 'Access Token' => $Accesstoken]);
}
}
i have updated laravel 6 to 8 & i am using sanctum for API auth.
This works for me when i want to get token for API auth.
in User model
use Laravel\Sanctum\HasApiTokens;
and use the traits in function
use HasApiTokens
Model/User.php
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Hash;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* #var string[]
*/
protected $fillable = [
'name',
'email',
'password',
'status'
];
/**
* The attributes that should be hidden for serialization.
*
* #var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
public function setPasswordAttribute($input)
{
if ($input) {
$this->attributes['password'] = app('hash')->needsRehash($input) ? Hash::make($input) : $input;
}
}
public function roles()
{
return $this->belongsToMany(Role::class);
}
public function scopeActive($query){
return $query->where('status', 'ACTIVE');
}
}
I create a policy called LetterPolicy , this is the code
namespace App\Policies;
use App\Letter;
use App\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class LetterPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
//
}
public function update(User $user, Letter $letter)
{
return($user->id === $letter->user_id || $user->role_id===1 ) ;
}
}
and this is authserviceprovider
namespace App\Providers;
use App\Letter;
use App\Policies\LetterPolicy;
use App\Policies\UserPolicy;
use App\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
User::class => UserPolicy::class,
Letter::class => LetterPolicy::class,
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}
And in the following code I check for the user
class LetterController extends Controller
{
protected $user;
public function __construct()
{
$this->middleware(function ($request, $next){
$this->user = Auth::user();
return $next($request);
});
}
public function edit(Letter $letter)
{
if($this->user->can('update', $letter)){
//edit
}
else
abort('403', 'Access Denied');
}
The code is working well in localhost but on the remote server it reports the access denied error. I created this policy after deploying the site on the server so I create a route /clear-cache with code
Route::get('/clear-cache', function() {
$exitCode = \Illuminate\Support\Facades\Artisan::call('cache:clear');
});
To clear the cache after creating the policy. But it still reports the 403 error. What is the problem?
I tried dd($this->user->id === $letter->user_id || $this->user->role_id===1 ); in the COntroller and it returned false. I tried dd($this->user->id == $letter->user_id || $this->user->role_id==1 ); and it was true. Now it works but I don't know why!!!
Fjarlaegur's answer was the key. I had the same problem: in localhost there was no issue, but in production server somehow every authorization failed and it was because of the comparison operator. Changed from === to == and all as good.
I try to use Laravel's access policies, however, I receive over and over and over and over again the same error and I do not see what I need to import and / or use in functions or models.
First I show you my AuthServiceProvider
use Illuminate\Support\Facades\Gate;
use App\User;
use App\Policies\UserPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as
ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
User::class => UserPolicy::class,
];
public function boot()
{
$this->registerPolicies();
//
}
}
Now my UserPolicy
namespace App\Policies;
use App\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class UserPolicy
{
use HandlesAuthorization;
public function __construct()
{
//
}
public function edit(User $authUser, User $user)
{
return $authUser === $user;
}
}
And finally the edit function of my UsersControllers
use App\User;
use Illuminate\Http\Request;
use App\Http\Requests\UpdateUserRequest;
public function edit($id)
{
$user = User::findOrFail($id);
$this->authorize($user);
return view('users.edit', compact('user'));
}
With my UpdateUserRequest with authorize
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required',
'email' => 'required|unique:users,email,'.$this->route('usuario')
];
}
}
When I try to access it always tells me that I do not have authorization, even if I modify the edit function of UserPolicy.
Your authorization call is wrong. The first argument of the authorization method needs to be the action you are trying to authorize. So the correct code for authorization check would be:
$this->authorize('edit', $user);
You are using identity operator === for comparing the user models. Like return $authUser === $user;.
According to the php manual:
When using the identity operator (===), object variables are identical if and only if they refer to the same instance of the same class.
However, the $authUser instance and $user instance are different instances of same model. Use the comparison operator ==.
When using the comparison operator (==), object variables are compared in a simple manner, namely: Two object instances are equal if they have the same attributes and values (values are compared with ==), and are instances of the same class.
Now your user policy method will be:
public function edit(User $authUser, User $user) {
return $authUser == $user;
}
Laravel makes me crazy showing the next following error:
The error happens when login controller is triggered.
public function login(Request $request)
{
$this->validateLogin($request);
// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
$throttles = $this->isUsingThrottlesLoginsTrait();
if ($throttles && $lockedOut = $this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
$credentials = $this->getCredentials($request);
// $credentials['type']=1;
// return $credentials;die;
if (Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'))) {
return $this->handleUserWasAuthenticated($request, $throttles);
}
// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
if ($throttles && ! $lockedOut) {
$this->incrementLoginAttempts($request);
}
return $this->sendFailedLoginResponse($request);
}
Seems like is an error related to the database though the login service is managed by an API that works well. I don't know why this error happens. I'm really newbie to Laravel so if anyone can guide me I will thank.
User Model
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password','phone','photo','first_name', 'contact_name', 'address', 'phone_number', 'fiscal_number', 'about_us',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function subscription()
{
return $this->hasMany('App\Models\EmployerSubscription','employer_id','_id');
}
public function jobseekers()
{
return $this->hasOne('App\Models\JobSeekers');
}
public function experience()
{
return $this->hasMany('App\Models\Experience')->where('status',true);
}
}
The default User model is extending Illuminate\Database\Eloquent\Model, where it has to extend Jenssegers\Mongodb\Eloquent\Model to work with MongoDB. By changing the User model in app/User.php, this can be resolved. Change your user model to following-
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class User extends \Jenssegers\Mongodb\Eloquent\Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword, Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password','phone','photo','first_name', 'contact_name', 'address', 'phone_number', 'fiscal_number', 'about_us',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function subscription()
{
return $this->hasMany('App\Models\EmployerSubscription','employer_id','_id');
}
public function jobseekers()
{
return $this->hasOne('App\Models\JobSeekers');
}
public function experience()
{
return $this->hasMany('App\Models\Experience')->where('status',true);
}
}
As you are saying that login service is managed by API, but as i can see you have used laravel Auth service provider for authentication. Please check database settings in your project .env file. if issue still persist kindly follow below steps.
composer dump-autoload
php artisan cache:clear
php artisan view:clear
php artisan config:clear
and restart your server. Hope that will work.
Go to User model and add the following;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
class User extends Eloquent implements AuthenticatableContract,