Applying Passport scopes conditionally on api resource methods - laravel

I am using passport personal access token to secure my API. Here are some of the code snippets.
// api.php
Route::apiResource('categories', 'CategoryController');
AuthServiceProvider.php
public function boot()
{
$this->registerPolicies();
//scopes
Passport::tokensCan([
'admin' => 'Perform every action',
'user' => 'Perform only normal user actions',
]);
// passport routes
Passport::routes();
//
}
CategoryController.php
class CategoryController extends Controller
{
function __construct()
{
$this->middleware('api:auth', ['scopes: admin']);
}
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(Request $request)
{
return CategoryResource::collection(Category::all());
}
...
As you can see I have used the admin scope which is only accessible to admin users. But the problem is category model can only be edited or updated by the admin scope and can be accessed by both admin and user scopes. What could be the best approach to address this issue?

One solution that worked for me is I have used middleware two times with different scopes.
class CategoryController extends Controller
{
function __construct()
{
$this->middleware('api:auth', ['scopes: admin'])->except(['index']);
$this->middleware('api:auth', ['scopes: user'])->only(['index']);
}
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(Request $request)
{
return CategoryResource::collection(Category::all());
}
...

Related

Policies not hitting / working in Laravel 9 with Spatie Permission Package 5

Greetings. I'm having difficulties to use Policy Laravel 9. I have tried all day to figuring this out why POLICY class is not hitting by controller / route but could not find solution that. All possible solutions which I understand already tried.
Here is my code.
Policy class:
class PacketPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can create models.
*
* #param \App\Models\User $user
* #return \Illuminate\Auth\Access\Response|bool
*/
public function create(User $user)
{
dd('hit packet create');
}
}
And controller class
class PacketController extends Controller
{
/**
* Create the controller instance.
* Also check authorization on route level as per associated policies
*
* #return void
*/
public function __construct()
{
$this->authorizeResource(Packet::class, 'packet'); // Option-1 tried. Not working
}
public function create()
{
//request()->user()->can('create', Packet::class); // Option-2 tried. Not working
return inertia('Packet/Create', [
'item' => [],
]);
}
}
and even register policy classes in AuthServiceProvider class like so.
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* #var array<class-string, class-string>
*/
protected $policies = [
'App\Models\Packet' => 'App\Policies\PacketPolicy', // Not working
OutboundOrder::class => OutboundOrderPolicy::class, // Not working either
];
}
Route is
Route::resource('packet', PacketController::class);
Please help me out what i'm missing here. I don't understand why my Policy class not hitting when in visit domain/packet/create route

Laravel Login Controller - Direct to Admin or User Routes

I have a Laravel8 Project where I am doing everything from scratch so i can learn the system (newbie) - I have a LoginController with the code
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class LoginController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
// Show Login Page
return view('auth.login');
}
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
$this->validate($request, [
'email'=> 'required|email',
'password' => 'required',
]);
if (!auth()->attempt($request->only('email', 'password'), $request->remember)) {
return back()->with('status', 'Invalid Login Details' );
}
return redirect()->route('admin.dashboard');
}
I have the roles tables and pivot table set up but not sure how to amend the return redirect()->route('admin.dashboard'); to the correct code so depending if the user is an admin or a standard user it uses the correct route
You can give simple condition after login attempt success like below.
if (auth()->user()->role == 'admin') {
return redirect()->route('admin.dashboard');
}
return redirect()->route('user.dashboard');
As per your described question, you have a different table for assign roles to the user. So you have to create relation with roles table to identifies the role of the user.
You'll need to make sure to import the Auth facade at the top of the class. Next, let's check out the attempt method:
use Illuminate\Support\Facades\Auth;
...
public function store(Request $request) {
// ...
if (!auth()->attempt($request->only('email', 'password'), $request->remember)) {
return back()->with('status', 'Invalid Login Details' );
}
// Redirect to admin dashboard
return redirect()->intended('route.dashboard');
}
For more details read the Docs Manually Authenticating Users

laravel authentication redirect

I'm new to Laravel and have just added the Authentication package to an existing project.
Upon logging in, I want to be redirected to /Result a page that that I know works using a controller. If I type the URL /Result the page loads correctly but when I login I am being redirected to index each time rather than /Result
Routes
Route::get('/result','ResultsController#getResults')->name('result');
Auth::routes();
Route::get('/', 'HomeController#index')->name('/');
Home Controller
class HomeController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index()
{
return view('result');
}
}
Results Controller
class ResultsController extends Controller
{
public function getResults( )
{
$results = Result::all();
return view('/result', ['results' => $results]);
}
}
Login Controller
class LoginController extends Controller
{
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = 'result';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
/**
* #return
*/
public function authenticated()
{
return redirect()->route('result');
}
}
So far I can load index and be redirected to login, when I login I want to be redirected to /Result but instead I recieve an Undefined variable: results.
I have jumped to /Results by manipulating the URL and the page /Results does work.
Any help would be much appreciated, just le me know if you need any additional code examples from any other files.
thanks
James
First of all change the route name format use only result.
Route::get('/result','ResultsController#getResults')->name('result')
For redirect any route you can use LoginController authenticate method.
\App\Http\Controllers\Auth\LoginController.php
Add this method to that controller:
/**
* #return
*/
public function authenticated()
{
return redirect()->route('result');
}

Custom Guard Authentication Exception

I created a custom guard (admin) in my application, and everything looks good following this tutorial online (https://pusher.com/tutorials/multiple-authentication-guards-laravel). The only problem is when i try to access a view that is protected by the admin guard, instead of giving me a exception NotAuthenticate, is giving me a "InvalidArgumentException
Route [login] not defined.".
My Dashboard controller custom guard is:
public function __construct()
{
$this->middleware('auth:admin');
}
But the strange thing that i cant understand whats going on is that when i add in my web.php routes the "Auth::routes();", it works fine, the Exception NotAuthenticate gets fired.
Is there a reason why my custom only works has expected with i add the Auth::routes() ?
Admin Login Controller:
namespace App\Http\Controllers\Admin\Auth;
use Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Validation\ValidationException;
class LoginController extends Controller
{
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = '/admin/dashboard';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest:admin')->except('logout');
}
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.
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
return $this->sendLoginResponse($request);
}
// 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.
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
public function showLoginForm()
{
return view('admin.auth.login');
}
/**
* Log the user out of the application.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function logout(Request $request)
{
$this->guard()->logout();
$request->session()->invalidate();
return $this->loggedOut($request) ?: redirect('/admin');
}
/**
* Get the guard to be used during authentication.
*
* #return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard('admin');
}
}
Try following the following tutorial, it did the trick for me.
On a side note: Avoid modifying the existing controller that laravel ships with, instead extend it and implement your own functionality. This practice will take you the long way.
https://www.codementor.io/okoroaforchukwuemeka/9-tips-to-set-up-multiple-authentication-in-laravel-ak3gtwjvt

How do I pass data from LoginController to my HomeController?

I want to pass my input information ($request->all()) from my LoginController to my HomeController. How can I do that? The LoginController is generated by Laravel scaffold.
LoginController:
<?php
namespace App\Http\Controllers\Auth;
use \Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Auth\Authenticatable;
use Illuminate\Http\Request;
use App\Account;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct(Request $request)
{
$this->middleware('guest', ['except' => 'logout']);
}
/**
* Override the username method used to validate login
*
* #return string
*/
public function username()
{
return 'username';
}
}
HomeController in which dd($request->all() returns []
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Blog;
use App\Account;
use Illuminate\Foundation\Auth;
use Illuminate\Support\Facades\Input;
class HomeController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
//$this->middleware('auth');
}
/**
* Show the application dashboard.
*
* #return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$blogItems = Blog::all();
$onlinePlayers = Account::getOnlinePlayers()->count();
$onlineStaff = Account::getOnlineStaff()->count();
//return view('home.index', compact('blogItems', 'onlinePlayers', 'onlineStaff'));
return dd($request->all()); //This returns an empty array
}
}
Quick and dirty
The following method is a quick and dirty method. It relies on how Laravel's authentication is designed under the hood. If Laravel changes this in the next version, this may need to change with it.
In your LoginController, implement your own login() method. You will need to rename the login() method provided by the trait because we still want to call it:
class LoginController extends Controller
{
use AuthenticatesUsers {
login as traitLogin
}
public function login(Request $request)
{
$request->session()->flash('form_type', 'login');
return $this->traitLogin($request);
}
}
In your RegisterController, implement your own register() method. You will need to rename the register() method provided by the trait because we still want to call it:
class RegisterController extends Controller
{
use RegistersUsers {
register as traitRegister
}
public function register(Request $request)
{
$request->session()->flash('form_type', 'register');
return $this->traitRegister($request);
}
}
Now, in your HomeController, you can get the type of form that was submitted from the flashed session data.
class HomeController extends Controller
{
public function index(Request $request)
{
$form = $request->session()->get('form_type');
// the rest of your logic
}
}
Cleaner
In contrast to the method above, I would suggest using a hidden form value and a new middleware to process that form value.
This is a little bit cleaner because it doesn't rely on any of the built in Laravel authentication logic. If Laravel changes the name of the traits, the method names, the route actions, or the actual logic used inside the methods, it won't affect how this functionality is designed or how it works.
In your login and register forms, add a new hidden field:
Login form:
<input type="hidden" name="form_type" value="login" />
Register form:
<input type="hidden" name="form_type" value="register" />
Now, create a middleware that will process this new form element and flash the value to the session.
class FlashFormType
{
public function handle($request, Closure $next)
{
$response = $next($request);
if ($request->has('form_type')) {
$request->session()->flash('form_type', $request->input('form_type'));
}
return $response;
}
}
From here, you can either add this middleware to the web middleware group in app/Http/Kernel.php so it is used for all web requests, or you could just add this middleware to the LoginController and RegisterController constructors so that only they use it.
Once you've assigned the middleware somewhere, update your HomeController to access your flashed data:
class HomeController extends Controller
{
public function index(Request $request)
{
$form = $request->session()->get('form_type');
// the rest of your logic
}
}
NB: none of the provided code is tested. treat as pseudo-code.
If you want to get current user, that was authenticated with your login data, then \Auth:user() will go the trick. Here is index() of HomeController:
public function index()
{
return \Auth::user();
}
If you really need input data (actually I cannot imagine the case) , a solution may be to put request->all() in session session(['login_data' => $request->all() ]) and then retrieve it via session('login_data'). If you go that way, add this method to your LoginController:
/**
* Send the response after the user was authenticated.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
protected function sendLoginResponse(Request $request)
{
$request->session()->regenerate();
$this->clearLoginAttempts($request);
session(['login_data' => $request->all() ]);
return $this->authenticated($request, $this->guard()->user())
?: redirect()->intended($this->redirectPath());
}
This overrides original sendLoginResponse() method of AuthenticatesUsers trait. Then in HomeController put this:
public function index()
{
return session('login_data');
}
Hope this helps!

Resources