everybody.
I made a middleware. I made a global one putting it into Kernel.php, which is called to every request and which verify if user is loged in.
The point was I needed to use the session for that. But the session was empty. Probably because it is populated later in script.
So I fix it using a global middleware called '\Illuminate\Session\Middleware\StartSession::class'. It was great because after that I could see, in my middleware, what session contains.
But another bug showed up. Since I puted the '\Illuminate\Session\Middleware\StartSession::class' middleware, my redirects doesn't put on session the variable anymore.
Till now code below worked perfectly and redirect created the arsh variable on session:
return redirect('/admin')->with('arsh', $arsh);
But now doesn't put anymore the arsh variable on session on redirect.
I researched a lot on internet, but nothing. I saw a lot of advices but no one worked.
I just don't know what I can do. If you know something would be awesome.
You also can think about using another solving method for reading session in my middleware, and probably this won't need anymore thinking about last bug.
I hope you understood what I wrote and sorry for my english.
Edit:
I did what Hamoud said:
I moved my middleware (\App\Http\Middleware\RedirectIfNotAuthenticated::class,) from $middleware to $middlewareGroups:
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrimStrings::class,
];
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,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\RedirectIfNotAuthenticated::class, // my middleware
],
'api' => [
'throttle:60,1',
'bindings',
],
];
And now redirect works well and can sets variables on session. But my middleware doesn't work anymore:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use Session;
use Request;
class RedirectIfNotAuthenticated
{
/**
* handle
*/
public function handle($request, Closure $next, $guard = null)
{
if ($request->is('admin/*') && !$request->is('admin') && !$request->is('admin/login') && (!Session::has(_DB_PREFIX_.'id_admin') || !Session::has(_DB_PREFIX_.'name_admin'))) {
return redirect('/admin');
}
return $next($request);
}
}
If statement is never true...
That because $middleware is called before verifying the route, while $middlewareGroups is called after. So I am redirect to 404 when my url is something like domain.com/admin/fgtsdr.
What my middleware does is if route is admin/* and I am not loged in, it redirects me to /admin. The point is it has to do that even if route exists or not...
You did not post your Kernel.php file, and it's not clear what do mean by global middleware. Do you add it the $middleware array or $middlewareGroups array?
The order of these middleware matters. In the Kernel.php file there are three arrays in which you can register your middleware.
$middleware. To be triggered with every request.
$middlewareGroups. You register group of middleware to assign to specific route. It has two main groups: web and api. The web group is assigned to all routes registered in the routes/web.php file.
$routeMiddleware. Individual middleware to assign to specific route.
I assume you started by putting your custom middleware in the $middleware to make it global, and when this did not work you added Laravel's StartSession middleware to the same array. However, StartSession is already registered in the $middlewareGroups array within the web group. So, you have two sessions one of them is destroying the other.
The correct way to add a middleware to all routes, when they need a session (web routes), is to add it in the web group if the $middlewareGroups after the StartSession middleware.
For example,
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class, <---- Laravel session middleware
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\CustomMiddleware::class, <--- Your global middleware.
],
Related
I am using a middleware to check for the existence of a login token from another site. If the login token is present and the user is not already logged in, I would like to use the token to log the user in and send them to their intended page. If they are already logged in, I would like it to do nothing.
As suggested, should this be a ServiceProvider instead?
Here is my middleware:
<?php
namespace App\Http\Middleware;
use Session;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Auth\LoginController;
class CheckRepLoginToken
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next){
$loginToken = $request->repLoginToken;
$goto = '/'.$request->path();
if(isset($loginToken) && Auth::guest()){
(new LoginController)->login($loginToken,$goto);
}
return $next($request);
}
}
The problem is that I need this to all run prior to the $middlewareGroups and $routeMiddleware so the user IS NOT sent to the login screen if Auth::guest() is true but the token is present.
I currently have the middleware in the protected $middleware section of the Kernel and everyone seems to be a "guest" whether or not they are logged in.
This is the kernel file:
<?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 = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\CheckRepLoginToken::class,
// 'checkStatus' => \App\Http\Middleware\CheckStatus::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' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* 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,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::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' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
}
How can I achieve my desired result without messing with the current authentication?
At first, middleware seems like the right tool for a one-time login token feature, but the implementation can be tricky without an understanding of Laravel's request pipeline. At the end of this answer, we'll look at a simple alternative that uses custom authentication functionality instead.
For traditional web requests from a browser, Laravel's authentication services depend on the existence of a session identified by a cookie. The question's CheckRepLoginToken middleware is declared in the global middleware array in Kernel.php, and these handlers execute before route middleware which include the StartSession middleware in the 'web' group.
Since the StartSession middleware initializes session state for a request, authentication context for a global CheckRepLoginToken middleware is not available yet. Calling Auth::guest() from the global middleware will always return true for the configuration shown in the question. I'm not sure what the LoginController::login() method does in your particular project, but I imagine that the auth state set-up by attempting to invoke that method from global middleware may disappear when the standard session and auth middleware run afterward.
Depending on what your LoginController::login() method does, it may be enough to move the declaration for the CheckRepLoginToken middleware below StartSession in the 'web' group. As an aside, some might consider it bad practice to instantiate the controller to call the method directly. We can achieve a similar outcome without much code:
public function handle(Request $request, Closure $next)
{
if ($request->has('repLoginToken') && Auth::guest()) {
$user = // ...try to fetch a user with $request->repLoginToken...
if ($user !== null) {
Auth::login($user);
}
}
return $next($request);
}
A more complete solution takes advantage of Laravel's pluggable authentication system. We can wrap Laravel's standard authentication guard with a custom implementation that handles the token.
First, we'll update config/auth.php to switch the default 'web' guard to use a custom driver that we'll implement below. We rename the original 'web' guard to 'session' so that we can refer to it later.
'guards' => [
'web' => [
'driver' => 'rep-token',
],
'session' => [
'driver' => 'session',
'provider' => 'users',
]
],
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 bind our custom guard in the boot() method in AuthServiceProvider.php:
public function boot()
{
Auth::viaRequest('rep-token', function ($request) {
$baseGuard = Auth::guard('session');
if ($request->has('repLoginToken') && $baseGuard->guest()) {
$user = // ...try to fetch a user with $request->repLoginToken...
if ($user !== null) {
$baseGuard->login($user);
}
}
return $baseGuard->user();
});
}
As we can see, this wrapper reuses the functionality of Laravel's standard session-based authentication and handles the special case for the presence of repLoginToken. It does not need any additional middleware.
Since this post is a public, I feel obligated to emphasize a point from Mtxz's answer. Exercise great caution in the design and implementation of a third-party authentication scheme. In general, anyone who obtains a valid token—including the third-party—has complete access to a user's account. The simplicity of the authentication flow described in this question suggests vulnerabilities that may not be acceptable for many applications.
try to name your route and do like this here:
Route::get('/login/{loginToken}', 'LoginController#login')->name('login.route');
if (isset($loginToken) && Auth::guest()) {
return redirect()->route('login.route', [
'token' => $loginToken
])
}
Actually, for Laravel to know if the user is logged in or not, the request needs to passe the auth middleware. So your custom middleware would need to be triggered after the auth one.
So if your middleware need to know if the user is logged in or not, the auth middleware have to be passed first (also meaning it'll only work for routes or route groups under the auth middleware - as I see the auth middleware is not in your default app middleware stack).
And if the auth middleware redirects the user, then yours is never called.
override the Auth middleware
as your business is "auth related", you could easily override the authenticate middleware you are using. Copy or extend the vendor class, replace the auth middleware class with yours in the Kernel, and add your custom business after the default middleware business identified or not the user as logged in.
I guess you could also go by creating a custom authentication Guard and use it instead of the default one, but I think the custom auth middleware is faster.
I don't know much about your custom auth business, but be careful about tokens in URL (that can be sent by mail, or saved elsewhere) that authenticate a user: those tokens should expire after a delay and after use. Also, you should prevent brute-forcing of this parameter.
I am using laravel 5.2 and I have problems with working with larvel sessions.
I am trying to get session id using Session::getId() in laravel but every time I refresh the page I get a diffrent value. I commented the EncryptCookies::class middleware it worked fine.
In config\session.php I change the domain to my site domain ddev.dev and the path is set to '\'.
[EDIT]
In SessionHandler::class middleware I echo the session id.
here is my app\Http\kernel.php:
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Dideo\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Dideo\Http\Middleware\VerifyCsrfToken::class,
\Dideo\Http\Middleware\GlobalConfig::class,
\Dideo\Http\Middleware\SessionHandler::class,
];
I would like to build a call API laravel project, separate the front-end and back-end, but how?
All the API are writing in the routes/api.php, but the job that return a view still live in routes/web.php ,
is that normal ? if not, what should I do ?
The code like ...
routes/web.php:
Route::get('/book/{id}',function(){ return view('book.show')->with('id',$id) ;});
show.blade.php:
...
$(function(){
$.ajax{
url: 'api/book/{{ $id }}',
....
}
});
...
routes/api.php:
//return the book data that id = {id}
Route::get('/book/{id}','BookController#show');
You are using it correctly.
One of the benefits of separating web and api routes is you have more granular control over applying middlewares.
Take a look at app/Kernel.php file:
/**
* 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\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
As #angad-dubey mentioned its good practise to separate them out so that the web deals with the front end and the api routes for ... Api calls.
I tend to separate mine further by adding an admin.php within the routes folder, so that my backend routes are also separated, Thus you don't have one file with a long list of routes to search through, and this way as shown above you can append different middleware to them and not to the others, which could cause issues in the long term
This is driving me crazy for weeks now:
How can I make sessions available on the 404 page? I just
embed the 404 error page in my default template. It also shows the navbar
and the footer but how can I keep my user logged in when on a 404?
On a 404 Auth::check() always returns false and every else whats session specific is null or empty.
How to enable sessions on (404) error pages?
What you can do is, inside the app/http/Kernel.php add the follwing block of code:
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
Inside the $middleware variable. So it would look like:
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
];
It worked for me, I hope it works for you as well.
So I know this is an old question but in case anyone finds it useful here's how I've dealt with this:
In my App/Exceptions/Handler.php I changed the default render method to:
public function render($request, Exception $exception) {
\Route::any(request()->path(), function () use ($exception, $request) {
return parent::render($request, $exception);
})->middleware('web');
return app()->make(Kernel::class)->handle($request);
}
This achieves 2 requirements:
All the web middleware run correctly for the request
Valid API routes do not start the session
This is in practice a decision to make the web middleware group the default to run when there's an error. Laravel would normally not run any middleware group on error pages. You can of course specify additional middleware or other route parameters if you want or use other conditions like e.g. if the path starts with api/ then use the api middleware instead making it more consistent.
Hopefully this is helpful to someone.
For Laravel 5.5.5 or above just use the Route::fallback:
Route::fallback('PagesController#notFound');
this will help you to customize your 404 views having access to all the session, and more, stuff.
Just to expand a bit on the existing answer: make sure to remove that middleware from $middlewareGroups if it's also there, so you don't apply the middleware twice.
You'll end up with something like this:
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,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
];
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',
],
];
As far as my understanding goes this was caused because the middleware that deals with the session, by being at the web group was being applied only to those pages routed on web.php. And since the default error handling does not redirect to a routed page we had no access to the session.
This way the middleware will be applied to all pages, and not only to those routed on web.php, including the error ones.
I originally found the soution here, but took me a while to understand why this was happening (thought i cold have gotten it all wrong, feel free to confirm or correct this please).
Hope it helps, it's working for me on Laravel 5.4
To avoid problems when you have other exception or recive request with api middleware,change the render function In App/Exceptions/Handler.php to :
public function render ($request, Exception $exception)
{
if ($this->isHttpException($exception)) {
switch ($exception->getStatusCode()) {
case '404':
\Route::any(request()->path(), function () use ($exception, $request) {
return parent::render($request, $exception);
})->middleware('web');
return app()->make(Kernel::class)->handle($request);
break;
default:
return $this->renderHttpException($exception);
break;
}
} else {
return parent::render($request, $exception);
}
}
I don't really see how any of these answers are great. They are over complicated and tend to suggest to rebuild the kernel with an additional route, causing a spike in memory and cpu usage just to make a 404 page.
I suggest just creating the route you need, and not using the automatic error pages that the Laravel pages suggest. In newer versions of Laravel this has been improved, but for this version I just recommend the following:
routes.php
Route::group(['middleware' => ['web']], function ($router) {
$router->get('/404', [
'uses' => 'HttpErrorController#404',
'as' => 'errors.404',
]);
});
Obviously depending on how you are set up, this is how I'd use the routes and create a HttpErrorController to handle the route, this why it's already in the web middleware group
App\Exceptions\Handler.php
public function render($request, Exception $exception)
{
if ($this->isHttpException($exception) && $exception->getStatusCode() === 404)
{
return redirect()->route('404');
}
return parent::render($request, $exception);
}
You can handle these any way you see fit, add a switch in there, check if it's a json request and then handle that differently etc
I've a payment system, where data is submitted to 3rd party site and than hauled back...
When data returns it hits specific url lets say /ok route. $_REQUEST['transaction'].
But because of laravel middleware I'm getting token mismatch. There is no way 3rd party payment API can generate token, so how I disable it? only for this route?
or is there a better option?
Route::get('/payment/ok', 'TransactionsController#Ok');
Route::get('/payment/fail', 'TransactionsController#Fail');
public function Ok( Request $request )
{
$transId = $request->get('trans_id');
if ( isset( $transId ) )
{
return $transId;
}
}
Since version 5.1 Laravel's VerifyCsrfToken middleware allows to specify routes, that are excluded from CSRF validation. In order to achieve that, you need to add the routes to $except array in your App\Http\Middleware\VerifyCsrfToken.php class:
<?php namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
protected $except = [
'payment/*',
];
}
See the docs for more information.
Since Laravel 7.7 you can use method withoutMiddleware eg:
Route::get('/payment/ok', 'TransactionsController#Ok')
->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);
Route::get('/payment/fail', 'TransactionsController#Fail')
->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);
The technique described by #jedrzej.kurylo works well for excluding one or two pages.
Here's a different technique if you need to exclude lots of pages from CSRF validation, with more future-proofing.
You can segment your routes, and apply different middleware to each. So you can put your payment routes into a separate route groups, and not apply VerifyCsrfToken to them. Here's how.
1. Create a routes file
You'll notice in your routes directory, you have the following tree:
routes/
routes/api.php
routes/web.php
Create a new file here, routes/payment.php, and add your routes above to it:
<?php
use Illuminate\Support\Facades\Route;
Route::get('/payment/ok', 'TransactionsController#Ok');
Route::get('/payment/fail', 'TransactionsController#Fail');
2. Process the route with the RouteServiceProvider
In Laravel, Routes are processed by app\Providers\RouteServiceProvider.php. You'll notice these functions: map() and mapWebRoutes(). Add to this file accordingly (I've excluded the stock comments for brevity).
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
$this->mapPaymentRoutes(); // <---- add this line
}
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
protected function mapPaymentRoutes() // <--- Add this method
{
Route::middleware('payment') // <--- this line is important
->namespace($this->namespace)
->group(base_path('routes/payment.php'));
}
Notice we've added a new middleware layer. This is important for the next step.
3. Add a new middleware layer
Your middleware for your route groups are defined in App\Http\Kernel.php.
Update the $middlewareGroups property, and add a middle entry for 'payment'. It can be exactly the same as web, but without the VerifyCsrfToken line.
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,
\App\Http\Middleware\NoClickjack::class,
\App\Http\Middleware\SecureReferrerPolicy::class,
\App\Http\Middleware\NoXssScripting::class,
],
// ********** Add this *******************
'payment' => [
\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,
// This is the line you want to comment-out / remove
// \App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\NoClickjack::class,
\App\Http\Middleware\SecureReferrerPolicy::class,
\App\Http\Middleware\NoXssScripting::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
🎉
Now whenever you add new routes that need to be excluded from the CSRF Token check, add them to the routes/payment.php file.