I'm building a package called under-construction. When this package is activated in a config
file the site will be underconstruction only people with the right code can access the
application.
https://github.com/larsjanssen6/underconstruction
The problem that I have right now:
When the code is entered I make an ajax call that hit's this controller method (called check):
https://github.com/larsjanssen6/underconstruction/blob/master/src/Controllers/CodeController.php
If the code is correct a session variable is being set:
session(['can_visit' => true]);
Then in my vue.js code I redirect to /. And it will hit my middleware again. Here I check if a session called can_visit exists.
return session()->has('can_visit');
https://github.com/larsjanssen6/underconstruction/blob/master/src/UnderConstruction.php
But the session variable can_visit is always gone! How is that possible?
Thanks for your time.
You're not loading the session middleware, so session is not started and no values are persisted.
As was mentioned in the comments, even though your protected routes (/) are within the web middleware (read session), your service provider's routes (/under/construction, /under/check) are not (no write session).
The simple fix is to add the session, or even better, the whole web middleware.
$routeConfig = [
'namespace' => 'LarsJanssen\UnderConstruction\Controllers',
'prefix' => 'under',
'middleware' => [
'web', // add this
// DebugbarEnabled::class, // leaving this dead code behind despite vcs
],
];
However, you might quickly run into trouble with infinite redirect loops if a user adds your middleware to their web middleware group. So I would add a check of some sort to make sure you're not on one of the existing underconstruction routes.
public function handle($request, Closure $next)
{
// check this isn't one of our routes
// too bad router hasn't loaded named routes at this stage in pipeline yet :(
// let's hope it doesn't conflict with user's routes
if ($request->is('under/*')) {
return $next($request);
}
if (! $this->config['enabled']) {
return $next($request);
}
if (!$this->hasAccess($request)) {
return new RedirectResponse('/under/construction');
}
return $next($request);
}
And ultimately guessing from the context of this project, I'd expect most people would want to stick this in the global middleware. However, you're going to run into the same session-hasn't-started-yet issues because that doesn't run in the global middleware. So there's more to chew on. Happy coding!
Related
tl;dr; How can I get session data, then based on authentication, manipulate routes in a laravel ServiceProvider?
The Problem
I need to do two things in a service provider. Register routes and use session data at the same time potentially.
Existing Solutions
Route Without Session Data
I know ServiceProvider::register may happen before RouteProvider::register gets called. ServiceProvider::boot can add routes using something like this...
public function boot(Router $router)
{
$router->prefix('api')->group(function ($router) {
$router->resource('lists', '\App\Extensions\ExtensionName\Http\Controllers\MyController');
});
}
Now this will add the route properly. But by the time that route is accessed, there will be no session data. Therefore Auth::user() will return null. And of course Auth::user() is going to return null in the boot() method as well during the adding of routes.
Session Data Without Route
I could extend the \Illuminate\Session\Middleware\StartSession with my own middleware and fire an event like session.started at the end of an overloaded startSession() method. With this, I should have the session data needed to do something like Auth::user().
public function boot(Router $router)
{
Event::listen('session.started', function () use ($router) {
if (Auth::user()->can('do.something')) {
$router->middleware(['auth'])->prefix('api')->group(function ($router) {
$router->resource('lists', '\App\Extensions\ExtensionName\Http\Controllers\MyController');
});
}
});
}
However, the route will not be added at this point as that stage has already been long over with.
Proper Solution Hints
I've been leaning towards registering my routes, then in my controller, I would somehow inject the session middleware so that it starts the session before my controller code actually runs. But I'm unsure how to do something like that.
It would be very nice if I could have access to the session data before even supplying my routes as it would be cleaner, faster and more secure to include routes that users have access to in the first place instead of checking on every call to them or removing them after they've been added and authorization has been checked.
References
How to retrieve session data in service providers in laravel?
https://laracasts.com/discuss/channels/general-discussion/laravel-5-session-data-is-not-accessible-in-the-app-boot-process
https://github.com/laravel/framework/issues/7906
https://github.com/laravel/framework/pull/7933
https://laravel.io/forum/12-17-2014-session-content-not-available-in-service-providers-register
https://josephsilber.com/posts/2017/01/23/getting-current-user-in-laravel-controller-constructor
I have a brand new installation of Laravel 5, in fact I have tried this on multiple versions and keep hitting the same issue.
I have not changed anything from the default except setting the session driver to redis. (File based also has the same issue).
I have two routes set as follows
Route::get('/set/{value}', function($value) {
var_dump(Session::getId());
Session::set('test', $value);
return view('welcome');
});
Route::get('/get', function() {
return 'Get ' . Session::get('test');
});
If I visit the url /set/abc I see the session appear in REDIS (I also see the file created when using file based). The session looks fine in REDIS as shown below
127.0.0.1:6379> KEYS *
1) "laravel:1a3ae6caff6346e4a173fdc1ab4c6eb0f138806b"
2) "laravel:fed1af2fb44c6e625953237c3fa6fcbb05366a5c"
3) "laravel:cb37286ccfe3e7caa20557aca840f50cb5a5f20d"
Every time I visit the page though, it recreates a new session.
The key parts of session.php file is as follows:
'lifetime' => 120,
'expire_on_close' => false,
I have also checked in REDIS the TTL of the session variables and they do get initialised at 120 minutes (equivalent in seconds).
Any idea what I am doing wrong?
It might be worth noting I am using a homestead vm (completely stock) to test this. I have also tried using multiple browsers. No cookies are ever sent to the browser, I presume a session id should be sent to the browser as part of the initial get request?
Laravel's middleware class \Illuminate\Session\Middleware\StartSession is responsible for starting your session. Before L5.2, this ran on every request because it was part of the global middleware stack. Now, it's optional because L5.2 wants to allow for both a web UI and an API within the same application.
If you open up app/Http/Kernel.php, you'll see that the StartSession middleware is part of a middleware group called web. You need to put all your routes inside there for your example to work.
Route::group(['middleware' => ['web']], function () {
Route::get('/set/{value}', function($value) {
var_dump(Session::getId());
Session::set('test', $value);
return view('welcome');
});
Route::get('/get', function() {
return 'Get ' . Session::get('test');
});
});
You can see that the web middleware group is also responsible for other things like providing the $errors variable on all views.
You can read more about it in the docs:
By default, the routes.php file contains a single route as well as a route group that applies the web middleware group to all routes it contains. This middleware group provides session state and CSRF protection to routes.
Any routes not placed within the web middleware group will not have access to sessions and CSRF protection, so make sure any routes that need these features are placed within the group. Typically, you will place most of your routes within this group:
Source: https://laravel.com/docs/5.2/routing
I have a brand new installation of Laravel 5, in fact I have tried this on multiple versions and keep hitting the same issue.
I have not changed anything from the default except setting the session driver to redis. (File based also has the same issue).
I have two routes set as follows
Route::get('/set/{value}', function($value) {
var_dump(Session::getId());
Session::set('test', $value);
return view('welcome');
});
Route::get('/get', function() {
return 'Get ' . Session::get('test');
});
If I visit the url /set/abc I see the session appear in REDIS (I also see the file created when using file based). The session looks fine in REDIS as shown below
127.0.0.1:6379> KEYS *
1) "laravel:1a3ae6caff6346e4a173fdc1ab4c6eb0f138806b"
2) "laravel:fed1af2fb44c6e625953237c3fa6fcbb05366a5c"
3) "laravel:cb37286ccfe3e7caa20557aca840f50cb5a5f20d"
Every time I visit the page though, it recreates a new session.
The key parts of session.php file is as follows:
'lifetime' => 120,
'expire_on_close' => false,
I have also checked in REDIS the TTL of the session variables and they do get initialised at 120 minutes (equivalent in seconds).
Any idea what I am doing wrong?
It might be worth noting I am using a homestead vm (completely stock) to test this. I have also tried using multiple browsers. No cookies are ever sent to the browser, I presume a session id should be sent to the browser as part of the initial get request?
Laravel's middleware class \Illuminate\Session\Middleware\StartSession is responsible for starting your session. Before L5.2, this ran on every request because it was part of the global middleware stack. Now, it's optional because L5.2 wants to allow for both a web UI and an API within the same application.
If you open up app/Http/Kernel.php, you'll see that the StartSession middleware is part of a middleware group called web. You need to put all your routes inside there for your example to work.
Route::group(['middleware' => ['web']], function () {
Route::get('/set/{value}', function($value) {
var_dump(Session::getId());
Session::set('test', $value);
return view('welcome');
});
Route::get('/get', function() {
return 'Get ' . Session::get('test');
});
});
You can see that the web middleware group is also responsible for other things like providing the $errors variable on all views.
You can read more about it in the docs:
By default, the routes.php file contains a single route as well as a route group that applies the web middleware group to all routes it contains. This middleware group provides session state and CSRF protection to routes.
Any routes not placed within the web middleware group will not have access to sessions and CSRF protection, so make sure any routes that need these features are placed within the group. Typically, you will place most of your routes within this group:
Source: https://laravel.com/docs/5.2/routing
I am developing web application using Laravel 5 and angularJs with RESTFUL apis.
Using middleware to authentication purpose. My problem is after sending few request simultaneously,system automatically logged out and sending 401 exception from laravel side.
API base controller:
class ApiController extends BaseController {
use DispatchesCommands, ValidatesRequests;
function __construct() {
$this->middleware('api.auth');
}
}
Middleware:
class APIMiddleware {
/**
* Handle an incoming request.
*
* #param Request $request
* #param Closure $next
* #return mixed
*/
public function handle($request, Closure $next) {
if (!Auth::check()) {
abort(401, "Unauthorized");
}
return $next($request);
}
}
Log in controller
public function login(LoginRequest $request) {
if (Auth::check()) {
Auth::logout();
}
if (Auth::attempt(['email' => $request->input('email'), 'password' => $request->input('password')], $request->input('is_remember'))) {
return array(true);
} else {
abort(401, "Invalid email & password");
}
}
After few request gone, Server log out and sends 401 exception. I am stuck with this issue.
Now I'm not 100% sure (and depending on your set-up I can't even say I'm 90% sure) But after changing my session_driver from file to database I seem to have fixed this issue - that is if it's the same issue.
I think do the samething as you with my app - that is on a start up of a page, I'm making 6 request (this is development and I will be changing it to one so please don't cry). If I load this page, it works with about 3 or 4 request, then the other 2-3 come back with a unauthorised response. It also only happens on request that require middleware => auth.
So here's my theory to why this is happening: Because, by default, sessions are saved in a file - making multiple requests at once means that file is being opened 6 times at once - probably messing it up (depending on your machine). Therefore changing the session to a database, which is designed to have thousands of requests at once, works!
SOLUTION:
Go to your .env file and change SESSION_DRIVER=file to SESSION_DRIVER=database.
Next you will need to create a session migration: php artisan session:table.
Now composer dump-autoload for good practice.
Finally migrate (php artisan migrate).
NOTE: I'm not 100% sure though if this is the case, but for me this solution worked. I am also aware that this question is really old, but both the developers I work with and myself have had this issue and there doesn't seem to be a solution, so Just though I'd post this.
Managed to figure it out.. Since i use laravel for pretty much all my projects, I forgot to change the session name, as a result, one session was overwriting the other, causing the auto-loggout.. So if you have multiple laravel projects running, make sure they all have different session names. Hope this helps someone in future !
Here is a Laracast thread on this issue.
For me this was the process to solve the problem:
Cleared my browser's cookies for localhost.
Changed value of cookie key in app/session.php.
Ran php artisan config:clear.
It may be a problem that you are accessing the user variable illegally. Please use Auth::check() before accessing Auth::user() This seems to work for my project. Optionally you can try for changing the session driver from .env file.
Might be useful for someone: Had the very same problem. I've changed the cookie name in session settings. By default it is laravel_session, so try setting it to something else
I solved the same issue by clearing cache using php artisan cache:clear and also running composer dump-autoload. Hope this works for you.
I had a similar problem this week. I have a server with multiple Laravel applications. One application was logging the other out.
The problem had to do with session management. The session name was the same for all the applications. Changing it would be enough to avoid different applications conflict. However, I can have different instances of the same application in the server (for testing purposes, for example). So, changing only the session name would not be enough.
To solve my problem properly, I used the session path to make the configuration unique per instance. In the config/session.php, I defined something like this:
'cookie' => 'systemx_session',
'path' => parse_url(env('APP_URL', 'http://localhost'), PHP_URL_PATH),
I use the parse_url function with the environment variable APP_URL because my server has the instances deployed under something like http://example.com/systemx.
I hope this helps someone who might end up having the same kind of problem.
I think you copied an old project for a new application, so you need to change the config/session.php
'cookie' => 'new_session',
I had a similar problem that the users didn't login at all & I found Its because of my authenticatable eloquent model, specified in my auth guard in config/auth.php (User in my case).
I was applying a global scope (in my case verified) so that users were filtered by a specific column & auth guard couldn't find the user so it logged out everytime ...
I solved my problem by this post https://laracasts.com/discuss/channels/laravel/ignore-global-scopes-for-auth
I'm attempting to clean up an existing application by writing unit tests for some legacy code (and updating it along the way). I've rewritten a number of libraries and I've really been loving the TDD approach. However, now it's time to move on to testing some controllers, and I've run into a problem on the very first set of tests. I'm following the explanations in Jeffery Way's Laravel Testing Decoded.
The goal here is to test my login route: http://my-development-server/login. The code should work like this: first, check to see if someone is already logged in - if they are, redirect them to the dashboard (the main page in the app). Otherwise, render the login page. Pretty straight forward.
Here are the routes involved:
Route::get('login', array(
'as' => 'login',
'uses' => 'MyApp\Controllers\AccountController#getLogin',
));
Route::get('/', array(
'as' => 'dashboard',
'uses' => 'MyApp\Controllers\DashboardController#showDashboard',
'before' => 'acl:dashboard.view',
));
Here's the AccountController::getLogin method:
public function getLogin()
{
// Are we logged in?
if (\Sentry::check())
return Redirect::route('dashboard');
// Show the page.
return View::make('account.login');
}
I'm using the Sentry library for user authentication.
And here's my first test:
class AccountControllerTest extends TestCase {
public function tearDown()
{
Mockery::close();
}
public function test_login_alreadyLoggedIn()
{
// arrange
//
\Sentry::shouldReceive("check")
->once()
->andReturn(true);
// act
//
$response = $this->call("GET", "/login");
// assert
//
$this->assertRedirectedToRoute("dashboard");
}
}
This test emulates the "user attempts to log in when they're already logged in" case. check returns true (already logged in) and the user is redirected to the route named dashboard. It works perfectly, test passes.
Next, I add a new test, to test the "user attempts to log in when nobody's logged in" case. Here's the code for that test:
public function test_login_notLoggedIn()
{
// arrange
//
\Sentry::shouldReceive("getUser")
->twice()
->andReturn(null);
\Sentry::shouldReceive("check")
->once()
->andReturn(false);
// act
//
$response = $this->client->request("GET", "/login");
// assert
//
$h2 = $response->filter("h2");
$this->assertEquals("Please sign in", $h2->text());
}
When I run this test, the first test passes but I get a NotFoundHttpException in the second test method.
There are a couple of strange things about this problem:
if I comment out the first test method (test_login_alreadyLoggedIn), the second test passes.
if I reverse the order of the two test methods, test_login_notLoggedIn passes and test_login_alreadyLoggedIn fails (ie. the first method in the class passes and the second fails).
This looks to me like some sort of configuration issue - after the first test (which passes), something is messed up and it cannot make the request in the second test - but I'm at a loss. I've gone over the book several times and google but can't find any reference to any configuration that I'm missing.
Any suggestions?
For anyone who may run into this problem in the future...
This was caused by problems with my routes.php file. In particular, I organized by routes into a set of files:
accounts.php - contains routes related to "account" functions
admin.php - contains routes related to "admin" functions
etc...
These files were then included in my routes.php file with require_once. This works fine for my application, but for some reason, the routes were not loaded properly during testing. The solution is to move my routes back to the routes.php file until I can find a better way to organize them.
You can solve this problem by replacing your require_once() to require()