Custom Middleware Not working when using Sanctum - laravel

I am using Dynamic database for my laravel API Project. I am doing that with a middleware. When I use auth:sanctum in api rout middleware not working. But normally its working fine.
Route> api.php
Route::group(['middleware'=>'database'], function() {
Route::group(['namespace' => 'App\Http\Controllers\Admin', 'middleware' => ['auth:sanctum']], function () {
Route::get('dashboard', function () {
return "conncted sucessfully to database ".\DB::connection()->getDatabaseName();
});
});
});
Middleware > Database.php
namespace App\Http\Middleware;
use Closure;
use App\Models\SoftClient;
class Database
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$route = app('router')->getRoutes()->match($request);
$prefix = $route->action['name'];
$s_client = SoftClient::where('url', $prefix)->first();
\Config::set('database.connections.multidomain.host', $s_client->db_host );
\Config::set('database.connections.multidomain.database', $s_client->db_name );
\Config::set('database.connections.multidomain.username', $s_client->db_username );
\Config::set('database.connections.multidomain.password', $s_client->db_password );
\DB::reconnect('multidomain');
return $next($request);
}
}
'database' => \App\Http\Middleware\Database::class,
I add this to Karnel.php
When I send request to dashboard I got bellow error. If I not use auth:sanctum It's working fine.
Illuminate\Database\QueryException: SQLSTATE[HY000] [1045] Access denied for user ''#'localhost' (using password: NO)

auth sanctum is used to authenticate user. Maybe you are directly accessing the routes with authenticating user.

when you check the routes with "php artisan route:list", do you see the middleware you configured with the route?
I had a similar problem, where route:list pointed me to the fact that the middleware I defined with the group wasn't actually used, and in the end I dropped the "group" and just defined the routes individually. A bit cumbersome but it worked.

if your goal is to use laravel sanctum for dynamic database connection then i use this method and it works
step 1 : copy this file Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful to our middlewear and then modify duplicate this file like this
<?php
namespace App\Http\Middleware;
use Illuminate\Routing\Pipeline;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\App;
class EnsureFrontendRequestsAreStateful
{
/**
* Handle the incoming requests.
*
* #param \Illuminate\Http\Request $request
* #param callable $next
* #return \Illuminate\Http\Response
*/
public function handle($request, $next)
{
$this->changeDatabaseConnection($request);
$this->configureSecureCookieSessions();
return (new Pipeline(app()))->send($request)->through(static::fromFrontend($request) ? [
function ($request, $next) {
$request->attributes->set('sanctum', true);
return $next($request);
},
config('sanctum.middleware.encrypt_cookies', \Illuminate\Cookie\Middleware\EncryptCookies::class),
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
config('sanctum.middleware.verify_csrf_token', \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class),
] : [])->then(function ($request) use ($next) {
return $next($request);
});
}
protected function changeDatabaseConnection($request)
{
$encodeCc = $request->input('cc');
$cc = json_decode(base64_decode($encodeCc));
if ((!empty($cc) && $cc != 'axiasolusi')) {
$compCode = $cc;
if (App::environment() === 'local') {
$compCode = 'ess-' . $compCode;
}
} else {
$compCode = env('DB_DATABASE');
}
Config::set('database.connections.' . env("DB_CONNECTION") . '.database', $compCode);
}
/**
* Configure secure cookie sessions.
*
* #return void
*/
protected function configureSecureCookieSessions()
{
config([
'session.http_only' => true,
'session.same_site' => 'lax',
]);
}
/**
* Determine if the given request is from the first-party application frontend.
*
* #param \Illuminate\Http\Request $request
* #return bool
*/
public static function fromFrontend($request)
{
$domain = $request->headers->get('referer') ?: $request->headers->get('origin');
if (is_null($domain)) {
return false;
}
$domain = Str::replaceFirst('https://', '', $domain);
$domain = Str::replaceFirst('http://', '', $domain);
$domain = Str::endsWith($domain, '/') ? $domain : "{$domain}/";
$stateful = array_filter(config('sanctum.stateful', []));
return Str::is(Collection::make($stateful)->map(function ($uri) {
return trim($uri) . '/*';
})->all(), $domain);
}
}
step 2 : change karnel like this
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, // original
\App\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
and last step : change route api
Route::middleware('change.database')->group(function () {
Route::post('/verify-company-code', 'Api\Web\Auth#verifyCompanyCode');
Route::post('/verify-user-account', 'Api\Web\Auth#verifyUserAccount');
Route::post('/verify-user-pin', 'Api\Web\Auth#verifyUserPin');
Route::post('/forgot-password', 'Api\Web\Auth#forgotPassword');
Route::post('/reset-password', 'Api\Web\Auth#resetPassword');
});
Route::middleware(['auth:sanctum'])->group(function () {
Route::post('logout', [Auth::class, 'logout']);
Route::post('logout-all', 'Api\Web\Auth#logoutAll');
});
});

Related

ERR_TOO_MANY_REDIRECTS - Laravel 5.7

I'm using laravel 5.7. I have a users table with a column user_type where
0 = default users
1 = Professionals
2 = Facilities
8 = Employees
9 = Managers
10 = Administrator
And another column which is boolean is_premium which returns either true or false.
I have created a middleware for admin
namespace App\Http\Middleware;
use Closure;
use Auth;
class IsAdmin
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (Auth::user() && Auth::user()->usertype == 10) {
return $next($request);
}
return redirect('/administration/dashboard');
}
}
And i have used it in the routes/web.php file as
Route::group(['prefix' => 'administration', 'middleware' => 'admin'], function(){
Route::get('/dashboard', 'Admin\DashboardController#index')->name('admin.dashboard');
});
When i visit the admin dashboard route it says
ERR_TOO_MANY_REDIRECTS
I know there is something im doing wrong...what is it?
And im assuming to create middlewares for other usertypes too and use in the routes file. Is this the right way to do?
Try false logic first:
...
public function handle($request, Closure $next)
{
if ( ! (Auth::user() && Auth::user()->usertype == 10) ) {
return redirect('/home');
}
return $next($request);
}
...

How to setup two route groups using middleware in Laravel 5.4

I'm setting up a web application in which I would like to distinguish two route groups. Both groups work as expected on their own, but when combined one of them fails. I've checked documentation on L5.4 website and followed instructions. After a whole day of digging decided to ask you.
Here is my routes/web.php file:
Route::group(['middleware' => ['auth']], function () {
Route::group(['middleware' => ['medewerker']], function () {
Route::get('/urencorrectie','UrenRegelsController#urencorrectie');
});
Route::group(['middleware' => ['officemanager']], function () {
Route::get('/', 'DashboardController#index');
Route::post('/', 'DashboardController#index');
Route::get('/profile', function(){
return view('profile');});
});
});
Auth::routes();
Route::get('/home', 'HomeController#index');
In order to enable roles I addes a column Rolid to the user model. Rol 1 is officemanager and role 3 is employee.
Then in the subsequent middleware we find employee.php:
namespace App\Http\Middleware;
use Closure;
use Auth;
class Employee
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(Auth::user()->Rolid=='3')
{
return $next($request);
}
else
{
return redirect('/home');
}
}
}
The Middleware officemanager.php file contains:
namespace App\Http\Middleware;
use Closure;
use Auth;
class Officemanager
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$user=Auth::user();
if(Auth::user()->Rolid=='1')
{
return $next($request);
}
else
{
return redirect('/home');
}
}
}
The code as is produces the following result:
- When an Officemanager logs in, he/she is redirected to the proper routes. Everything works fine.
- When an Employee logs in, he/she gets redirected to the /home redirect (bottom of routing/web.php file).
Any clues or help is very welcome. Kinda stuck on something probably basic.
[UPDATE]
In kernel.php both classes are mapped:
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,
'officemanager' => \App\Http\Middleware\Officemanager::class,
'employee' => \App\Http\Middleware\Employee::class,
];
The only thing that I can think of is that the Rolid of employee is not 3 - so try to debug it.
In general, it is not recommended to rely on DB ids in your code, because they can change between environments. I would add a relation for the user model and check the rol name:
User model:
public function role()
{
return $this->belongsTo('App\Role', 'Rolid');
}
Employee middlaware
class Employee
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(Auth::user()->role->name == 'employee')
{
return $next($request);
}
else
{
return redirect('/home');
}
}
}
Office manger middleware:
class Officemanager
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(Auth::user()->role->name == 'officemanager')
{
return $next($request);
}
else
{
return redirect('/home');
}
}
}

Pass parameter to Laravel Middleware

How can I passed a parameter in my middleware? I'm always getting this error
Here are the structure of my middlware
<?php
namespace App\Http\Middleware;
use Closure;
class SubDomainAccess
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $subdomain)
{
dd($subdomain); // Just trying to output the result here
return $next($request);
}
}
And on the Kernel.php under the $routeMiddleware I added this
'subdomain.access' => \App\Http\Middleware\SubDomainAccess::class,
Now on my web.php route file I added this
Route::group(['domain' => '{subdomain}.' . config('site.domain')], function () {
Route::get('/', ['as' => 'site.home', 'uses' => 'Site\Listing\ListingController#showListing'])->middleware('subdomain.access');
});
Also I tried this
Route::group(['domain' => '{subdomain}.' . config('site.domain')], function () {
Route::group(['middleware' => 'subdomain.access'], function () {
Route::get('/', ['as' => 'site.home', 'uses' => 'Site\Listing\ListingController#showListing']);
});
});
I tried this but nothings working. The only thing I haven't tried is placing the middleware in my controller constructor. But I don't wan't it that way as I think this is messy and it's more elegant if its within the route file.
Hope you can help me on this. Thanks
Ok so I managed to find a way to get the parameters without passing a third parameter on the middleware handle function thanks to this link
So what I did to retrieve the subdomain parameter is this
$request->route()->parameter('subdomain')
or if all parameter
$request->route()->parameters()
['middleware' => 'subdomain.access'] is wrong, try to use ['middleware' => 'subdomain:access'] with a : instead.
https://mattstauffer.co/blog/passing-parameters-to-middleware-in-laravel-5.1
Get URI from $request object and then return domain. No need to pass subdomain as params to middleware.
namespace App\Http\Middleware;
use Closure;
class SubDomainAccess
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $subdomain)
{
$sudomain = $this->getSubDomain($_SERVER['HTTP_HOST']);
return $next($request);
}
/**
* Get Subdomain name
* #param $uri
* #return bool
*/
private function getSubDomain($uri)
{
if(!empty($uri))
{
$host = explode('.', $uri);
if(sizeof($host) > 2)
return $host[0];
}
return false;
}
}

Session in middleware don't working

I'm storing a value in session in my middleware:
but when I refresh or go to new page the sessions is null.
what I do wrong?
class WorkflowContextMiddleware
{
/**
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|mixed
*/
public function handle(Request $request, Closure $next)
{
$types = $request->input('types', []);
foreach ($types as $type => $context) {
$request->session()->put("somekey.contexts.{$type}", $context);
$request->session()->save();
}
return $next($request);
}
}
route:
Route::group([
'prefix' => LaravelLocalisation::setLocale(),
'middleware' => ['web','localise','localeSessionRedirect']
], function () {
Route::get('/', function() {
(new \Illuminate\Support\Debug\Dumper)->dump(\Session::get('somekey'));
});
});
route provider:
class RouteServiceProvider extends ServiceProvider
{
/**
* This namespace is applied to the controller routes in your routes file.
*
* In addition, it is set as the URL generator's root namespace.
*
* #var string
*/
protected $namespace = 'Arcanine\Http\Controllers';
/**
* Define your route model bindings, pattern filters, etc.
*
* #param \Illuminate\Routing\Router $router
* #return void
*/
public function boot(Router $router)
{
//
parent::boot($router);
}
/**
* Define the routes for the application.
*
* #param \Illuminate\Routing\Router $router
* #return void
*/
public function map(Router $router)
{
$router->group(['namespace' => $this->namespace], function ($router) {
require app_path('Http/routes.php');
});
}
}
Kernel.php
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\App\Workflow\Http\Middleware\WorkflowContextMiddleware::class,
],
In order for your session to work, wrap all your routes within:
Route::group(['middleware' => 'web'], function () {
...
});
Remove web middleware from route group if you're using 5.2.27 and higher.
The thing is all routes in web.php are already using web middleware and adding it manually will cause problems with sessions.

Laravel middleware 'except' rule not working

I have a controller with the following in the constructor:
$this->middleware('guest', ['except' =>
[
'logout',
'auth/facebook',
'auth/facebook/callback',
'auth/facebook/unlink'
]
]);
The 'logout' rule (which is there by default) works perfectly but the other 3 rules I have added are ignored. The routes in routes.php look like this:
Route::group(['middleware' => ['web']],function(){
Route::auth();
// Facebook auth
Route::get('/auth/facebook', 'Auth\AuthController#redirectToFacebook')->name('facebook_auth');
Route::get('/auth/facebook/callback', 'Auth\AuthController#handleFacebookCallback')->name('facebook_callback');
Route::get('/auth/facebook/unlink', 'Auth\AuthController#handleFacebookUnlink')->name('facebook_unlink');
}
If I visit auth/facebook, auth/facebook/callback or auth/facebook/unlink whilst logged in I get denied by the middleware and thrown back to the homepage.
I've tried specifying the 'except' rules with proceeding /'s so they match the routes in routes.php exactly but it makes no difference. Any ideas why these rules are being ignored, whilst the default 'logout' rule is respected?
Cheers!
You need to pass the method's name instead of the URI.
<?php
namespace App\Http\Controllers;
class MyController extends Controller {
public function __construct() {
$this->middleware('guest', ['except' => [
'redirectToFacebook', 'handleFacebookCallback', 'handleFacebookUnlink'
]]);
}
}
Since Laravel 5.3, you can use fluent interface to define middlewares on controllers, which seems cleaner than using multidimensional arrays.
<?php
$this->middleware('guest')->except('redirectToFacebook', 'handleFacebookCallback', 'handleFacebookUnlink');
I solved this issue in my Middleware by adding this inExceptArray function. It's the same way VerifyCsrfToken handles the except array.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class MyMiddleware
{
/**
* Routes that should skip handle.
*
* #var array
*/
protected $except = [
'/some/route',
];
/**
* Determine if the request has a URI that should pass through.
*
* #param Request $request
* #return bool
*/
protected function inExceptArray($request)
{
foreach ($this->except as $except) {
if ($except !== '/') {
$except = trim($except, '/');
}
if ($request->is($except)) {
return true;
}
}
return false;
}
/**
* Handle an incoming request.
*
* #param Request $request
* #param Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
// check user authed or API Key
if (!$this->inExceptArray($request)) {
// Process middleware checks and return if failed...
if (true) {
// Middleware failed, send back response
return response()->json([
'error' => true,
'Message' => 'Failed Middleware check'
]);
}
}
// Middleware passed or in Except array
return $next($request);
}
}
If you are trying to follow the Laravel Documentation, an alternative solution to this is suggested by adding routes to the $except variable in the /Http/Middleware/VerifyCsrfToken.php file. The documentation says to add them like this:
'route/*'
But I found the only way to get it to work is by putting the routes to ignore like this:
'/route'
When assigning middleware to a group of routes, you may occasionally need to prevent the middleware from being applied to an individual route within the group. You may accomplish this using the withoutMiddleware method:
use App\Http\Middleware\CheckAge;
Route::middleware([CheckAge::class])->group(function () {
Route::get('/', function () {
//
});
Route::get('admin/profile', function () {
//
})->withoutMiddleware([CheckAge::class]);
});
for more information read documentation laravel middleware
Use this function in your Controller:
public function __construct()
{
$this->middleware(['auth' => 'verified'])->except("page_name_1", "page_name_2", "page_name_3");
}
*replace page_name_1/2/3 with yours.
For me it's working fine.
I have this solved, and here's what I am doing. Aso, I just realized this is very similar to what cmac did in his answer.
api.php
Route::group(['middleware' => 'auth'], function () {
Route::get('/user', 'Auth\UserController#me')->name('me');
Route::post('logout', 'Auth\LoginController#logout')->name('logout');
});
LoginController.php
class LoginController extends Controller
{
use AuthenticatesUsers, ThrottlesLogins;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
// ...
/**
* If the user's session is expired, the auth token is already invalidated,
* so we just return success to the client.
*
* This solves the edge case where the user clicks the Logout button as their first
* interaction in a stale session, and allows a clean redirect to the login page.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function logout(Request $request)
{
$user = $this->guard()->user();
if ($user) {
$this->guard()->logout();
JWTAuth::invalidate();
}
return response()->json(['success' => 'Logged out.'], 200);
}
}
Authenticate.php
class Authenticate extends Middleware
{
/**
* Exclude these routes from authentication check.
*
* Note: `$request->is('api/fragment*')` https://laravel.com/docs/7.x/requests
*
* #var array
*/
protected $except = [
'api/logout',
];
/**
* Ensure the user is authenticated.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
try {
foreach ($this->except as $excluded_route) {
if ($request->path() === $excluded_route) {
\Log::debug("Skipping $excluded_route from auth check...");
return $next($request);
}
}
// code below here requires 'auth'
{ catch ($e) {
// ...
}
}
I over-engineered it slightly. Today I only need an exemption on /api/logout, but I set the logic up to quickly add more routes. If you research the VerifyCsrfToken middleware, you'll see it takes a form like this:
protected $except = [
'api/logout',
'api/foobars*',
'stripe/poop',
'https://www.external.com/yolo',
];
That's why I put that "note" in my doc above there. $request->path() === $excluded_route will probably not match api/foobars*, but $request->is('api/foobars*') should. Additionally, a person might be able to use something like $request->url() === $excluded_route to match http://www.external.com/yolo.
You should pass the function name to 'except'.
Here's an example from one of my projects:
$this->middleware('IsAdminOrSupport', ['except' => [
'ProductsByShopPage'
]
]);
This means the middleware 'IsAdminOrSupport' is applied to all methods of this controller except for the method 'ProductByShopPage'.

Resources