I want to know how Laravel:
creates CSRF tokens
where it is located
expiration time
When I refresh the web page I see the same token that was already created and how increase or decrease expiration time?
In laravel/vendor/laravel/framework/src/Illuminate/Session/Store.php there is a function called regenerateToken() (github)
/**
* Regenerate the CSRF token value.
*
* #return void
*/
public function regenerateToken()
{
$this->put('_token', Str::random(40));
}
It just uses a 40 character long random string as you can see.
Related
I am implementing some custom auth functionality in to my application, which is built in Laravel v8. I have been looking at Laravel Breeze to see how it is has been implemented there.
These are the relevant functions from Laravel Breeze (https://github.com/laravel/breeze/blob/1.x/stubs/default/App/Http/Controllers/Auth/AuthenticatedSessionController.php):
class AuthenticatedSessionController extends Controller
{
/**
* Handle an incoming authentication request.
*
* #param \App\Http\Requests\Auth\LoginRequest $request
* #return \Illuminate\Http\RedirectResponse
*/
public function store(LoginRequest $request)
{
$request->authenticate();
$request->session()->regenerate();
return redirect()->intended(RouteServiceProvider::HOME);
}
/**
* Destroy an authenticated session.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\RedirectResponse
*/
public function destroy(Request $request)
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}
So you will notice:
In the store() function, which is called during Login, it does $request->session()->regenerate();
In the destroy() function, which is called during Logout, it does $request->session()->invalidate();
In my application custom auth code, I have applied the same implementation in my login and logout actions.
What I have found is, when I logout, it deletes the existing session file inside storage/framework/sessions but then creates another one.
Then when I login, it creates a brand new session file. This essentially means the folder gets full of session files.
Does anyone know the reason why it is implemented this way? I would have thought logout would just delete the session file without creating a new one?
That is the normal behavior of PHP and it is not specific to Laravel.
In PHP by default, all sessions are files that are stored in a tmp directory which if you analyze the session.save_path value in php.ini file, you will see where it is. These files include the serialized session data that you access using $_SESSION keyword.
Laravel utilize basically the original session file store but acts a little bit different. Beside changing the directory they are saved, when you call the regenerate function it creates another session file and deletes the old one. You can see it the implementation Illuminate\Session\Store.php. The migrate function is used to delete the session and return new session id.
public function regenerate($destroy = false)
{
return tap($this->migrate($destroy), function () {
$this->regenerateToken();
});
}
For the invalidate function it deletes the session file actually. If you inspect the implementation, the invalidate calls for migrate method, it calls a destroy method with session id as input and this function simply deletes the session file. But soon after it deletes the file, it needs to create a new session file, why? Because the logged out user needs a new session id so we can track them.
public function invalidate()
{
$this->flush();
return $this->migrate(true);
}
Laravel Session Cleanup
Laravel has a garbage cleanup functionality which runs randomly and deletes the session files that are not valid. What does it mean randomly? Well the cleanup operation is triggered based on a randomness and traffic. So in each request Laravel checks the odd of triggering the clean or not, and the odds are 2 out of 100 by default. This means if applications receives 50 requests, there is a high chance that it will trigger this cleanup.
So if you have a high traffic, there is a high chance that the session directory will be cleared at short intervals, which is quire cool since it always makes sure that the specified directory does not get over populated when visiting users increase.
By the way if you want to act aggressively and delete on a higher chance, you can change the lottery odds in the config\session.php file:
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
I need to get the currently active (not making a new one) token from the token stored in the oauth_access_tokens table generated by Laravel Passport.
I already tried this code I got from GitHub:
See Code
I get the token but I always get 401 Unauthenticated from the token that I got from that code, maybe the code miss some configuration?
You can use
$currentActive Token = Auth::user()->token()
in Laravel\Passport\HasApiTokens you can see this code
/**
* Get all of the access tokens for the user.
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function tokens()
{
return $this->hasMany(Passport::tokenModel(), 'user_id')->orderBy('created_at', 'desc');
}
For get all tokens you can use
Auth::user()->tokens;
We recently got our Laravel 5.6 application penetration tested and one of the issues which were flagged was the expiration not being set correctly on Logout. The AuthenticatesUsers trait calls the invalidate method on the session which basically flushes the session data and regenerates the ID but doesn't set expiration to it.
According to the report, if an attacker can obtain a valid session token, they will be able to hijack the affected user’s account. The user logging off will not invalidate the attacker’s session.
Any pointers here would be of great help.
Thanks
/**
* Log the user out of the application.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function logout(Request $request)
{
$this->guard()->logout();
$request->session()->invalidate();
return redirect('/');
}
Laravel 5.6 added an Auth::logoutOtherDevices() method for this purpose:
https://laravel.com/docs/5.7/authentication#invalidating-sessions-on-other-devices
https://laracasts.com/series/whats-new-in-laravel-5-6/episodes/7
https://github.com/laravel/framework/issues/16311
I am overwriting session.timeout value in one of the middleware (for Laravel web app) but it doesn't seem to be affecting in terms of timing out a session. Though if I debug it shows value I have overwritten.
Config::set('session.lifetime', 1440);
default value is as following:
'lifetime' => 15,
Website that I am working on has very short session lifetime for most of the users but for selected users I want to provide extended session lifetime.
It seems the only way to accomplish a dynamic lifetime value, is by setting the value in middleware, before the session gets initiated. Otherwise its too late, as the application SessionHandler will have already been instantiated using the default config value.
namespace App\Http\Middleware;
class ExtendSession
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, $next)
{
$lifetime = 2;
config(['session.lifetime' => $lifetime]);
return $next($request);
}
}
Then in the kernel.php file, add this class prior to StartSession.
\App\Http\Middleware\ExtendSession::class,
\Illuminate\Session\Middleware\StartSession::class,
Here is what worked for me (using Laravel 5.6 or 5.5) to let a user choose session duration at login time.
Editing the lifetime of the session in Auth controller doesn't work because by then the session is already started. You need to add middleware that executes before Laravel runs its own "StartSession" middleware.
One way is to create a cookie to store the user's lifetime length preference and use that value when setting the session expiration on each request.
New file: app/Http/Middleware/SetSessionLength.php
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Cookie;
class SetSessionLength {
const SESSION_LIFETIME_PARAM = 'sessionLifetime';
const SESSION_LIFETIME_DEFAULT_MINS = 5;
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, $next) {
$lifetimeMins = Cookie::get(self::SESSION_LIFETIME_PARAM, $request->input(self::SESSION_LIFETIME_PARAM)); //https://laravel.com/api/6.x/Illuminate/Support/Facades/Cookie.html#method_get
if ($lifetimeMins) {
Cookie::queue(self::SESSION_LIFETIME_PARAM, $lifetimeMins, $lifetimeMins); //https://laravel.com/docs/6.x/requests#cookies
config(['session.lifetime' => $lifetimeMins]);
}
return $next($request);
}
}
Modify Kernel: app/Http/Kernel.php
Add \App\Http\Middleware\SetSessionLength::class, right before \Illuminate\Session\Middleware\StartSession::class,.
Modify Config: config/session.php
'lifetime' => env('SESSION_LIFETIME', \App\Http\Middleware\SetSessionLength::SESSION_LIFETIME_DEFAULT_MINS),
Modify: resources/views/auth/login.blade.php
To let the user chose their preferred number of minutes, add a dropdown of minutes, such as starting with <select name="{{\App\Http\Middleware\SetSessionLength::SESSION_LIFETIME_PARAM}}">. Otherwise, change SetSessionLength.php above not to pull from $request->input but retrieve from somewhere else, such as a database record for that user.
The problem occurs because the session has already started, and after that you are changing session lifetime configuration variable.
The variable needs to be changed for current request, but the user already has a session with lifetime specified.
You have to change your login method. And do following steps:
See if user exists in database
If yes, and he is user who needs longer session lifetime, run config(['session.lifetime' => 1440]);
log user in
I recommend using helper to change config on the fly.
config(['session.lifetime' => 1440]);
Is it possible to make sessions per Browser tabs?
As example a user opened 2 tabs in his browser:
Tab 1 and Tab 2
In Tab 1 he has a session:
$_SESSION['xxx'] = 'lorem';
And in Tab 2 the session is:
$_SESSION['xxx'] = 'ipsum';
Now on refresh i need to get the current session in the active tab. For example if the user refreshes Tab 2 i need to get the $_SESSION['xxx'] for tab 2 on load which is 'ipsum'. But $_SESSION['xxx'] shouldn't change on Tab 1.
Is there any option to save sessions per tab. If not what are other options to handle this issue?
Thanks for any help!
PHP stores session IDs in cookies and cookies are per client (browser), not tab. So there is no simple and easy way to do this. There are ways of doing this by creating your own session handlers, but they are more hacks than solutions and as such come with their own risks and complexity. For whatever reason you may need this, I am quite sure that there is a better architectural solution than session splitting.
I've been scouring the web for answers to this problem and have not yet found a satisfying solution. I finally pulled together something in JavaScript that sort of works.
//generate a random ID, doesn't really matter how
if(!sessionStorage.tab) {
var max = 99999999;
var min = 10000000;
sessionStorage.tab = Math.floor(Math.random() * (max - min + 1) + min);
}
//set tab_id cookie before leaving page
window.addEventListener('beforeunload', function() {
document.cookie = 'tab_id=' + sessionStorage.tab;
});
HTML5 sessionStorage is not shared between tabs, so we can store a unique tab ID there. Listening for the beforeunload event on the window tells us that we're leaving (and loading some other page). By setting a cookie before we leave, we include our value in the new request without any extra URL manipulation. To distinguish between tabs you just have to check $_COOKIE['tab_id'] on the server and store sessions values appropriately.
Do note that Firefox behaves strangely, in that triggering window.open() will create a window that shares sessionStorage with its parent, giving you two tabs with the same ID. Manually opening a blank tab and then navigating to the target URL will give you separate storage. Chrome works for me in all of my tests so far.
I realize this is probably not the right answer, or even a "good" answer, but it is an answer.
Here's my solution; we're using this to allow multiple app views open per client.
POST to get data:
'doStuff=getData&model=GTD&sn=6789&type=random&date=18-Dec-2018'
the controller then gets the data from the tables, build the object for the device, stores it to the session object and stores it in the variable as such. It generates a per tab guid so that the user can compare the same instrument with a different view in the UI.
$_session[$model][$sn][$type][$guid][$key];
the guid of course is also sent back in the data object so that the tab knows how to recall that data later on.
When the user wants to print the results to a file (pdf, etc) it sends a post with the relevant data in a POST.
'doStuff=saveFile&model=GTD&sn=6789&type=random&calDate=18-Dec-2018&guid=randomKey'
The controller then will pass that to the storage to retrieve.
Session class file example:
<?php
class Session {
public $fieldKey1;
public $fieldKey2;
public function GetStorage($model, $sn, $type, $guid, $key) {
return $_SESSION[$model][$sn][$type][$guid][$key];
}
}
?>
the controller file:
<?php
require_once('session.php');
global $session; //from session class file
switch($_POST['doStuff']) {
case 'saveFile':
$session->GetStorage($_POST['model'], $_POST['sn'], $_POST['type'], $_POST['guid'], $key);
break;
}
?>
This allows the user to have several views of the same data, without overwriting the data-set from each tab. If you don't need as much data granularity per tab, you can of course simplify the number of keys for your $_SESSION variable.
I've been taking a shot to make a web app with this feature.
It have not been matured and have constraints and flows, like having to transmit the session id in the url if your javascript make a post to an external php code depending on it, but it's functional and suits my needs (for now).
I am thinking of a more secure solution, so feel free to adapt it to your needs and give me your suggestions.
<?php
/**
* Split $_SESSION by browser Tab emulator.
* methods exemples are used whith :
* $session = new SessionSplit();
* as SessionSplit may reload the page, it has to be used on top of the code.
*
*/
class SessionSplit{
public $id;
private $gprefix="session_slice_";
private $prefix="";
private $witness="";
function SessionSplit($witness='witness'){
if(session_status()===PHP_SESSION_NONE){
session_start();
}
$this->witness=$witness;
if($this->get_id()){
$this->prefix=$this->gprefix.$this->id;
//set a witness to 'register' the session id
$this->set($this->witness,'true');
}else{
// force the session id in the url to not interfere with form validation
$actual_link = "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
$new_link = $actual_link.(strpos($actual_link,'?')===false?'?':'&').
'session_id='.$this->id;
header('Location: '.$new_link);
}
}
private function get_id(){
if(isset($_GET['session_id'])){
$this->id=$_GET['session_id'];
return true;
}else{
$this->new_id();
return false;
}
}
private function new_id(){
$id=0;
while(isset($_SESSION[$this->gprefix.$id.'.'.$this->witness])){$id++;}
$this->id=$id;
}
// ----------- publics
public function clearAll(){
foreach($_SESSION as $key=>$value){
if(strpos($key,$this->prefix.'.')===0){
unset($_SESSION[$key]);
}
}
}
/**
* $is_user=$session->has('user');
* equivalent to
* $is_user=isset($_SESSION['user']);
* #param {string} $local_id
* #return {boolean}
*/
public function has($local_id){
return isset($_SESSION[$this->prefix.'.'.$local_id]);
}
/**
*
* $session->clear('user');
* equivalent to
* unset($_SESSION['user']);
* #param {string} $local_id
*/
public function clear($local_id){
unset($_SESSION[$this->prefix.'.'.$local_id]);
}
/**
* $user=$session->get('user');
* equivalent to
* $user=$_SESSION['user'];
* #param {string} $local_id
* #return {mixed}
*/
public function get($local_id){
if (isset($_SESSION[$this->prefix.'.'.$local_id])) {
return $_SESSION[$this->prefix.'.'.$local_id];
}else return null;
}
/**
* $session->set('user',$user);
* equivalent to
* $_SESSION['user']=$user;
* #param {string} $local_id
* #param {mixed} $value
*/
public function set($local_id,$value){
$_SESSION[$this->prefix.'.'.$local_id]=$value;
}
};
?>