Where to switch to other service depending on logged user field? - laravel

In laravel 9 app I try to switch to different service
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
if ($this->app->environment('local')) {
$this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class);
$this->app->register(TelescopeServiceProvider::class);
$this->app->register(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class);
}
// Error pointing at this line
$loggedUser = Auth::user();
if( $loggedUser->membership_mark === 'G') { // G=>Gold Membership
$this->app->bind('App\Interfaces\PageMembershipMarkStrategyInterface',
'App\Implementations\PageGoldMemberStrategy');
}
}
But I got error :
Target class [hash] does not exist. in...
Looks like register method is not proper place for app->bind when I need to get logged user.
But where have I to make this binding? In custom service provider? Im middleware ?
MODIFIED BLOCK :
I create a middleware with command :
php artisan make:middleware SetPageMembershipMarkService
and in app/Http/Kernel.php I added it :
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
// I added this line :
\App\Http\Middleware\SetPageMembershipMarkService::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
But I got error :
Illuminate\Contracts\Container\BindingResolutionException: Target [App\Interfaces\PageMembershipMarkStrategyInterface] is not instantiable while building [App\Http\Controllers\InformOnPageInStockController]....
I am not sure in which place of Kernel.php I have to add ref to PageMembershipMarkStrategyInterface ?
checking log it appears that my SetPageMembershipMarkService is not triggered before error...
MODIFIED BLOCK 2:
In file app/Http/Kernel.php I added my Middleware reference:
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
// I set it AFTER auth
'setPageMembership' =>\App\Http\Middleware\SetPageMembershipMarkService::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminat`enter code here`e\Auth\Middleware\EnsureEmailIsVerified::class,
];
abd in routes/api.php :
Route::middleware('auth:api')->prefix('v1') /*->name('api.v1')*/ ->group(function () {
Route::get('/user', function (Request $request) {
return $request->user();
});
...
Route::middleware(['setPageMembership'])->group(function () {
Route::resource('/inform-on-page-in-stock', InformOnPageInStockController::class)->except(['edit', 'add']);
});
and I still got error :
Illuminate\Contracts\Container\BindingResolutionException: Target [App\Interfaces\InformOnPageInStockStrategyInterface] is not instantiable while building [App\Http\Controllers\InformOnPageInStockController].
Checking logs I do not find log message I set in SetPageMembershipMarkService middleware, so looks like it does
not set before my controllers...
What is wrong ?
You mentioned using of RouteServiceProvider. Which is correct syntax ?
File app/Http/Middleware/SetPageMembershipMarkService.php has lines :
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Auth;
class SetPageMembershipMarkService
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* #return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
$loggedUser = Auth::user();
\Log::info(varDump($loggedUser, ' -1 SetPageMembershipMarkService $loggedUser::'));
$membershipMark = $loggedUser->membership_mark;
if( $membershipMark === 'S') { // G=>Silver Membership
app()->bind('App\Interfaces\InformOnPageInStockStrategyInterface',
'App\Implementations\HasNoInformOnPageInStockStrategy');
}
if( $membershipMark === 'G') { // G=>Gold Membership
app()->bind('App\Interfaces\InformOnPageInStockStrategyInterface',
'App\Implementations\InformOnPageInStockGoldMembersStrategy');
}
return $next($request);
}
}
But in log file I do not see any log message I have in this Middleware...
Thanks!

You will not be able to get Auth:user() data in Service Provder. Because service provider runs before Laravel sets the Auth data, you'll have to use some hacky methods to handle it.
I think better ways to handle data related to auth would be on middleware and not in Service Provider.
Additional explanations can be found on this questions answer Laravel - How to get current user in AppServiceProvider
EDIT for modifed block
Your middleware registration is wrong.
$routeMiddleware this array is used to register a custom middleware which you gonna use and define manually
For example:
protected $routeMiddleware = [
.....
// I added this line :
'setPageMembership' => \App\Http\Middleware\SetPageMembershipMarkService::class,
Then you gonna use this middleware in your route groups like:
Route::middleware(['auth', 'setPageMembership'])->group(function () {
Route::get('test', [TestController::class, 'test']);
});
If you want the middleware to run for all the routes it is better to add it in the $middleware array or if you want in the certain route group (web/api) then in the $middlewareGroups individual web and api array.
Adding the middleware in RouteServiceProvder also works if you want every route to have the middleware.
However, for your use case, authentication is required before binding. So, you will need auth middleware to fire first before you perform those bind actions. So adding the middleware to your route file like I showed above will be enough for your use case.

Related

How to create verification code session between controllers

I have 2 controller which are Verify Controller and Register Controller. To access the register page, I have added a custom middleware where super admin need to enter a code to access it which means only super admin have access to the registration form.
I have build the verification code form and inside the Verify controller, I have hard coded a code to test if user entered the same code. I also create a session variable on Verify Controller to store if success.
How my session works
If I go to superadmin/register then store session as empty.
If it matches, store session value as success.
Checking session
Finally check the session on middleware using if else condition.
If empty, redirect to verify page.
If not empty, redirect to register page and proceed to register new super admin.
The problem is, I still can directly access superadmin/register. It does not redirecting to verify page.
Verify Controller
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Session;
class SuperAdminVerifyController extends Controller
{
public function showVerifyForm()
{
return view('auth.superadmin-verify');
}
public function verify(Request $request)
{
// Validate the form data
$this->validate($request, [
'verifycode' => 'required'
]);
$code = "123";
$verifycode = $request->verifycode;
if ($code != $verifycode)
{
return redirect()->back()->with('status', 'Invalid Code');
}
else
{
Session::put('session_val', 'success');
return redirect()->route('superadmin.register');
}
}
}
Register Controller
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use Session;
use App\Superadmin;
class SuperAdminRegisterController extends Controller
{
public function __construct()
{
$this->middleware('checksessionvar');
}
public function showRegisterForm()
{
return view('auth.superadmin-register');
}
protected function register(Request $request)
{
$this->validate($request, [
'name' => 'required',
'email' => 'required|email',
'password' => 'required'
]);
if (Superadmin::where('email', '=', $request->email)->exists())
{
return redirect()->route('superadmin.register')->with('status', "Email already exists");
}
else
{
$admin = new Superadmin;
$admin->name = $request->name;
$admin->email = $request->email;
$admin->password = Hash::make($request->password);
$admin->save();
Session::flush();
return redirect()->route('superadmin.register')->with('status', "Successfull added to database. Please go to Super Admin login page.");
}
}
}
Custom Middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Session;
class CheckSessionVariable
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$getSession = Session::get('session_val');
if($getSession == null)
{
return redirect()->route('superadmin.verify');
}
else if($getSession == "success")
{
return $next($request);
}
}
}
Routes
Route::prefix('superadmin')->group(function() {
Route::get('/verify', 'Auth\SuperAdminVerifyController#showVerifyForm')->name('superadmin.verify');
Route::post('/verify', 'Auth\SuperAdminVerifyController#verify')->name('superadmin.verify.submit');
Route::get('/register', 'Auth\SuperAdminRegisterController#showRegisterForm')->name('superadmin.register');
Route::post('/register', 'Auth\SuperAdminRegisterController#register')->name('superadmin.register.submit');
Route::get('/login', 'Auth\SuperAdminLoginController#showLoginForm')->name('superadmin.login');
Route::post('/login', 'Auth\SuperAdminLoginController#login')->name('superadmin.login.submit');
Route::post('/', 'CompanyController#store_company')->name('superadmin.company.submit');
Route::get('/', 'SuperAdminController#index')->name('superadmin.dashboard');
Route::get('/logout', 'Auth\SuperAdminLoginController#logout')->name('superadmin.logout');
});
Kernel.php
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* #var array
*/
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* #var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* #var array
*/
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,
'checksessionvar' => \App\Http\Middleware\CheckSessionVariable::class,
];
}
You could probably cut down on a lot of the steps you're currently doing and use a Gate in the controller. The scenario you describe is similar to the example provided in the documentation:
Gate::before(function ($user, $ability) {
if ($user->isSuperAdmin()) {
return true;
}
});

NotFoundHttpException in Laravel middleware and controller

I am new to laravel and learning basics from https://www.tutorialspoint.com/laravel/laravel_middleware.htm
After installation I have created RoleMiddleware and TestController.
RoleMiddleware.php
<?php
namespace App\Http\Middleware;
use Closure;
class RoleMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $role)
{
echo "Role: ".$role;
return $next($request);
}
}
and TestController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class TestController extends Controller
{
//
public function index(){
echo "<br>Test Controller.";
}
}
and app\http\ routes.php
Route::get('/role',[
'middleware' => 'Role:editor',
'uses' => 'TestController#index',
]);
and in Kernel.php
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware \AuthenticateWithBasicAuth::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'Age' => \App\Http\Middleware\AgeMiddleware::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'Role' => \App\Http\Middleware\RoleMiddleware::class,
'terminate' => \App\Http\Middleware\TerminateMiddleware::class,
];
After written all these code I have executed this using http://localhost:8000/role but it gives NotFoundHttpException, Please help me to solve this issue..
First of all you are using some older version of Laravel, as your routes are listed in app\http\routes.php file because in newer versions, it is actually routes\web.php file:
Then you made a mistake in your route:
Route::get('/role', ['middleware' => 'Role:editor', 'uses' => ]);
http routes must have a callback/closure, whether it is custom function or using a controller... Try with following basic options:
Route::get('/role', function() { return "Working"; });
Also check whether your route is not listed as excepted route
If its working, then play with middlewares!!!
i cant send a comment to ask for hole error ( bcs of my reputation )
but i write middle ware in this way . maybe its help you :
first make sure that $role is string or put it in json_encode ( on RoleMiddleware.php )
==> echo "Role: ".json_encode($role);
then change your rout to below :
Route::get('/role', 'TestController#index')->middleware('Role');
send back to me the result

Laravel 5.4 - Prefixing and applying two middlewares for a multi-tenant application's routes

I'd like to group /account/{account_id} as well as protect every route inside it with the auth middleware and a middleware that will check if the logged user has access to this account. Unfortunately it's not working.
Here is my code
web.php:
Route::group(['middleware' => 'auth'], function () {
Route::group(['prefix' => 'account/{account}', 'middleware' => 'userHasPermissionForAccount'], function() {
Route::group(['prefix' => 'posts'], function () {
Route::get('{post}', 'PostsController#index')->where([
'post' => '\d+'
]);
});
// more routes here...
});
});
app/http/kernel.php
// ...
protected $routeMiddleware = [
// ...
'userHasPermissionForAccount' => \App\Http\Middleware\UserCanAccessContent::class,
];
It don't even triggers the code inside my custom middleware and I don't understand why.
I would use policies (gates), since you can use policy as middleware.
$ php artisan make:policy AcccountPolicy
Register the policy in AuthServiceProvider:
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Account::class => AccountPolicy::class, //Account is model, remember to import!
];
Inside policy file (app/policies/AccountPolicy.php), create method lets say "manage"
/**
* Determine if ....
*
* #param \App\User $current
* #param \App\Account $account
* #return bool
*/
public function manage(User $current, Account $account)
{
//return some logic here to check if $current is part of $account
}
And then use this policy as middleware:
Route::group(['prefix' => 'account/{account}', 'middleware' => 'can:manage,account'], function...

Customising token response Laravel Passport

I am working on an API at the moment and have hit a brick wall. I am using Passport with the 'Password' grant type.
I want to return the user information with the access tokens, however, I am not sure how to.
Which class could I implement, edit or extend to get this?.
I would like this to be returned:
{
"token_type": "Bearer",
"expires_in": 31536000,
"access_token": "lalalalalal",
"refresh_token": "lalalallala",
"user": {
"username": "a username",
"user_type": "admin"
}
}
Thanks in advance.
The instructions on how to do this are hinted in the BearerTokenResponse class (part of the league/oauth2-server package).
Tested on Laravel 5.7.
1. Extend the BearerTokenResponse class, add the extra params you need in the response.
namespace App\Auth;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
class BearerTokenResponse extends \League\OAuth2\Server\ResponseTypes\BearerTokenResponse
{
/**
* Add custom fields to your Bearer Token response here, then override
* AuthorizationServer::getResponseType() to pull in your version of
* this class rather than the default.
*
* #param AccessTokenEntityInterface $accessToken
*
* #return array
*/
protected function getExtraParams(AccessTokenEntityInterface $accessToken): array
{
return [
'user_id' => $this->accessToken->getUserIdentifier(),
];
}
}
2. Create your own PassportServiceProvider class and override the makeAuthorizationServer() method in order to pass in your own BearerTokenResponse class.
namespace App\Providers;
use App\Auth\BearerTokenResponse;
use Laravel\Passport\Bridge;
use League\OAuth2\Server\AuthorizationServer;
class PassportServiceProvider extends \Laravel\Passport\PassportServiceProvider
{
/**
* Make the authorization service instance.
*
* #return \League\OAuth2\Server\AuthorizationServer
*/
public function makeAuthorizationServer()
{
return new AuthorizationServer(
$this->app->make(Bridge\ClientRepository::class),
$this->app->make(Bridge\AccessTokenRepository::class),
$this->app->make(Bridge\ScopeRepository::class),
$this->makeCryptKey('private'),
app('encrypter')->getKey(),
new BearerTokenResponse() // <-- The class you created above
);
}
}
3. Add your provider to the providers array in config/app.php
/*
* Application Service Providers...
*/
App\Providers\PassportServiceProvider::class,
4. Exclude the passport package from laravel auto-discovery in composer.json
This stops the default PassportServiceProvider class from being loaded.
"extra": {
"laravel": {
"dont-discover": [
"laravel/passport"
]
}
},
Then run composer install.
Two steps.
1. Add a new route in your routes file.
// routes/api.php
Route::post('oauth/token', 'AuthController#auth');
Keep in mind this will change the route for getting the token from /oauth/token to /api/oauth/token.
2. Add the controller method.
<?php
// app/Http/Controllers/AuthController.php
namespace App\Http\Controllers;
use App\User;
use Psr\Http\Message\ServerRequestInterface;
use \Laravel\Passport\Http\Controllers\AccessTokenController;
class AuthController extends AccessTokenController
{
public function auth(ServerRequestInterface $request)
{
$tokenResponse = parent::issueToken($request);
$token = $tokenResponse->getContent();
// $tokenInfo will contain the usual Laravel Passort token response.
$tokenInfo = json_decode($token, true);
// Then we just add the user to the response before returning it.
$username = $request->getParsedBody()['username'];
$user = User::whereEmail($username)->first();
$tokenInfo = collect($tokenInfo);
$tokenInfo->put('user', $user);
return $tokenInfo;
}
}
Im using Multi-Auth with passport, so the previous answers didn't help me.
After hours of "googling" I found this answer (after-) middleware.
My middleware basically gets the result of Passport auth, checks if there is an Bearer inside and append more data to the content.
<?php
namespace App\Http\Middleware;
use Closure;
class AppendTokenResponse
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$response = $next($request);
$content = json_decode($response->content(), true);
if (!empty($content['access_token'])) {
$content['moredata'] = 'some data';
$response->setContent($content);
}
return $response;
}
}
Now put the new Middleware in $routemiddleware at App/Http/Kernel.php
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* #var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'cors' => \App\Http\Middleware\Cors::class,
'multiauth' => \SMartins\PassportMultiauth\Http\Middleware\MultiAuthenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'oauth.providers' => \SMartins\PassportMultiauth\Http\Middleware\AddCustomProvider::class,
'append_auth' =>\App\Http\Middleware\AppendTokenResponse::class,
];
Then just register this middleware to Passport Routes in Providers/AuthServiceProvider.php
With Multiauth:
Route::group(['middleware' => ['oauth.providers','append_auth']], function () {
Passport::routes(function ($router) {
return $router->forAccessTokens();
});
});
I believe regular passport should be (not tested):
Route::group(['middleware' => ['append_auth']], function () {
Passport::routes();
});
Another better answer from the web
Custom Laravel Passport BearerTokenResponse
https://gist.github.com/messi89/489473c053e3ea8d9e034b0032effb1d
To add custom claims to your Passport token, here is a gist using Passport 8 with Laravel 6
https://gist.github.com/onamfc/0422da15743918e653888441ba6226ca

Lavavel 5: Error page middlewares are not accessible

I'm using Laravel 5.0 and getting one issue. My site is working fine when there is no error like 404, 500 etc. But when there is error on my site then it render error page but some middlewares are not loading.
My site has two parts frontend and backend. Both parts have diff-diff middlewares which I'm using. I'm using some number of custom middlewares, some of them I'm using on frontend and some of them using on backend. But when there is any error on site then middlewares are not working which are loaded in $routeMiddleware array in Kernel.php file.
I research number of articles but all gives me same solution to load all required middlewares in $middleware, which seems not fine to me. Because in $middleware array all middlewares are loading for both fronend and backend sections. But I need to load spacial middlewares only single section. How could I implement this.
This is my kernel.php file
class Kernel extends HttpKernel {
/**
* The application's global HTTP middleware stack.
*
* #var array
*/
protected $middleware = [
'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode',
'Illuminate\Cookie\Middleware\EncryptCookies',
'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',
'Illuminate\Session\Middleware\StartSession',
'Illuminate\View\Middleware\ShareErrorsFromSession',
// 'App\Http\Middleware\VerifyCsrfToken',
];
/**
* The application's route middleware.
*
* #var array
*/
protected $routeMiddleware = [
'auth' => 'App\Http\Middleware\Authenticate',
'auth.frontend' => 'App\Http\Middleware\FrontendAuthenticate',
'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth',
];
}
And this is my route.php file code
Route::group(['middleware' => 'auth.frontend'], function() {
Route::get('/home', ['as' => 'home', 'uses' => 'HomeController#index']);
Route::get('/inner_page', ['as' => 'inner_page', 'uses' => 'HomeController#inner_page']);
});
How this is possible, please help me on this issue.
As explained in documentation, you must define a terminate($request, $response) method to run the middle-ware after the response is ready to be sent to the browser. The terminate($request, $response) method should receive both the request and the response.
then you can either add your middle-ware to the global or route middle-ware arrays.
example :
<?php
namespace App\Http\Middleware;
use Closure;
class FrontendAuthenticate
{
public function handle($request, Closure $next)
{
return $next($request);
}
public function terminate($request, $response)
{
// Check if the $response is an error and then do stuff
}
}

Resources