In my Laravel application, administrators can be assigned to jobs, I also have super administrators that can do everything.
Administrators that are not super administrators should only be able to access jobs they are assigned to.
Let's use a rough route for illustration:
http://localhost:3000/jobs/{job}
http://localhost:3000/jobs/{job}/notes{note}
In this scenario {job} is an id acquired via route model binding and a note is attached to a job.
Administrators assigned to jobs are done so via the following relationship method:
/**
* Get the jobs that this admin has access to via the pivot table
*
* #return void
*/
public function jobs()
{
return $this->belongsToMany(JobPost::class, 'job_post_admin', 'admin_id', 'job_post_id');
}
So I can use $user->jobs
I want to be able to say the following - if you're a super admin you can go anywhere, if you're not, you should be restricted to what you've been assigned to.
So if a user only has access to http://localhost:3000/jobs/{1} and they go to http://localhost:3000/jobs/{2} they should be redirected.
I created a Middleware called Access
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (Auth::guard('admin')->user()) {
$user = Auth::guard('admin')->user();
if ($user->is_admin) {
return $next($request);
} else {
if ($user->jobs->contains($job)) {
return $next($request);
} else {
return response('You do not have sufficient priveledges to perform this action.', 403);
}
}
} else {
return redirect()->back();
}
}
However, I'm confused as to how I would get the job ID from the URL.
I have a working MiddleWare that looks like this:
<?php
namespace App\Http\Middleware;
use Closure;
use Auth;
class Access
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (Auth::guard('admin')->user()) {
$user = Auth::guard('admin')->user();
$job = $request->vacancy;
if ($user->is_admin) {
return $next($request);
} else {
if ($user->jobs->contains($job)) {
return $next($request);
} else {
return response('You do not have sufficient priveledges to perform this action.', 403);
}
}
} else {
return redirect()->back();
}
}
}
I am now interested though, as without sounding dense I haven't really acknowledged authorization in Laravel, does it have benefits over Middleware?
Authentication is different to authorization. Laravel supports both.
You can do what you need using authorisation policies, there is no need for extra middleware.
First create a policy:
php artisan make:policy JobsPolicy --model=Job
This will make your boilerplate. Then you can add the actions:
class JobsPolicy {
use HandlesAuthorization;
//If you return true here the policy always succeeds
public function before(User $user, $ability) {
if ($user->is_admin) {
return true;
}
}
public function view(User $user, Job $job) {
return $user->jobs->contains($job);
}
public function create(User $user) {
return true; //Return true if the user can create jobs
}
public function update(User $user, Job $job) {
return $user->jobs->contains($job);
}
}
You need to also register your policy in the AuthServiceProvider in the $policies array:
protected $policies = [
Job::class => JobPolicy::class
];
Then you can add the already existing middleware e.g.:
Routes::get('jobs/{job}/notes/{note}', ...)->middleware("can:view,job");
This will ensure that the currently authenticated user can view the job specified by the route job parameter.
There's more information in the documentation
Related
When a user is not authenticated, Laravel redirects all request made to routes using auth::api middleware to the default laravel login page. However since I am building the API only, I want to return a 401 unauthorised response to any app that will be consuming my api. This should be simple but for some reason, I haven't been able to do just that. Here's what my code looks like
public function show(User $user)
{
if ($user->id == auth()->user()->id) {
// do something here
} else {
return response()->json([ 'status_message' => 'Unauthorised'], 401);
}
}
public function update(Request $request, User $user)
{
if ($user->id == auth()->user()->id) {
// do something here
} else {
return response()->json(['status_message' => 'Unathorised'], 401);
}
}
When I hit the endpoints calling these methods and the user isn't authenticated, I am redirected to the default laravel login page. How can I override this default behavior?
Call api.auth middleware in your routes as follows
$api->group(['middleware' => 'api.auth'], function ($api){
//your routes
});
Create a file Authenticate.php inside Middleware folder
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Factory as Auth;
class Authenticate {
/**
* The authentication guard factory instance.
*
* #var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* Create a new middleware instance.
*
* #param \Illuminate\Contracts\Auth\Factory $auth
* #return void
*/
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null $guard
* #return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if ($this->auth->guard($guard)->guest()) {
return response('Unauthorized.', 401);
}
return $next($request);
}
}
and in kernel.php which is inside http folder include the following
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
];
Problem solved guys. Here's how I did it, in the Handle.php file in app\Exceptions\ I added this code to the render function
if ($exception instanceof AuthenticationException) {
return response()->json(['status_message' => 'Unauthorised'], 401);
}
And that was it.
Note: this worked in laravel 5.8
I have a custom login controller and its using return redirect()->intended(route('home')) , as per the documentation this should send the user redirect the user to the URL they were attempting to access before being intercepted by the authentication middleware.
But for my case every time it is redirecting to home route.I am sure i have done correctly or at least i think i have done correctly. Can anyone please tell me where i am doing this wrong ??
My logincontroller is like this:
public function __construct()
{
$this->middleware('guest');
}
public function login(Request $request)
{
$validatedData = $request->validate([
'email' => 'required|email|max:255',
'password' => 'required|max:255',
]);
try {
$response = HelperFunctions::fetchData('post', 'login/login', [
'loginId' => $request->get('email'),
'password' => md5($request->get('password'))
]);
if ($response['code'] == 200 && $response['success']) {
session([
'api_token' => $response['sessionId'],
'user_data' => $response['data']['profile']
]);
return redirect()->intended(route('home'));
} else {
return redirect()->back()->with('error', 'Please provide valid credentials');
}
} catch (\Exception $e) {
return redirect()->back()->with('error', 'Opps!! Something is wrong. We are trying to fix it');
}
My authentication checking middleware
class checkAuthentication
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
//check if user is logged in
if(Session::has('api_token') && Session::has('user_data')) {
return $next($request);
}
return redirect(route('login'));
}
}
I also tried to dd() Session::get('url.intended') but it returns empty .
I did tried looking for reference on google, laracast & stackoverflow i found some reference , but those did not help me. Can anyone please help me thank you.
Some of the reference i have checked & tried:
https://laracasts.com/discuss/channels/laravel/redirect-intended-not-working-after-login?page=1
Laravel 5 - redirect()->intended() after authentication not going to intended
https://laravel.io/forum/11-24-2014-problems-with-redirectintended-and-auth
The simplest way would be to redirect()->guest() in your Auth middleware instead of the redirect you currently have:
return redirect()->guest(route('login'));
Using the guest() method on the redirector is what adds the url.intended value to the session.
Alternatively, instead of returning from the middleware if they're not authenticated you could throw an AuthenticationException:
public function handle($request, Closure $next)
{
//check if user is logged in
if(Session::has('api_token') && Session::has('user_data')) {
return $next($request);
}
throw new AuthenticationException('Unauthenticated.');
}
This will allow you to take advantage of Laravel's ExceptionHandler if you need to. You will also need to add use Illuminate\Auth\AuthenticationException; to the top of your class.
I also tried to dd() Session::get('url.intended') but it returns empty .
In my case, this key existed in my session, but for some weird reason, something funny happens after redirect()->intended() returns the redirect response. Maybe the response is discarded in transit, or intercepted by some other caller on the stack.
What I did to work around it was to manually obtain the intended url from the session and hand it over to the redirect (instead of relying on the underlying protocols of intended()).
By adding this
redirect(Session::get('url.intended', $this->redirectPath()));
at the bottom of LoginController#showLoginResponse, once user is authenticated, Laravel is able to remember intended url and redirect to it
I have also faced this problem and the best way I come up with is using the authenticated method.
So Override the authenticated method in your controller and use the intended route there.
/**
* The user has been authenticated.
*
* #param \Illuminate\Http\Request $request
* #param mixed $user
* #return mixed
*/
protected function authenticated(Request $request, $user)
{
if ($user->role == 'admin') {
return redirect()->intended('/admin');
} elseif ($user->role == 'user') {
return redirect()->intended('/');
}
}
also sometimes changing the RedirectIfAuthenticated middleware also helps.
so go to App\Http\Middleware\RedirectIfAuthenticated.php and edit it as well.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null $guard
* #return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
if ($request->user()->role == 'admin') {
return redirect()->intended('/admin');
} elseif ($request->user()->role == 'user') {
return redirect()->intended('/');
}
// return redirect('/home');
}
return $next($request);
}
}
Do this:
return redirect()->intended('home');
Instead of this:
return redirect()->intended(route('home'));
With the laravel 5.3 and above the concept of filters is gone and middleware is used instead. I have migrated my project with laravel 4 to 5.4.
I want to modify the DeviceLoginController that is when I am not logged in it must refresh to the login page. Other details can be seen in the controller page.
Problem: The controller page is useless as even when I am not logged in anyone can access this page and and anyone can fill anything. I have been trying to resolve this issue from 2 days still I am no where.
DeviceLoginController page looks like this:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\BaseController;
use Auth;
use Format;
use Input;
use DB;
use Session;
use Validator;
use Hash;
use Redirect;
use User;
use App\Models\License;
use App\Models\LicenseCount;
use App\Models\Manufacturer;
use App\Models\DeviceModel as Model;
use App\Models\Device;
use App\Models\Application;
class DeviceLoginController extends BaseController {
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth');
}
public function attempt()
{
$username = Format::ltr(Input::get("username"));
$device_key = Input::get("device_key");
$imei = Format::ltr(Input::get('imei'));
$model = Format::utr(Input::get('model'));
$manufacturer = Format::utr(Input::get('manufacturer'));
$app_code = Format::ltr(Input::get('app_code'));
$user = User::where('username', $username)->first();
if(!Hash::check($device_key, $user->device_key)) {
Event::fire('auth.login.fail', array($username, Request::getClientIp(), time()));
die("1");
}
Auth::loginUsingId($user->id);
// check if device is already registered under given user for given app
$license = License::where('device_imei', $imei)->where('app_code', $app_code)->where('user_username', $username);
// if device isn't registered, first check if device is registered by different user. If not, check if licenses are available or not with the user to register new device
if(!$license->count()) {
// checking if licenses are available or not
$license_count = LicenseCount::where('user_username', $username)->where('app_code', $app_code)->first();
// if licenses are left, register the device
if((int) $license_count['left']) {
$manufacturer = Manufacturer::firstOrCreate(array('name' => $manufacturer));
$model = Model::firstOrCreate(array('name' => $model, 'manufacturer_code' => $manufacturer->code));
$device = Device::where('imei', $imei)->first();
if(!$device) {
$device = Device::firstOrCreate(array('imei' => $imei, 'model_code' => $model->code));
}
License::create(array('device_imei' => $imei, 'app_code' => $app_code, "user_username" => $username, "expiry_date" => date("Y-m-d H:i:s", strtotime("+1 year"))));
$license_count->left = Format::itr($license_count->left) - 1;
$license_count->save();
} else {
// Prints 3, if the device is not registered and user has no more licenses left for the given app
die("3");
}
// Prints 2, if the device was not previously registered and it is now registered under given user for given app
Session::put('login_response', '2');
} else {
// Prints 0, if device is already registered under given user for given app
Session::put('login_response', '0');
}
}
}
My authenticate.php file looks like this
<?php
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as Auth;
class Authenticate
{
/**
* The authentication factory instance.
*
* #var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* Create a new middleware instance.
*
* #param \Illuminate\Contracts\Auth\Factory $auth
* #return void
*/
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string[] ...$guards
* #return mixed
*
* #throws \Illuminate\Auth\AuthenticationException
*/
public function handle($request, Closure $next, $guards=null)
{
if($this->auth ->guest())
{
if($request->ajax())
{
return response('unauthorized',401);
}
else
{
return redirect()->guest('login');
}
}
//$this->authenticate($guards);
return $next($request);
}
/**
* Determine if the user is logged in to any of the given guards.
*
* #param array $guards
* #return void
*
* #throws \Illuminate\Auth\AuthenticationException
*/
protected function authenticate(array $guards)
{
if (empty($guards)) {
return $this->auth->authenticate();
}
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
}
throw new AuthenticationException('Unauthenticated.', $guards);
}
}
I am new to Laravel please forgive me if I have asked some silly question. I am clueless what to do at this point. Please help and let me know if I need to add some other file.
It's great you have done the migration to Laravel 5.4. However, I suggest you go through the documentation first or watch the Laravel 5.4 from Scratch series.
For your question, you need the put the route that calls the controller function under the 'auth' middleware. Laravel provides this middleware out of the box. You can change the route to where the user will be redirected if he is not logged and calls the route.
Please go through the documentation for this.
Suppose your route is 'admin/profile' and you have defined this in the web.php routes file, you can add a middleware to it as shown (picked this example from the DOC.)
Route::get('admin/profile', function () {
//
})->middleware('auth');
To place multiple routes under the same middleware, you can use Route groups.
I am writing features using Behat in my Laravel projects.
My scenarios are just to check if the user is logged in through CAS system successfully or fail.
Scenario: Authentication
Given I am on the homepage
And I press "Login" button
Then I should see "Login Successfully"
However, it's not working as I expected.
Any idea?
Thanks much
This is the solution that one of my colleague found, and implemented successful
app/Http/CASAuth.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
class MiamiCASAuth
{
protected $auth;
protected $cas;
public function __construct(Guard $auth)
{
if(strcmp(env(“APP_ENV”),“acceptance”) !== 0) {
$this->auth = $auth;
$this->cas = app(‘cas’);
}
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(strcmp(env("APP_ENV"),"acceptance") !== 0) {
if ($this->cas->isAuthenticated()) {
// Store the user credentials in a Laravel managed session
session()->put('cas_user', $this->cas->user());
} else {
if ($request->ajax()) {
return response('Unauthorized.', 401);
}
$this->cas->authenticate();
}
}
return $next($request);
}
}
features/bootstrap/FeatureContext.php
/**
* #Given /^I am logged in as "([^"]*)"$/
*/
public function iAmLoggedInAs($id)
{
if (Session::isStarted()) {
Session::regenerate(true);
} else {
Session::start();
}
// Set server session
Session::put('cas_user', $id);
// Set client cookie
$minkSession = $this->getSession();
$minkSession->setCookie(Session::getName(), Crypt::encrypt(Session::getId()));
}
Scenario: login
Given I am logged in as "trinhdh"
And I am on the homepage
Then the response should contain "Logged in as: Trinh Daniel"
That should works.
When my users register in my app it automatically redirects them to /dashboard which is technically fine, but it isn't checking to see if the confirmed column in the database has a value of 1 or 0, it's just logging in based on the username and password.
I will happily include code but right now I don't actually know what code you guys need to see.
I need it to check the confirmed column and if it's a 0, not to log them in and throw and error.
thanks for any info,
Andy
I achieve this by utilizing middleware:
My routes.php:
Route::get('home', ['middleware' => 'auth', function () {
return "This is just an example";
}]);
My Kernel.php:
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
];
My Authenticate.php middleware:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
class Authenticate
{
/**
* The Guard implementation.
*
* #var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* #param Guard $auth
* #return void
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($this->auth->guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
} else {
return redirect()->guest('auth/login');
}
}
$user = $this->auth->user();
if (!$user->confirmed) {
$this->auth->logout();
return redirect()->guest('auth/login')->with('error', 'Please confirm your e-mail address to continue.');
}
if (!$user->type) {
$this->auth->logout();
return redirect()->guest('auth/login')->with('error', 'A user configuration error has occurred. Please contact an administrator for assistance.');
}
return $next($request);
}
}
I tried to cut this down as much as possible for you.