I am working on an application, Where i have three type of users(real scenario), The application have three areas Freelancers and Lms (Learning Management systems) and admin panel for both :
Admins => Users of the admin panel, Where all the statistics/data is there.
Freelancers section : Freelancers section signin/signup
Lms section => Learning management system section's users signin/signup
Currently i am not using any kind of multi auth functionalities, And whenever i login in a user in the freelancer section and i goes to the lms section the authenticated user is available there.
As i am using only one table for the users where i have a column userType(Which is not in use but there for future proofing).
I know a couple of packages Like this one. which i can implement but i haven't and thought that there might be a better way and stackoverflow community can provide one.
My question is how to handle this type of situation, Whats the most efficient and robust way.
This is how I would do it.
I am going to skip long details about setting up auth controllers and views. They are quite straight forward to scaffold using the artisan console.
The first thing we need is new field on your Users table. An admin field if you only have two levels (admin and non-admin). An ENUM value in your case.
Depending on what the value in that field is you want to grant (or not) access to certain sections/pages/resources etc.
The artisan console generates all the necessary pages, middle-ware, routes for a basic login. But after you have done that you will need a second middle-ware to check for different levels of access. Lets call it CheckAdmin.
Use the following command
php artisan make:middleware CheckAdmin. This creates a new middleware with specified name in app\Http\Middleware
Now register the middleware in Kernel.php (Last line of code). This gives the middleware class that we just created a name (admin in this case).
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'admin' => \App\Http\Middleware\CheckAdmin::class,
];
All the routes in your that check for certain admin rights should use the admin middleware we just registered right after the auth factory middleware provided with laravel. There are two ways of doing this depending how you are building your app/website.
A. NOT using a resources controllers for the route.
Go to your routes web.php file. And register the auth and admin middleware for a request. An example follows.
Route::get('/example-admin-only-route', function () { //SOME LOGIC
})->middleware('auth', 'admin');
B. USING resources controllers. (Which you probably should whenever you can)
In the constructor of the resource controller. ExampleController.php for our Example resource.
class ExampleController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('admin');
}
...
}
Write the logic for your CheckAdmin middleware.
namespace App\Http\Middleware;
use Closure;
class CheckAdmin
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if($request->user()->admin == 'Admin') //or FreeLancers or LMS (or maybe Unicorns)
{
return $next($request);
}
else
{
return redirect("/not-worthy");
}
}
}
The code checks the admin privileges and either lets the request pass or does something else with it. In our-case redirects to a different end-point. But you may wish to do more creative things with it.
Cheers.
It's probably not multiauth you're looking for but permissions on certain pages. Take a look at: https://github.com/spatie/laravel-permission
It's very simple and straightforward. It even lets you single out users, that for example both have admin level, but just one can view it.
The most efficient and robust way is to use simple user_type column to store user type and create some helpers. For example, to check if user is an admin you can create something like this in the User model:
public function isAdmin()
{
return auth()->check() && auth()->user()->user_type === 3;
}
This is the simplest method with a lot of advantages. It's good for apps that use a lot of relationships, it's good for speed etc.
Related
I have three services, Tickets, Cards and Certificates, all in services table. the table columns looks like this:\
user_id, Service, amount_paid, created_at, created_by, transaction_id
this is my route:
Route::get('print/enumeration/{id}', 'TransactionController#printEnumeration');
I want the user to be taken to a separate page based on the service selected. I know i can use middlewares but I dont have a great knowlegde on how to implement it, any help will be greatly appreciated, Thanks.
First create middleware using
php artisan make:middleware midlewareName
Register middleware app/Http/Kernel.php
protected $routeMiddleware = [
'name' => \App\Http\Middleware\name::class,
];
now define your middleware app/Http/Middleware
example
public function handle($request, Closure $next)
{
if (Auth::check() && Auth::user()->isRole()=="admin") {
// Redirect...
return $next($request);
}
return redirect('login');
}
read docs for more details https://laravel.com/docs/6.x/middleware
first, create your middleware by running:
php artisan make:middleware ServiceMiddleWare
now open the file created in app/Http/Middleware directory. inside handle method you can place a switch statement and set a case per service and define redirects.
do not forget to register your middleware in kernel. take a look at this document
I'm working on a project with laravel. in my project there's two type of users one of them are admins and other one is normal users.
btw project is only provides API and there's no blade views.
I give a token to any user or admin logins with the api. and application will identify user or admin by sending that token with an authorization header and I check if token is validate and the user type is admin then give access to the admin features for that client.
here's my code for this part:
$admin = Auth::guard('admin-api')->user();
if ($admin) {
// allow to use admin features
}
else {
return response()->json(['error' => 'token is invalid'], 401);
}
I read something about applying Restrictions on a controller class in laravel and it was written there to add a constructor like this into controller class:
public function __construct() {
$this->middleware('admin-api');
}
and also there's something like that just for Restricting routes. like this
but I just want to know is it necessary to add same constructor to my controller class while the project just provides API? or the way that I'm doing is correct?
You are doing it right.
I would prefer restricting the request via routes, so there is no need to add constructor on each new Controllers.
Route::middleware(['admin-api'])
->group(function () {
Route::get('cart', 'Carts\CartController#retreive');
Route::post('cart/coupon', 'Carts\CartCouponController#addCoupon');
Route::delete('cart/coupon', 'Carts\CartCouponController#deleteCoupon');
Route::delete('cart/flyer', 'Carts\CartController#deleteFlyer');
});
This will apply the admin-api middleware on all the routes in the group and there is no need to add a constructor on Carts\CartController and Carts\CartCouponController, just to have middleware restriction.
I want to implement localization on my website using Laravel 5.5.
However, I am not sure what the standard practice when using localization should be. I have used the LocalizationController module from the Laravel documentation. My goal is to have the localization option selected via a dropdown. Then the user's selection should be remember.
Do I store their selection in a database for future use?
Or, is this something to keep in a cookie?
Side note:
(I want to avoid having their selection in the url. I'll either pass the data in a request or get method.)
For registered and logged-in users i recommend to store the users language in the database. Everytime a user logs in the application should set the language for the current user. Maybe you take a closer look on middleware. Build a language middleware, register it as new middlewaregroup and assign it to every route (-group) you need. A middleware could look like this:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class LanguageMiddleware
{
public function handle($request, Closure $next)
{
if(Auth::check()){
// user is logged in
App::setLocale(Auth::user()->language);
return $next($request);
}
App::setLocale(config('app.locale'));
return $next($request);
}
}
Now register the new middleware in app/Http/Kernel.php as new middleware-group under protected $middlwareGroups:
// other middleware-groups
'language' => [
\App\Http\Middleware\LanguageMiddleware::class
]
Finally assign middelware-group to route (-group):
Route::group(['middleware' => ['language']], function(){
// Routes...
});
Unfortunately there is no build-in function to show a dropdown-language-select. But you can simply build a blade-partial which you can integrate in your navbar or where-ever you want to show/use it. You could ask new users during registration for their preferred language.
Guests/unregistered users could use the dropdown. By default they should see the default language.
Hopefully this helps you.
After following a tutorial on how the built-in acl of laravel works I tried it and it works well by defining every route by itself.
Now I'm trying to use a resource but it's not working as intended. I added the following code to my routes file:
Route::group(['middleware' => 'acl:create_client'], function()
{
Route::resource('clients', 'ClientController');
});
Now I understand what the problem is:
all the methods in the Clientcontroller will be checked against my db if this user has the acl:create_client, resulting in all methods available to the logged in user that has this acl.
How do I split every method to use it's own acl without having to write it like this:
Route::get('/client/create', [
'middleware' => 'acl:create_client',
'as' => 'clients.create',
'uses' => 'ClientController#create'
]);
Resulting in something like this:
create needs create_client
index needs index_client
update need update_client
etc etc
The bottom line is: you need to setup the 'list' in the Access Control List (ACL) somehow. IMO, the most flexible way would be to pull this list from the database based on the session user; you're off to a good start. You can skip the explicit route assignment by using the already assigned 'as' that you define in a route. An example route:
Route::get('/', ['as'=>'clients.create', 'uses'=>'ClientsController#create']);
Here, you would use the 'clients.create' in your ACL check. Just remember: the ACL will still need the 'as' value set for all your routes (which is good to do anyway).
Step-by-step
Now that you have the required background information, here's how to get it working. These steps assume you were able to correctly setup the tutorial code and database. This will stick to the original tutorial setup and will focus on making the ACL independent from the route configuration.
1) In App\Http\Middleware\Acl\CheckPermission, you will need to replace the argument $permission = null with the 'as' string that you set in routes.php. The new code:
<?php namespace App\Http\Middleware;
use Closure;
class CheckPermission
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next/*, $permission = null REMOVE THIS*/)
{
// Add the next two lines:
$action = $request->route()->getAction();
$permission = isset($action['as']) ? $action['as'] : '';
if (!app('Illuminate\Contracts\Auth\Guard')->guest()) {
if ($request->user()->can($permission)) {
return $next($request);
}
}
return $request->ajax ? response('Unauthorized.', 401) : redirect('/login');
}
}
2) Now, you need to assign this middleware in a different manner. You do not want to use a specific permission, but instead use the 'as' string we just setup in the middleware. You can assign the middleware in two different ways: a) assign it to a group of routes, or b) assign it to every page. I suggest using 2a instead of 2b, because you may not want to use the ACL on all routes.
2a) Here's the method to assign it to only a group of routes. The two important things to notice here are the 'as'=>'clients.*' strings and the assignment of the middleware to the route group 'middleware' => 'acl'. Also note this route group does not pass the extra string parameter like the tutorial does (e.g. 'middleware' => 'acl:manage_user'). This is because we removed that argument from the handle() function above. You will need to change these example routes to match your target URIs and controller functions.
Route::group(['middleware' => 'acl'], function()
{
Route::get('/clients', ['as'=>'clients.view', 'uses'=>'ClientsController#index']);
Route::get('/clients/new', ['as'=>'clients.create', 'uses'=>'ClientsController#create']);
// Add more routes ...
}
2b) Here's how to assign it to every page. The tutorial uses the file /app/Http/Kernel.php to setup the middleware as a $routeMiddleware. This is the correct way to do it for step 2a above, but not if you want it on every page. To make the middleware a global middleware: add '\App\Http\Middleware\CheckPermission' to the $middleware variable found in the same file. You will not need the $routeMiddleware addition from the tutorial if you use the global variable.
3) In the tutorial database, you need to use the 'as' string in the permissions table within the permission_slug column. Here are example SQL inserts that allows user with id 123 to access route clients.create. These two create the permission and role we need to create access to the 'client.create' route.
INSERT INTO permissions ('permission_title', 'permission_slug', 'permission_description')
VALUES ('Create a Client', 'clients.create', 'Allow the user to create a client');
INSERT INTO roles ('role_title', 'role_slug')
VALUES ('Client Admin', 'clients.admin');
For the next query, you need to know the id of the two rows above. This assumes your database was newly created with no rows yet added, so each of the inserts will be id=1. This says: permission with id=1 is assigned to role with id=1.
INSERT INTO permission_role ('permission_id', 'role_id') VALUES (1, 1);
The next query also assumes the new role will be id=1 and the user id is 123. This assigns the new role with id=1 to the existing user with id=123.
INSERT INTO role_user ('role_id', 'user_id') VALUES (1, 123);
At this point, you should have a user with id=123 that has the Client Admin role. The Client Admin role should have the 'clients.create' permission. When you are logged in as user id=123, you will be verified to have the 'clients.create' permission, and you should be able to access the page (example.com/clients/new in my example). Any other user will not have access, and they will be redirected to the login page (this doesn't make sense to me if you're already logged in; this is just what the tutorial setup).
I recommend you do not build acl by yourself,there have some good packages out there like entrust
and if you truly want know the principle or laravel acl just follow this video tutorial from laracast laracast laravel acl tutorial
I am building APIs for my Android app using laravel and default session driver set to REDIS.
I found a good article here http://dor.ky/laravel-prevent-sessions-for-routes-via-a-filter/ which sort of serves the purpose.
However when ever I hit the url it also hits the redis and generates the key which is empty. Now I want avoid creating empty session keys in redis. Ideally it should not hit the redis How can I do that?
Can we customise sessios in a way so that sessions are generated only for specific routes (or disable for specific routes)?
I can explain more with specific use case, please let me know.
Its really easy using the middleware in Laravel 5, I needed any request with an API key not to have a session and I simply did :
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Session\Middleware\StartSession as BaseStartSession;
class StartSession extends BaseStartSession
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(\Request::has('api_key'))
{
\Config::set('session.driver', 'array');
}
return parent::handle($request, $next);
}
}
Also you will need to extend the SessionServiceProvider as follows:
<?php namespace App\Providers;
use Illuminate\Session\SessionServiceProvider as BaseSessionServiceProvider;
class SessionServiceProvider extends BaseSessionServiceProvider
{
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->registerSessionManager();
$this->registerSessionDriver();
$this->app->singleton('App\Http\Middleware\StartSession');
}
}
and place in your config/app.php under providers:
'App\Providers\SessionServiceProvider',
Also you must change it in your kernel file: App/Http/Kernel.php, in the $middlewareGroups section change the default entry, \Illuminate\Session\Middleware\StartSession::class, to your new class \App\Http\Middleware\StartSession::class,.
In Laravel 5, just don't use the StartSession, ShareErrorsFromSession, and VerifyCsrfToken middlewares.
In my application I've moved these three middlewares from the web group to a new stateful group, and then I have included this stateful group on routes which need to know about the session (in addition to web in all cases, in my app at least). The other routes belong to either the web or api groups.
Now when making requests to the routes which are not using the stateful middleware group session cookies are not sent back.
The simplest way to achieve this is to Make your own AppStartSession middleware that subclasses Illuminate\Session\Middleware\StartSession and the replace the class being used in kernel.php. The only method you need to override in your subclass is sessionConfigured() for which you can return false to disable the session or parent::sessionConfigured() to allow it.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Session\Middleware\StartSession;
class AppStartSession extends StartSession
{
protected function sessionConfigured(){
if(!\Request::has('api_key')){
return false;
}else{
return parent::sessionConfigured();
}
}
}
kernel.php (see *** comment for where the change is done)
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* #var array
*/
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
// *** Replace start session class
// \Illuminate\Session\Middleware\StartSession::class,
\App\Http\Middleware\AppStartSession::class,
// *** Also comment these ones that depend on there always being a session.
//\Illuminate\View\Middleware\ShareErrorsFromSession::class,
//\App\Http\Middleware\VerifyCsrfToken::class,
];
/**
* The application's route middleware.
*
* #var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
];
}
Don't fight the framework, embrace it!
Since Laravel 5.2, when middleware groups were introduced, you may disable session for certain routes by defining them outside of the "web" middleware group (which includes the StartSession middleware responsible for session handling). As on latest 5.2.x versions the whole default routes.php file is wrapped with "web" middleware group, you need to make some modification in app/Providers/RouteServiceProvider.php file, as described here.
There appears to be a way to accomplish this using a session reject callback.
Relevant sources...
https://github.com/laravel/framework/blob/4.2/src/Illuminate/Foundation/Application.php#L655
https://github.com/laravel/framework/blob/4.2/src/Illuminate/Foundation/Application.php#L660
https://github.com/laravel/framework/blob/4.2/src/Illuminate/Session/Middleware.php#L60
https://github.com/laravel/framework/blob/4.2/src/Illuminate/Session/Middleware.php#L97
I can't find many references to this around the web, but reading more through the source it appears that if the session reject callback returns a truthy value, the session will be forced to use an array driver for the request rather than whatever is configured. Your callback also gets the current request injected so you can do some logic based on the request parameters.
I've only tested this on a local Laravel 4.2 install but it seems to work. You just need to bind a function to session.reject.
First, create a SessionRejectServiceProvider (or something like that)
<?php
use \Illuminate\Support\ServiceProvider;
class SessionRejectServiceProvider extends ServiceProvider {
public function register()
{
$me = $this;
$this->app->bind('session.reject', function($app)use($me){
return function($request)use($me){
return call_user_func_array(array($me, 'reject'), array($request));
};
});
}
// Put the guts of whatever you want to do in here, in this case I've
// disabled sessions for every request that is an Ajax request, you
// could do something else like check the path against a list and
// selectively return true if there's a match.
protected function reject($request)
{
return $request->ajax();
}
}
Then add it to your providers in your app/config/app.php
<?php
return array(
// ... other stuff
'providers' => array(
// ... existing stuff...
'SessionRejectServiceProvider',
),
);
Edit / More Info
The net result is that the reject() method is called on every request to your application, before the session is started. If your reject() method returns true, sessions will be set to the array driver and basically do nothing. You can find a lot of useful info the $request parameter to determine this, here's the API reference for the request object in 4.2.
http://laravel.com/api/4.2/Illuminate/Http/Request.html
I've been trying to accomplish a similar feature.
Our API is stateless except for 1 route - the version 1 cart.
I ended up with setting 'driver' in the app/config/session.php like this ...
'driver' => 'v1/cart' === Request::getDecodedPath() ? 'native' : 'array',
Nothing magic. Initially we though of using a before filter, but that wasn't happening early enough.
It seems a simple way to do things, but I may be missing something.
Putting the switch in the config seems an easy place for other developers to see what the driver is whereas putting it in a service provider is so tucked out of the way, without knowing what service providers are installed and what they interact with, it would be far harder to debug.
Anyway. Hope this is of some use.
As pointed out below ... DO NOT CACHE YOUR CONFIG IF IT IS DYNAMIC.
Which does lead to it being of limited use. As soon as we no longer need to support v1/cart, we will be dropping this route and then be back on a static config.
Laravel default have two routes group called web and api, the api routes group default without session.
So, we can write any route role to routes/api.php, will not use session default.
If not want to use the api prefix url, we can modify app\Providers\RouteServiceProvider add a new group like this:
Route::middleware('api')
->namespace($this->namespace)
->group(base_path('routes/static.php'));
Now you can place any routes into routes/static.php file will not to use session.
Hope helpful.
Laravel 5x
In the App\Providers\RouteServiceProvider file, just copy the mapApiRoutes() method to a new method called mapStaticRoutes(), remove the prefix('api') call, and add "routes/static.php" (you will need to create this file). This will use the same stateless "api" middleware and not have an /api prefix assigned to the routes.
protected function mapStaticRoutes()
{
Route::middleware('api')
->namespace($this->namespace)
->group(base_path('routes/static.php'));
}
Just update the "map()" method to call "$this->mapStaticRoutes();" so that it knows of your new file. And any route added there should now be stateless and it wasn't much work.....
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
// Static Routes (stateless, no /api prefix)
$this->mapStaticRoutes();
}
static.php
// Health Check / Status Route (No Auth)
Route::get('/status', function() {
return response()->json([
'app' => 'My Awesome App',
'status' => 'OK'
]);
});