I have a problem when binding Class in service container. this is my route.
Route::group(['middleware' => 'partner', 'domain' => '{partner}.example.com'], function() {
$firebaseClient = app('App\Services\FirebaseClient');
$firebaseClient->pushData('users.json', ['name' => 'Risal Ganteng']);
});
this is my custom service provider for binding FirebaseClient.
class FirebaseClientServiceProvider extends ServiceProvider
{
/**
* Register FirebaseClient class with the Laravel IoC container.
*
* #return void
*/
public function register()
{
$this->app->bind(\App\Services\FirebaseClient::class, function($app)
{
$config = $app['config'];
$options = [
'api_version' => $config['services.firebase.api_version']
];
return new \App\Services\FirebaseClient($config['services.firebase.database_url'], $options);
});
}
}
and this is my custom route middleware to overide config services.firebase based on subdomain.
class PartnerMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$partner_id = $request->route('partner');
$partner = Partner::findOrFail($partner_id);
// set config
config([
'services.firebase.api_key' => $partner->firebase_api_key,
'services.firebase.auth_domain' => $partner->firebase_auth_domain,
'services.firebase.database_url' => $partner->firebase_database_url,
]);
return $next($request);
}
}
The problem is, when I try to get config in FirebaseServiceProvider the config services.firebase.* value is null because ServiceProvider registered before Route Middleware.
and how to resolve this?
Thank you.
You should not register your service provider in config/app.php file. Register FirebaseClientServiceProvider provider in PartnerMiddleware middleware dynamically.
class PartnerMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$partner_id = $request->route('partner');
$partner = Partner::findOrFail($partner_id);
// set config
config([
'services.firebase.api_key' => $partner->firebase_api_key,
'services.firebase.auth_domain' => $partner->firebase_auth_domain,
'services.firebase.database_url' => $partner->firebase_database_url,
]);
app()->register(FirebaseClientServiceProvider::class);
return $next($request);
}
}
Well, middleware executes much, much later than your service provider so overriding config has no effect. You have to bind your service after you change the config.
Why don't you move the binding to middleware? Just copy paste the whole block from register() and put it after config() in your middleware.
Alternatively, instead of resolving through service container, you could attach Firebase client to Request object in the middleware, after config().
Related
Is this feature included in the SPA Authentication? By reading the docs on Laravel Sanctum, it doesn't look like it, but at the same time, this is a common feature (used if user reset password or if you want only one login instance from a user) so I thought that it must be included...
After searching, I found this:
use Illuminate\Support\Facades\Auth;
Auth::logoutOtherDevices($currentPassword);
but you'll need to uncomment this middleware in the web before you can use this.
'web' => [
// ...
\Illuminate\Session\Middleware\AuthenticateSession::class,
// ...
],
However this only works on web and not on api. So I googled again how to have the same functionality with api and I found this youttube video. Here's what the guy did:
Create his own middleware (ex. AuthenticateSessionSPA.php).
Copy everything in AuthenticateSession and paste it in the new middleware. Change namespace and class name.
Create private variable with value of 'sanctum' (ex. private $driver
= 'sanctum')
Replace all $this->auth->getDefaultDriver() with $this->driver. So
the code will use the sanctum driver instead.
The file looks like this:
<?php
namespace App\Http\Middleware\Custom;
use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as AuthFactory;
class AuthenticateSessionSPA
{
private $driver = 'sanctum';
/**
* The authentication factory implementation.
*
* #var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* Create a new middleware instance.
*
* #param \Illuminate\Contracts\Auth\Factory $auth
* #return void
*/
public function __construct(AuthFactory $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 (! $request->hasSession() || ! $request->user()) {
return $next($request);
}
if ($this->guard()->viaRemember()) {
$passwordHash = explode('|', $request->cookies->get($this->auth->getRecallerName()))[2] ?? null;
if (! $passwordHash || $passwordHash != $request->user()->getAuthPassword()) {
$this->logout($request);
}
}
if (! $request->session()->has('password_hash_'.$this->driver)) {
$this->storePasswordHashInSession($request);
}
if ($request->session()->get('password_hash_'.$this->driver) !== $request->user()->getAuthPassword()) {
$this->logout($request);
}
return tap($next($request), function () use ($request) {
if (! is_null($this->guard()->user())) {
$this->storePasswordHashInSession($request);
}
});
}
/**
* Store the user's current password hash in the session.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
protected function storePasswordHashInSession($request)
{
if (! $request->user()) {
return;
}
$request->session()->put([
'password_hash_'.$this->driver => $request->user()->getAuthPassword(),
]);
}
/**
* Log the user out of the application.
*
* #param \Illuminate\Http\Request $request
* #return void
*
* #throws \Illuminate\Auth\AuthenticationException
*/
protected function logout($request)
{
$this->guard()->logoutCurrentDevice();
$request->session()->flush();
throw new AuthenticationException('Unauthenticated.', [$this->driver]);
}
/**
* Get the guard instance that should be used by the middleware.
*
* #return \Illuminate\Contracts\Auth\Factory|\Illuminate\Contracts\Auth\Guard
*/
protected function guard()
{
return $this->auth;
}
}
And this works wonderfully, I can now use Auth::logoutOtherDevices($currentPassword) in my api.
So my question is if this is safe? I'm just a jr. dev (6 months) and I'm not that confident in this solution since I found it from someone on the net and not from the docs. I'm wondering maybe the laravel devs didn't implement it because they have reasons like security?
What are your thoughts on this method? If you don't agree with this, how would you implement logging out from other devices in a project that has a SPA frontend and Laravel api?
Doesn't look like this is included in Laravel Sanctum SPA Authentication. So I just did my original answer found on the youtube video
I did a custom middleware to handle the auth api token and I call this middleware in the controller, but it's not working I added dd('') inside the middleware to see if it displays anything and it did not worked.
My middleware is:
<?php
namespace App\Http\Middleware;
use Closure;
use App\ApiUser;
class ApiAuth
{
/**
* Run the request filter.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $var)
{
dd('If I put this dd it does not display anything');
$api_user_count = ApiAuth::where('api_token', $var)->count();
if($api_user_count == 0)
{
abort(403, "Auth failed")
}
return $next($request)
}
}
My controller is, how you can see I am sending a parameter to the middleware:
/**
* Remove the specified resource from storage.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$this->middleware('apiauth:'.$request->api_token);
$transaction = new Transaction;
$transaction->folio = $request->folio;
$transaction->dte_code = $request->dte_code;
$transaction->cashier = $request->cashier;
$transaction->amount = $request->amount;
if($transaction->save())
{
return response()->json('Ok', 201);
}
else
{
return response()->json('Error', 400);
}
}
I put the middleware in the path App\Http\Middleware\ApiAuth.php
I put the middleware in the kernel.php like this:
/**
* 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,
'apiauth' => \App\Http\Middleware\ApiAuth::class, // THIS IS THE MINE
];
The weird thing is that it does not display any error, it's like it does not exist so I wonder what it's wrong with this?
Thanks!
instead of calling the middleware manually from your controller method you can register the middleware to apply only for that one method
public function __construct()
{
$this->middleware('apiauth')->only(['store']);
}
then you can extract the api_token from $request
$api_user_count = ApiAuth::where('api_token', $request-> api_token)->get()->count();
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;
}
}
Hi i am doing a website in laravel.
I am trying to do like a function that needs to execute before any controller.
Example :
I have function like
function xyz(){
//do code here
}
This function need to execute when user on site by refreshing page or doing some ajax requst.
I am aware with the codeigniter there is a way to do this using hook
$hook['pre_controller'] = array(
'class' => 'MyClass',
'function' => 'Myfunction',
'filename' => 'Myclass.php',
'filepath' => 'hooks',
'params' => array('beer', 'wine', 'snacks')
);
What is the way in laravel to do this ?
You can use Laravel Middleware to achieve this. The middleware can be registered as global for all controllers / routes, and will let you execute that function (or you can register it for subset of routes by using router groups).
Example:
<?php
namespace App\Http\Middleware;
use Closure;
class MyMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
// call your function
$this->xyz();
return $next($request);
}
public function xyz()
{
// do something
}
}
See the Laravel documentation on Middleware.
For that, you need to declare the function in the public construct part of your controller. For example, if you had a Controller for all pages as follows and you need to check if a user is above a certain age.
class PageController extends Controller
{
public function __construct()
{
Run this function for every function in controller
$this->middleware('age');
}
public function index()
{
//my public static page
}
}
Create a middleware with the artisan command
php artisan make:middleware AgeMiddleware
in your AgeMiddleware, you can have a function as follows.
class AgeMiddleware
{
/**
* Run the request filter.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($request->input('age') <= 200) {
//Do whatever you want, in this case I redirect home
return redirect('home');
}
return $next($request);
}
}
I have problem with my custom middleware. It doesn't work. I have registered it in Kernel.php, only in $routeMiddleware. Here is my code:
/**
* 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,
'test' => \App\Http\Middleware\TestMiddleware::class
];
}
Here is my Controller Code:
/**
* Middleware Activated
*/
public function __constructor()
{
$this->middleware('test');
}
and here is my custom middleware code:
protected $auth;
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (!$this->auth->check())
{
return redirect('/');
}
return $next($request);
}
When I'm logout and type in url
profile/21
it shows me the profile of user with id 21. I want to prevent that with middleware but it won't work for me.
Does anyone have an idea how to do that or where is the mistake?
To make sure if the middleware gets triggered put something like die('middleware triggerd'); inside the handle function of the middleware.
I noticed you have function __constructor() instead of function __construct().
That might be the problem.
If it does trigger the middleware but you still have the same problem try replacing:
if (!$this->auth->check()) with if (!\Auth::check())