I have a multiauth laravel 5.2 app, with the fallowing guards defined on config/auth.php:
...
'admin' => [
'driver' => 'session',
'provider' => 'admin',
],
'user' => [
'driver' => 'session',
'provider' => 'user',
],
...
So, admin and user.
The problem resides in the view layer, since this two loggedin guards share some views, ex:
Hello {{Auth::guard('admin')->user()->name}}
In this case the guard is hardcoded into the view to be always admin (it gives error when loggedin guard is user), but, to avoid have to do another equal view just for this little change, I would like have it dinamic, something like:
Hello {{Auth::guard(<LOGGEDIN GUARD>)->user()->name}}
PS: I know that this could be achieved getting the corresponding url segment, ex: www.site.com/pt/user/dasboard which in the case it would be segment 2, but this way the app would lose scalability, since in the future the corresponding segment may not be the same (2 in the example above)
One way to do this is to extend the Laravel authentication class in the IoC container to include, for instance, a name() method that check which guard is used for the current session, and calls user() on that Guard instance.
Another way is to simply use an if-statement in your Blade template:
#if(Auth::guard('admin')->check())
Hello {{Auth::guard('admin')->user()->name}}
#elseif(Auth::guard('user')->check())
Hello {{Auth::guard('user')->user()->name}}
#endif
However, this is a little dirty. You can clean this up a bit by using a partial, or by passing the view a variable containing the guard name, either directly from your Controller, or via a ViewComposer, and then doing:
Hello {{Auth::guard($guardName)->user()->name}}
in your View.
Extending Laravel's authentication is your best option, imo.
This will get the guard name that is used for current logged in user
Auth::getDefaultDriver()
When you log in, by default it will get you the:
'web'
Dependable through which guard you've been logged in it will get you that guard name.
This is not applicable for APIs!!! Because APIs in laravel by default don't use session.
Since Laravel 5.5, this is easy to do with the #auth template directive.
#auth("user")
You're a user!
#endauth
#auth("admin")
You're an administrator!
#endauth
#guest
You're not logged in!
#endguest
Reference: https://laravel.com/docs/5.6/blade#if-statements
In new versions of laravel use:
Auth::getDefaultDriver()
I recommend to use global helper function like
function activeGuard(){
foreach(array_keys(config('auth.guards')) as $guard){
if(auth()->guard($guard)->check()) return $guard;
}
return null;
}
Depends of Harat answer, I built a Class names CustomAuth, and it give me easy access to Auth facade methods: user() and id().
<?php
namespace App\Utils;
use Illuminate\Support\Facades\Auth;
class CustomAuth{
static public function user(){
return Auth::guard(static::activeGuard())->user() ?: null;
}
static public function id(){
return static::user()->MID ?: null;
}
static private function activeGuard(){
foreach(array_keys(config('auth.guards')) as $guard){
if(auth()->guard($guard)->check()) return $guard;
}
return null;
}
}
using auth()->guard($guard)->getName() will return two different type of values
login_admin_59ba36addc2b2f9401580f014c7f58ea4e30989d
if is an admin guard
login_web_59ba36addc2b2f9401580f014c7f58ea4e30989d
if is a web guard or depending on your use case a user guard. So you can test against that.
so a simple use case can be as stated below
if(str_contains(auth()->guard($guard)->getName(), 'admin')){
dd('is admin');
}
here if is an admin, it will show 'is admin' otherwise you get the default
Auth::getDefaultDriver()
Above will return the current guard, in your case (user or admin)
In my case I had up and running project with too often usage of auth()->user() thus I was obligated to find a way to keep using multiple guards across my app.
Use a middleware to handle overwriting the value the default guard.
Add a custom web middleware to your web group within kernel.php
protected $middlewareGroups = [
'web' => [
.....
//This cool guy here
\App\Http\Middleware\CustomWebMiddleware::class,
],
Use CustomWebMiddleware.php content:
<?php
namespace App\Http\Middleware;
use Closure;
class CustomWebMiddleware
{
public function handle($request, Closure $next)
{
$customGuard = 'custom-guard-name-goes-here';
if (auth($customGuard)->check()) {
config()->set('auth.defaults.guard', $customGuard);
}
// if you have multiple guards you may use this foreach to ease your work.
/*$guards = config('auth.guards');
foreach ($guards as $guardName => $guard) {
if ($guard['driver'] == 'session' && auth($guardName)->check()) {
config()->set('auth.defaults.guard', $guardName);
}
}*/
return $next($request);
}
}
Related
I started creating a REST API using the lumen framework and wanted to set up a particular behaviour for my GET /user route. Behaviour is the following:
If the request come from an authenticated user (using auth middleware), the method getAllFields from UserController is called and return all the data from the user
If it's not the case, the method get from UserController is called and return some of the data from the user
It seems logic to me to just write it like that in my web.php using a simple middleware:
<?php
$router->group(['middleware' => 'auth'], function () use ($router) {
$router->get('/user/{id}', [
'uses' => 'UserController#getAllFields'
]);
});
$router->get('/user/{id}', [
'uses' => 'UserController#get'
]);
But for some reason, even if the middleware is correct, I always get the response of the second route declaration (that call get()). I precise that if I remove the second route declaration, the one in the middleware work as expected.
Have someone an idea how I can achieve something similar that work?
Router will check if your request matches to any declared route. Middleware will run AFTER that match, so You cannot just return to router and try to find another match.
To fallow Laravel and Routes pattern - You should have single route that will point to method inside controller. Then inside that You can check if user is logged or not and execute getAllFields() from that controller. It will be not much to rewrite since You are currently using UserController in both routes anyway.
web.php
$router->get('/user/{id}', 'UserController#get');
UserController.php
public function get()
{
return auth()->check() ? YourMethodForLogged() : YourMethodForNotLogged();
}
Or if there is not much logic You can keep this in single method.
Also it is good idea to fallow Laravels REST standards (so use show instead of get, "users" instead of "user" etc - read more https://laravel.com/docs/7.x/controllers)
web.php
$router->get('/users/{user}', 'UserController#show');
UserController.php
public function show(User $user)
{
if (auth()->check()) {
//
} else {
//
}
}
To summary - for your needs use Auth inside controller instead of middleware.
To check if user is logged You can use Facade Auth::check() or helper auth()->check(), or opposite Auth::guest() or auth()->guest().
If you are actually using Lumen instead of full Laravel then there is not auth helper by default (You can make own or use package like lumen-helpers) or just keep it simple and use just Facades instead (if You have then enabled in Lumen).
Read more https://laravel.com/docs/7.x/authentication and https://lumen.laravel.com/docs/7.x/authentication
This pattern is against the idea of Laravel's routing. Each route should be defined once.
You can define your route without auth middleware enabled and then define your logic in the controller.
So, as my title says, I want to change Laravel's default Broadcast auth middleware to a custom auth middleware that I made which uses token-based authentication. I made this because my app is an API-based app, and, when a user authenticates, I create a session token and send it to him and also store it inside the DB with an expires_at column.
I am using Pusher.
I have the following middleware:
class AuthCustom
{
public function handle($request, Closure $next)
{
// if we have the session token stored in header
if ($request->header('x-session')) {
$session = Session::where('id', $request->header('x-session'))->where('expires_on', '>=', date('Y-m-d G:i:s'))->with('user')->first();
if ($session !== null) {
$user = (new User())->where('id', $session->user_id)->first();
if ($user !== null) {
$request->merge(['user' => $user]);
return $next($request);
}
}
}
}
My BroadcastServiceProvider code is as follows:
class BroadcastServiceProvider extends ServiceProvider
{
public function boot()
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}
If I put Broadcast::routes(['middleware' => 'authcustom']); in BroadcastServiceProvider, the boradcasting/auth gives a 403 status code because $request->user() is null, which then results in an Access forbidden.
I have tried searching the whole damn web, and I found nothing about changing the default auth middleware for broadcasting.
I even tried removing Broadcast::routes() and customizing a new route /broadcast which returned a Pusher socket_auth object and everytime I got a 419 Unkown status code.
Any ideas or maybe you can point me in the direction where I could manage this?
Thank you!
Later edit:
My JS Echo connection looks like this:
Vue.use(VueEcho, {
broadcaster: 'pusher',
key: 'xxxxxxxxxxxxxx',
cluster: 'eu',
authEndpoint: 'http://localhost/api.easycargo.ro/public/broadcasting/auth',
auth: {
headers: {
'x-session': this.auth.token
}
}
});
I'm glad you got something working. For later readers, here's a more Laravel-esque way to solve the problem in the question: create a custom auth guard used to authenticate requests for the special routes.
Laravel's AuthManager includes a helper method—viaRequest()—that simplifies the creation of a Guard that authenticates a user with data from the request context without the need to fully-implement Illuminate\Contracts\Auth\Guard. We can bind our custom guard in the boot() method in AuthServiceProvider.php:
public function boot()
{
Auth::viaRequest('custom-auth', function ($request) {
// Any custom user-lookup logic here. For example:
if ($request->header('x-session')) {
$user = // ...try to fetch a user...
return $user;
}
});
}
As we can see, we just pass a closure to the viaRequest() method that returns a User object when authentication succeeds, or null when authentication fails.
Next, we'll tell Laravel about our new auth guard by adding an entry to the 'guards' array in config/auth.php:
'guards' => [
...
'broadcasting' => [
'driver' => 'custom-auth',
],
],
Finally, we need to update the middleware for any routes that should authenticate a user with our custom Guard. We can use Laravel's built-in auth middleware and specify which guard to apply as a middleware parameter. For example, we'll initialize the broadcasting routes in the question's BroadcastServiceProvider.php:
Broadcast::routes([ 'middleware' => [ 'auth:broadcasting', ... ] ]);
...where broadcasting matches the name we assigned to our custom Guard in config/auth.php.
This approach enables us to use all of Laravel's Auth services, provides a more central place to define our authentication logic, and simplifes automated testing because we can more easily mock up authentication as needed.
I actually managed to find a solution, so all I needed to do was bind the $user that I got in my custom auth middleware to the request by doing the following thing:
$request->merge(['user' => $user]);
//add this
$request->setUserResolver(function () use ($user) {
return $user;
});
and now $request->user() which laravel checks returns the user object and passes the validation.
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.
I have an application which uses Laravel 5's out of the box authentication. I need to require authentication on the show method of a controller ONLY when the field called "approved" is equal to 1.
How can I require authentication using middlewares on a conditional basis such that unauthenticated users can access entries whose approved column is equal to 1, but only authenticated users can see entries where approved is equal to 0.
My constructor currently looks like this:
public function __construct(){
$this->middleware('auth', ['only' => ['edit', 'destroy', 'approve', 'markRecovered']]);
}
You may create your own middleware instead of using Laravel's default auth middleware and in that case, you may create a middleware such as 'checkApproval' using something like this:
php artisan make:middleware checkApproval
Then in your app/Http/Middleware directory you'll find the new middleware created and it'll contain the basic structure including handle method, so now you may erite code inside this middleware's handle method to check the user's state and the approved field and then either redirect to login page if condition doesn't match or allow access. here is a basic idea:
use Illuminate\Contracts\Auth\Guard;
class CheckPermission implements Middleware {
protected $auth;
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
public function handle($request, Closure $next)
{
if($this->auth->guest() && !$this->checkApproval($request))
{
return redirect('login');
}
return $next($request);
}
protected function checkApproval($request)
{
// Get the auth/logged in user
// $user = $request->user();
// Get a parameter from route
// $id = $request->route()->parameter('id')
// Check the approved field here and return true or false
}
}
Now assign the middleware a short-hand key in your app/Http/Kernel.php file. By default, the $routeMiddleware property of this class contains entries for the middleware included with Laravel. To add your own, simply append it to this list and assign it a key to use in your route/controller, for example, checkApproval so in the place of auth you may use checkApproval for the the method view in your controller.
This is an abstract idea, but you can implement one of your own now so check the documentation for more information.
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'
]);
});