I'm displaying a front-end countdown of when the user's session will expire, I want to request the time left without updating it.
Here's what I have so far:
$ttl = Redis::ttl(config('cache.prefix') . ':' . Session::getId());
return response()->json($ttl);
Each time this is requested the ttl is reset back to the session.lifetime value.
I solved this by extending the StartSession middleware:
class StartSession extends \Illuminate\Session\Middleware\StartSession
{
public function terminate($request, $response)
{
if (!$request->is('auth/ping')) {
parent::terminate($request, $response);
}
}
}
Where auth/ping is the route I don't want the session to save on.
I then registered this in the app container as a singleton, so the terminate method resolves to the same instance:
In AppServiceProvider->register:
$this->app->singleton('App\Http\Middleware\StartSession');
The existing mapWebRoutes() method in app/Providers/RouteServiceProvider.php looks like this.
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* #param \Illuminate\Routing\Router $router
* #return void
*/
protected function mapWebRoutes(Router $router)
{
$router->group([
'namespace' => $this->namespace, 'middleware' => 'web',
], function ($router) {
require app_path('Http/routes.php');
});
}
You can simply add something like the following to that method, or you could duplicate the code above which loads routes.php and remove the web middleware.
$router->get('session-ttl', function () {
return response()->json(
\Redis::ttl(config('cache.prefix') . ':' . cookie(config('session.cookie')));
);
});
Or
$router->group([
'namespace' => $this->namespace
], function ($router) {
require app_path('Http/routes_wo_web.php');
});
The session stuff is handled by the Illuminate\Session\Middleware\StartSession middleware, which is in the web middleware group.
The simplest solution would be to place your route outside the standard web middleware group.
If you need any of the other web middlewares, you can add them back to the route.
To actually use the session in your route, you have a couple of options:
you can make a Session middleware that does not terminate the session (extend the existing one, and override the handle method to just return $next($request))
someone here's suggested that as it's a one off route, you could just start the session manually wherever you're handling your route (nick the code from the existing middleware - it's only a few lines).
I think the recommended solution would be to do it in a middleware, though.
Related
I'm wondering what is a better practice using the API with a token or the web router with a prefix:
Have a token that changes every time a user logs in. API:
Route::get('userdata/{key}', 'userController#show');
vs
Route::prefix('api')->group(function () {
Route::get('userdata', 'userController#show');
});
In the web router I can use cookies to verify an user.
Which of these is better?
You don`t need to use any 'key' or something fields in route/body to get data for logged / session user. You can create static route for get 'self-data' and fetch current user data according header (bearer or something other auth) or cookies.
Route::get('userdata', 'userController#show');
with cookies/header will be ok. Also don`t forget to use auth middleware, where you should validate cookies/header.
Group all the api routes and apply your custom middleware that will check for the bearer token in request headers. Here is how to do it:
Create a middleware:
class AuthenticateWithToken
{
public function handle($request, Closure $next)
{
$token = $request->bearerToken();
if ($token==='valid') {
return $next($request);
}
throw new Exception();
}
}
Register your middleware in App\Http\Kernel.php:
class Kernel extends HttpKernel
{
protected $routeMiddleware = [
'authenticateWithToken' => AuthenticateWithToken::class
];
}
Apply middleware on routes:
Route::middleware('authenticateWithToken')
->prefix('api')
->group(function () {
Route::get('/route1', 'YourController#action1');
Route::get('/route2', 'YourController#action2');
});
I've simple middleware which checks if there is a key in user session.
<?php
namespace App\Http\Middleware;
use Closure;
class CustomAuth
{
public function handle($request, Closure $next)
{
if($request->session()->has('uid')){
return $next($request);
}
else{
return view('unauth');
}
}
}
The problem is that I always get "Session store not set on request." error. Here is my route:
Route::get('home', function () {
return view('home');
})->middleware('web', 'CustomAuth');
I've added the middleware in app\Http\Kernel.php in the variable $middleware
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\App\Http\Middleware\CustomAuth::class
];
I also tried changing my route to this:
Route::group(['middleware' => ['web']], function () {
Route::get('home', function () {
return view('home');
})->middleware('CustomAuth');
});
But this didn't work. Any idea how I can make sure that the session had started, or start it before the middleware is called? I'm using Laravel 5.3
The L5 middleware consists of 3 "types".
The configuration is found in the Kernel.php file for HTTP requests (typically App\Http\Kernel. There's global middleware which will run for all requests and is declared in $middleware, there's the route group middleware which will run for all requests for a given route group and is declared in $middlewareGroups, by default all routes declared in web.php are considered to be web routes so all the web middleware apply.
The 3rd type is route middleware. These are declared in the $routeMiddleware array in the form "middlewareName" => Middleware::class and can be used in any route e.g.
Route::get("/route", function () { /* route body */ })->middleware("middlewareName");
These run in order global > group > route middleware and the SessionStart middleware runs as part of the group middleware. Any other middleware that needs access to the session will need to be placed after the SessionStart middleware.
Clarification
It occurs to be when re-reading this that this implies that you need to declare the middleware in the $middeware variable to use them. This is not the case, the following syntax is also allowed:
Route::get("/route", function () {
/* route body */
})->middleware(Middleware::class);
However this syntax will not allow you to provide parameters to the middleware when you use them as you would for example with the authentication middleware when you do auth:api (where api would be a parameter passed to the middleware).
I use Laravel 5.3 and I have the following problem.
[UPDATE]
My initial trouble was the appearance of an error when performing actions on the site when the user was not logged in the system.
This happened when the browser is started, where cached information is displayed by default on the page. Site interface displayed for logged users, and in his system was not. At the same time, producing some action, I get an error that the user is not authorized.
I also have group auth middleware for all my routes. When I reboot page of the site, the middleware is activated and redirectedme to the login page. The main problem is the browser shows the cached information.
So, in addition to middleware for routes I decided to make auth check in controllers.
[/UPDATE]
I want to check user's auth in every controller's action. Making the auth check in every controllers' action manually isn't a solution, because there are many controllers and actions.
So I decided to make it globally.
As all controllers extends Main Controller (App\Http\Controllers\Controller.php), I decided write the
auth()->check() in constructor:
function __construct()
{
if(auth()->check()) dd('success');
}
But... nothing happened((( Then I found the callAction method in BaseController which Main Controller extends and made checking here:
public function callAction($method, $parameters)
{
if(auth()->check()) dd('success');
return call_user_func_array([$this, $method], $parameters);
}
This time everything's OK, but I don't like this solution, because editing the core files isn't good.
Finally, I redeclared callAction method in Main Controller with auth checking, but I don't like this way too.
Is any solution?
You should use middleware:
Route::get('profile', ['middleware' => 'auth', 'uses' => 'UserController#showProfile']);
Or:
Route::get('profile', 'UserController#show')->middleware('auth');
Or using middleware groups:
Route::group(['middleware' => ['auth']], function () {
// Controllers here.
});
Or using controller's construct:
public function __construct()
{
$this->middleware('auth');
}
You can use auth middleware in your controller
public function __construct()
{
$this->middleware('auth');
}
check here : https://laravel.com/docs/5.3/authentication
if there is a group of routes this would be the easiest way
Route::group(['middleware' => ['auth']], function()
{
// here all of the routes that requires auth to be checked like this
Route::resource('user','UsersController');
}
another ways
function __construct()
{
$this->middleware('auth');
}
another way is specified on controller routes
Route::get('profile', [
'middleware' => 'auth',
'uses' => 'UserController#showProfile'
]);
see documentation
https://laravel.com/docs/5.0/controllers#controller-middleware
Per http://www.slimframework.com/docs/concepts/middleware.html, one can add middleware to an application, route, or group.
How should one add middleware to all routes (i.e. to the application), but exclude it from specific routes?
EDIT. As a possible solution, I am thinking of adding some logic in my application middleware to bypass the middleware functionality. Getting the method is easy enough using $request->getMethod(), however, the other URL methods described by http://www.slimframework.com/docs/objects/request.html are not available in the middleware.
I think you need to add middleware to group of route, sample admin route group and user group.
If you would like to change the behaviour of dynamic you have to modify your middleware logic to check your specific request,
sample your route returns JSON if client set type of request.
EDIT:
$app = new \Slim\App();
//pass $app to middleware
$app->add(function ($request, $response, $next) use ($app) {
//do what you want with $app.
//reads config file with routes for exclude
$response = $next($request, $response);
return $response;
});
$app->get('/', function ($request, $response, $args) {
$response->getBody()->write(' Hello ');
return $response;
});
$app->run();
I believe, as of writing this post (I'm using Slim 3.12.1), the answer to your question is it's not possible to remove a previously added middleware from a route.
The solution suggested by the accepted answer makes middleware implementation tightly coupled with route definition. If route (or in similar suggested solution, route name) changes, then middleware code needs to change as well. This is due to the fact that the middleware is technically not removed from route middleware stack. This gets complicated in some situations. For example, if the middleware is written by someone else. You probably want to avoid changing code of a third party library, so it's possible that you end up extending the third party middleware to override its functionality in some way, or some similar solution, yet to make the implementation coupled with your route definitions.
My suggestion is, instead of changing the logic of your middleware to prevent its execution for some routes, add logic to your code to control which middlewares should be added to which routes. Here is a fully functional example to demonstrate how to add a common middleware to all routes while adding some middlewares only to some specific routes but not all (the ones you meant to exclude). To keep things simple, there is no logic for determining which middlewares should be added to which routes, but this demonstrates the general idea. Here are the assumptions:
paths starting with /user should be protected by Authorization middleware
/user/login and /user/signup are exceptions and do not need to be protected by Authentication middleware. Instead, we want to allow login/signup attempts from specific IP addresses only, so we need to protect this route with IP Access Control middleware
all requests to all paths starting with /user should be logged using Logger middleware (no exception here)
other routes don't need any middleware (so we should avoid using $app->add())
<?php
require __DIR__ . '/../vendor/autoload.php';
$app = new Slim\App;
// A simple middleware to write route path and middleware name
class SampleMiddleware
{
public function __construct($name)
{
$this->name = $name;
}
public function __invoke($request, $response, $next)
{
$response->write($request->getUri()->getPath() . ' invokes ' . $this->name . "\n");
return $next($request, $response);
}
}
// Three middleware instances with different names
$auth = new SampleMiddleware('Authorization');
$ipAC = new SampleMiddleware('IP Access Control');
$logger = new SampleMiddleware('Logger');
$group = $app->group('/user', function($app) {
$app->get('/profile', function($request, $response, $args){
return $response;
});
$app->get('/messages', function($request, $response, $args){
return $response;
});
})->add($auth)
->add($logger);
$group = $app->group('/user', function($app) {
$app->get('/login', function($request, $response, $args){
return $response;
});
$app->get('/signup', function($request, $response, $args){
return $response;
});
})->add($ipAC)
->add($logger);
$app->get('{p:.*}', function($request, $response, $args){
$response->write('No middleware for ' . $request->getUri()->getPath());
return $response;
});
$app->run();
Output:
/user/profile
/user/profile invokes Logger
/user/profile invokes Authorization
/user/login
/user/login invokes Logger
/user/login invokes IP Access Control
/something/else
No middleware for /something/else
You can access the uri inside middleware with the following.
$uri = $request->getUri()->getPath();
With that information you can return from the middleware early if the $uri matches path you want to exclude from the middleware.
If you want to add a global middleware the easiest way to achieve this is the following.
$app->group('', function () {
//Add your routes here, note that it is $this->get() ... etc inside the closure
})->add(MyNewMiddlewareClass::class);
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'
]);
});