Laravel: Cookie disappears on refresh: Instead, XSRF-TOKEN is created wrongly - php

What I've been trying is to set a token (i.e. a string) as a cookie, which needs to persist for a certain period.
public function tokenAuth(Request $request)
{
try {
//Lines which create a token from input values
} catch (MyCustomException $e) {
//Return with a warning message
}
$minutes = time() + 60 * 60 * 24 * 30; //time()+60*60*24*30 = 30 days
$https = false;
$httpOnly = true;
return response('Hello World')->cookie("MY_JWT", $token, $minutes, '/', env('APP_URL'), $https, $httpOnly);
}
As long as valid credentials are provided, this tokenAuth is supposed to put a cookie MY_JWT. I can see that MY_JWT is generated properly from Developer Tools.
However, as the title suggests, MY_JWT disappears upon refreshing. According to Laravel's documentation, cookies can be generated in this way.
I also tried setcookie('cookie_id', $id, 0, '/'), referring to this, but I still have no luck.
A funny thing I found is that XSRF-TOKEN is in the cookies, although I haven't put csrf_token in my app.
This is what happens after I refresh the page.
The cookie named blah was created several days ago for an experimentation.
Do you see anything I'm doing wrong?
Any advice will be appreciated!

Related

User authetication with max login attemtps

First off let me know if this is not the correct place for this discussion!
We have decided to move our sessions to the Database layer in our application and handle the users state from there. One of the important issues that we hoped to resolve was user authentication to stop from brute forcing accounts by blocking login attempts from both the session layer (built from a database entry) as well as on the account layer - with an optional toggle to remove the account specific lockout for users as the potential remains for bad actors to then attempt to lock a legitimate user out of their account by spamming the account login - but the tradeoff here is then the account cannot be brute forced. This may be a toggle for specific accounts in the future if we decide the build the app around that idea more.
The current script for sessions includes a function (which i have included below) that you can call on the login event called check_login_attempts_exceeded and will return TRUE if they have been or FALSE if they have not been - Allowing you to either accept the login attempt, or block the attempt before it even hits the login server.
Basically, the user flow would work something like this:
Authenticated user::
We will just return FALSE right away because they are already logged in and don't
need to know this information on their own account. It does allow for them to check
other users with different user ids since they may be an admin and have a need for
that information? This is primarily designed for Guest logins anyways before the
user has been Authenticated in the system.
Spider::
Treat them as a guest account so the spider mechanism isn't exploited by people
to try and bypass the users login limit.
Guest account::
1) The system confirms they are a guest and continues, or returns FALSE if they
are already signed in to an account matching the ID of their user id -> this is
the Authenticated users section above.
2) Check if the user has a login lockout flag set on the SESSION. We don't care
about specific users **yet**, but we will check them afterwards.
NO::
1). Check if the user is at the max attempts in the system:
YES::
1). If the user is at the maximum attempts but doesn't have the flag set
for some reason, we add the login_lockout globally and add it to the
users SESSION in the database.
2). Return TRUE so the system knows they've exceeded max attempts
NO::
1). If the user has a supplied ID greater than 0, they can check if a
specific account is over. Since the flag is not yet set, this is the
only time this can be checked for specific accounts. This will be a
time code on the ACCOUNT level and if it is passed we handle it [1]
way, and if it's not passed we handle it [2] way.
[1]-> The lockout time on the ACCOUNT level is still in the future
so we are going to return TRUE because this account level check
shows the account is locked regardless of this SESSION and we
want to prevent brute forcing of accounts. The downside to this
method is a specific user can be locked out of their accounts
legitimately if a bad actor spams their login name.
[2]-> The lockout on the ACCOUNT level is in the past, so lets set
the login attempts and lockout time on the ACCOUNT level to 0
for both and return FALSE since the timeout is no longer on
and the account hasn't exceeded it's limit! :)
2). The user id wasn't supplied so we assume the user hasn't gone over
yet because the flag isn't set yet by any previous condition and the
lockout time doesn't exist either so we return FALSE :)
2). Return FALSE as a failsafe incase the user id wasn't set and they are
not at the limit meaning they've passed the test and not exceeded attempts
YES::
1). The user has the flag set so lets see if the timeout on the flag has
expired and remove it if it has
YES::
1). Remove the flag, update the session and reset the attempts and the
lockout time to 0 again. Ideally return FALSE now, but we need to
check the ACCOUNT specific timeout as well just in case that one is
still set.
The last portion is the bit I am wanting to be sure of. Basically, if the user HAS a time flag on their SESSION, we need to check if its expired and remove it if it is - but then the ACCOUNT layer inside of this may be time locked separately so we need to check that, remove it if its expired, or return as invalid if its not expired.
I am doing this with the following code (PHP) and have commented as much of the code as possible to explain the steps and where I am at:
// Check if the user has exceeded their login attempts, and if they are locked
// out return TRUE, and if the lockout has expired, remove it from the session
// AND the user account if it is set - Optional return time to unlock
function check_login_attempts_exceeded($database, $config, $userid = 0, $return_time = FALSE) {
// Set the time of now for checking if they are past the lockout expiry
$time = CURRENT_TIME;
$userid = (int) $userid;
$unauthenticated_user = FALSE;
// Check if this user is logged in - If they are, we can return false
// since this user is already logged in and doesn't need to have attempts
// logged for them anymore.
if ($userid > 0 && $userid == $this->userid) {
return FALSE;
} else {
$unauthenticated_user = TRUE;
}
// If the user is a guest, we can process the request since it's not important
// otherwise. This condition should always be true, but better safe than sorry?
if ($unauthenticated_user) {
// If the login_timeout isn't set we can check if the attempts are over
// and set it otherwise, it must be set so we will see if its expired
if (!$this->login_timeout || $this->login_timeout == 0) {
// Check the global lockout first - if it above the max then we will
// ignore the fact that the user is over since we're not there yet
if ($this->login_attempts >= $config->general->max_login_trys) {
$this->login_timeout = (int) $time + ($config->general->lockout_time * 60);
$database->update_prepared_query("sessions", array("lockout_time" => $this->login_timeout), array("sid" => $this->session_id));
return TRUE;
}
// Now lets check the specific user case, because we know that the
// above has been handled and returned already
if ($userid > 0) {
$user_info = $database->prepared_select("users", "WHERE uid = ? LIMIT 1", array($userid), "loginattempts, loginlockoutexpiry");
if ($user_info) {
$user_specific_loginattempts = $user_info["loginattempts"];
$user_specific_loginexpiry = $user_info["loginlockoutexpiry"];
// If this specific user is over the max attempts, then we return
// true because they're over the max attempt
if ($user_specific_loginattempts >= $config->general->max_login_trys) {
// We also want to see if their expiry is over the max limit
// and it if is, we will return true, if it is expired,
// lets remove it and set these back to 0 and return false!
if ($user_specific_loginexpiry > $time) {
if ($return_time) {
$secsleft = (int) ($user_specific_loginexpiry - $time);
$hoursleft = floor($secsleft / 3600);
$minsleft = floor(($secsleft / 60) % 60);
$secsleft = floor($secsleft % 60);
return array("hours" => $hoursleft, "minutes" => $minsleft, "seconds" => $secsleft);
}
return TRUE;
} else {
// This user specific timeout has expired, so lets
// remove it from the system and let the user attempt
// a login!
$database->update_prepared_query("users", array("loginattempts" => 0, "loginlockoutexpiry" => 0), array("uid" => $userid));
return FALSE;
}
}
} else {
// There was no user information found for this id, so we can't
// return a result and we will just return false instead
return FALSE;
}
}
// Must not be over yet then, so lets return false as no user was
// supplied and the user hasn't hit the limit globally yet either
return FALSE;
} else {
// The login timeout is set, let see if it's expired or not?
if ($this->login_timeout <= CURRENT_TIME) {
if ($userid > 0) {
$user_info = $database->prepared_select("users", "WHERE uid = ? LIMIT 1", array($userid), "loginattempts, loginlockoutexpiry");
if ($user_info) {
if ($user_info["loginattempts"] >= $config->general->max_login_trys) {
if ($user_info["loginlockoutexpiry"] > $time) {
return TRUE;
} else {
$this->login_timeout = 0;
$this->login_attempts = 0;
$database->update_prepared_query("sessions", array("login_attempts" => $this->login_attempts, "lockout_time" => $this->login_timeout), array("sid" => $this->session_id));
$database->update_prepared_query("users", array("loginattempts" => 0, "loginlockoutexpiry" => 0), array("uid" => $userid));
return FALSE;
}
} else {
// The user they're trying for is not at the max tries so we can return false!
return FALSE;
}
} else {
// There was no user information found for this id, so we can't
// return a result and we will just return false instead
return FALSE;
}
} else {
// The login timeout has expired for this guest account so
// we remove it globally!
$this->login_timeout = 0;
$this->login_attempts = 0;
$database->update_prepared_query("sessions", array("login_attempts" => $this->login_attempts, "lockout_time" => $this->login_timeout), array("sid" => $this->session_id));
return FALSE;
}
}
// They must still be expired, so lets return true!
return TRUE;
}
}
return FALSE;
}
Does this seem like a good userflow and does the function seem to be missing anything that you would suggest that could potentially lead to an error?
I have been troubleshooting it and all my conditions SEEM to be passing, but I've also spent about 100 hours on this application already and a fresh pair of eyes to suggest any holes I may have missed would be very beneficial at this point.
If it all looks good, let me know so I can stop going crazy overbuilding this thing!
i just force expire the cookie it's quite simple its balance between being secure and provably annoying for brute force
<?php
session_start();
session_regenerate_id(true); // regenerate new cookie everytime page refresh
$reload_userTimeout=40; // set the new cookie to expire 40seconds everytime user reload the page( they need to type the form in just 40 secs)
setcookie(session_name(),session_id(),time()+$reload_userTimeout);
if (login detail wrong) {
session_unset();
session_regenerate_id(true); // regenerate new cookie
$captchaWrong_userTimeout=15; // since they got login details wrong The cookie would now expire 15 secs ( shorter than before so they need to type fast as possible :p )
setcookie(session_name(),session_id(),time()+$captchaWrong_userTimeout);
} else { // login detail is correct
session_unset();
session_regenerate_id(true); // regenerate new cookie
$captchaCorrect_userTimeout=1; // force expire the cookie in just 1 second
setcookie(session_name(),session_id(),time()+$captchaCorrect_userTimeout);
session_write_close(); // prevent cookie from being overwritten ( user must clear web browser cookies or use incognito mode to overwrite the cookie )
}
?>

laravel | add to wishes without login

I am trying to make when non auth user click to link "Add To wishes" these link going to addtowishes method and send parameter id.
After, first I check does cookie 'wishes' exist if not, I make empty array and in that array I add 'id' after I push this array in cookie.
in else case I get cookie 'wishes' and in that array I push id.But every time when i start method, cookie::get('wishes') is null.
public function addtowishes($id)
{
if(emptyArray(Cookie::get('wishes'))) {
$wishes=Array();
array_push($wishes,$id);
$cookie = cookie('wishes', $wishes, 500);
}else {
$cookie=Cookie::get('wishes');
array_push($cookie->getValue(), $id);
$cookie = cookie('wishes', $cookie->getValue(), 500);
dd($cookie);
}
}
Cookies are set with the function setcookie()
also note that the expire parameter is a unix timestamp. therefore 500 will always be expired. Use time()+60*60*24*30 for 30 days for example

How to get all cache entries with a tag on Laravel

I'm building a login throttling system using Laravel, which I use to save every failed login on a cache Database. (I use Redis).
The code:
class FailedLogins
{
const NUM_FAILURES_TO_LOCK = 30,
TIME_RANGE = 10; // In minutes
public function add($email, $ip = null)
{
if (is_null($ip))
$ip = request()->ip();
$index = md5($email . $ip);
Cache::tags('failed.logins')->put($index, 1, self::TIME_RANGE);
}
public function hasTooMany()
{
$numFailedLogins = count(Cache::tags('failed.logins')->get());
return ($numFailedLogins >= self::NUM_FAILURES_TO_LOCK);
}
}
The issue is on the hasTooMany method, I have to provide a key parameter on the get method. What I was trying to do on this line: Cache::tags('failed.logins')->get() is to get all entries with the failed.logins tag, so I can count how many there are.
Well, that is not working, because I can't do that. So what do you recommend me to use so I can solve it? If it's a Redis only solutions that's fine too.
You could use redis hashes:
http://redis.io/commands/hset
But you can't set individual expiration date on hash keys, so you would have to delete them manually, or use main key with hour in it, like:
failed.logins:08 and expire whole.

Country based redirect only working for staging site not live site

I am trying to:
Make a redirect based on the IP of the visitor, i have my website hosted at wpengine and they have a GEO IP service from which i can get the users country based on their IP. If you are coming to the website from sweden,denmark or norway you will not be redirected but stay on page... http://www.centuri.se but if you are not from this countries you will be redirected to the english version of the site which is translated with wpml... so you will go to this page ... http://www.centuri.se/en/ which is the translated one.
I am using this piece of code to make the redirection.
<?php
// THE COOKIE NAME
$cookie_name = "country";
// ACCEPTED COUNTRIES THAT SKIPS THE REDIRECT
$countries = array('se','dk','no');
// CHECK IF YOUR COOKIE IS SET
if (!isset($_COOKIE[$cookie_name])) {
// GET USER INFO
$userInfo = do_shortcode('[geoip-country]');
// GET COUNTRY INTO LOWERCASE
$country = strtolower($userInfo);
//SET COOKIE BASED ON COUNTRY NAME FROM USER
setcookie('country', $country, time() + (3600 * 24 * 30), '/');
if(!in_array($country, $countries)) {
//Set a cookie to tell that this user has been redirected
setcookie('redirect', 1, time() + (3600 * 24 * 30), '/');
wp_redirect( home_url() . '/en/' ); exit;
}
}
?>
On my staging server from wpengine this sollution works flawless, you can test it for yourself http://centuri.staging.wpengine.com but when this script is applied for my live server i am redirected to http://www.centuri.se/en/en and will get an 404 message - i have tried to switch the part of the redirect from home_url() to site_url() instead to see any difference but if i do that on my live server this will give me a redirect loop. I have for now commented this out for my live site since it will make my website crash.
Could this be any setting done in WPML? i don't know really where to go from here... it's so confusing since it's works flawless on my staging server and not my live server and the code and databases are identical.
I haven't tested these but, here are a couple ideas:
1) You might just need to add a check for the current WPML language to make sure you aren't continuing to redirect the user once they are already on the EN site.
2) Your setting a $_COOKIE['redirect'] but not doing anything with it. You could check that where you are checking the other cookie (I'm not sure the cookie will get set and be available immediately though if you are redirecting in a loop).
<?php
// THE COOKIE NAME
$cookie_name = "country";
// ACCEPTED COUNTRIES THAT SKIPS THE REDIRECT
$countries = array('se','dk','no');
// CHECK IF YOUR COOKIE IS SET
if (
!isset( $_COOKIE[$cookie_name] )
// Check redirect cookie??
// && !isset( $_COOKIE['redirect'] )
) {
// GET USER INFO
$userInfo = do_shortcode('[geoip-country]');
// GET COUNTRY INTO LOWERCASE
$country = strtolower($userInfo);
// SET COOKIE BASED ON COUNTRY NAME FROM USER
setcookie('country', $country, time() + (3600 * 24 * 30), '/');
if (
// Don't redirect if current country is in skip list
!in_array($country, $countries)
// Also Check WPML lang code global to make sure the user
// isn't already on the english site
&& ICL_LANGUAGE_CODE !== 'en'
) {
// Set a cookie to tell that this user has been redirected
setcookie( 'redirect', 1, time() + (3600 * 24 * 30), '/' );
wp_redirect( home_url() . '/en/' ); exit;
}
}
I just recently implemented the WPEngine GeoIP check myself, and another thing you might consider is using their PHP Class directly instead of do_shortcode (seems like that is probably working alright for you, but here is another method).
add_action( 'init', 'country_geo_redirect' );
function country_geo_redirect() {
$geo = WPEngine\GeoIp::instance();
$lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
if (
!is_admin() && // Let's not redirect the admin panel
!in_array( $GLOBALS['pagenow'], array( 'wp-login.php', 'wp-register.php' ) ) &&
$_SERVER['HTTP_HOST'] !== 'example.com.au' && // Make sure we aren't already on this domain
(
strpos( strtolower( $lang ), 'en-au' ) > -1 // Check the user's browser languages for what we are targeting
|| $geo->country() == 'AU' // Fallback to using WPEngine's GeoIP Check if Browser lang doesn't include en-AU
)
) {
wp_redirect( 'http://example.com' . $_SERVER['REQUEST_URI'] , 301 );
exit;
}
}
You have to keep in mind though, with using their class ($geo = WPEngine\GeoIp::instance();) it isn't able to determine the user's country unless it is run within an init action (if you run it directly in your functions.php file, $country = $geo->country(); will return NULL).
With this second example, I'm also checking the user's browser languages first to see if it includes the target language (en-AU in this case) so that I might be able to skip the GeoIP lookup (in case there is any delay in that lookup) to improve loading speed (haven't actually tested that lookup time yet).

How to create "remember me checkbox" using Codeigniter session library?

in Codeigniter I am building an Authentication system for my web site and to achieve that I use session library
session->set_userdata('username')
this will save the session -I believe- for some time
I want to provide a "remember me" checkbox in the login form so the user can save the session forever - could not find a way to save the session forever!?
Note:$sess_expiration will not work because it sets expiration date for all users and what I want to do is setting the expiration date based on his preferences
is that possible? and how to do it?
Thanks
If the remember me checkbox is checked you set a cookie on the user's system with a random string. E.g.:
$cookie = array(
'name' => 'remember_me_token',
'value' => 'Random string',
'expire' => '1209600', // Two weeks
'domain' => '.your_domain.com',
'path' => '/'
);
set_cookie($cookie);
You also save this random string in the users table, e.g. in the column remember_me_token.
Now, when a user (who is not yet logged in) tries to access a page that requires authentication:
you check if there is a cookie by the name of remember_me token on his system
if it's there, you check the database if there is a record with the same value
if so, you recreate this user's session (this means they are logged in)
show the page they were visiting
If one of the requirements above is not met, you redirect them to the login page.
For security reasons you may want to renew the random remember_me_token every time the user logs in. You can also update the expiry date of the cookie every time the user logs in. This way he will stay logged in.
It would be too much work to write all the code for you, but I hope this helps you to implement this functionality yourself. Please comment if you have any questions. Good luck.
I needed the same thing. You cannot accomplish this using CI settings so I have chosen to override the setcookie method of the CI Session class (in MY_Session):
function _set_cookie($cookie_data = NULL)
{
if (is_null($cookie_data))
{
$cookie_data = $this->userdata;
}
// Serialize the userdata for the cookie
$cookie_data = $this->_serialize($cookie_data);
if ($this->sess_encrypt_cookie == TRUE)
{
$cookie_data = $this->CI->encrypt->encode($cookie_data);
}
else
{
// if encryption is not used, we provide an md5 hash to prevent userside tampering
$cookie_data = $cookie_data.md5($cookie_data.$this->encryption_key);
}
setcookie(
$this->sess_cookie_name,
$cookie_data,
$this->userdata('rememberme') == true ? $this->sess_expiration + time() : 0,
$this->cookie_path,
$this->cookie_domain,
0
);
}
Of course you need to set the rememberme flag in your session based upon the choice the user made.
I used hooks to do this
Enable hooks in "config.php" setting $config['enable_hooks'] = TRUE;
In your login controller save the checkbox value on session
$this->session->set_userdata('remember_me', $this->input->post('remember_me'));
In your "hooks.php" file add this
$hook['post_controller_constructor'] = array(
'class' => 'Change_Config',
'function' => 'change_session_expiration',
'filename' => 'change_config.php',
'filepath' => 'hooks'
);
In your "hooks" folder create a file called "change_config.php" and paste this
<?php
class Change_Config {
public function change_session_expiration() {
$ci = & get_instance();
$remember = $ci->session->userdata('remember_me');
if($remember && $ci->session->sess_expire_on_close) {
$new_expiration = (60*60*24*365); //A year milliseconds
$ci->config->set_item('sess_expiration', $new_expiration);
$ci->config->set_item('sess_expire_on_close', FALSE);
$ci->session->sess_expiration = $new_expiration;
$ci->session->sess_expire_on_close = FALSE;
}
}
}
You could use isset to check if the cookie has a value, and if the cookie is deleted, it will return negative. If it is negative, you can set the cookie again... of course, this will be like "renewing" the cookie, so if the user doesn't come by the expiration, they will be forgotten. (Set a very big time till the expiration!)
For example:
<?php
if (isset($_COOKIE['autologin'])) { //Checks for cookie
echo "Welcome back!... Logging you in.";
}else{ //Sets a cookie
echo "Please sign in";
$expiretime=time()+60*60*24*365; //set expire time to a year
setcookie("autologin", "What is here is not important.", $expiretime);
};
?>
$this->session->sess_expiration = '14400'; // expires in 4 hours
$this->session->set_userdata($data); // set session
You can just put this
if ($remember) {
$new_expiration = (60*60*24*365);
$this->config->set_item('sess_expiration', $new_expiration);
$this->config->set_item('sess_expire_on_close', FALSE);
$this->session->sess_expiration = $new_expiration;
$this->session->sess_expire_on_close = FALSE;
}
You can set sess_expiration to 0 and then set a custom variable into the session like, remember_me=1. Check this value and destroy the session if needed after a time.
This would be a workarround.

Categories