Laravel 4.1 authentication session data not persisting across requests - database

I have a Laravel 4.1 app using the eloquent authentication driver and the database session driver. My authentication controller is successfully running Auth::attempt and redirecting to a new page. However, once on the new page the authentication session data seems to be gone. I have an auth filter running on the redirected-to page and it fails, which then redirects the user to the login page again. The user never gets past the login page.
Here is my session.php:
<?php
return array(
'driver' => 'database',
'lifetime' => 120,
'expire_on_close' => true,
'files' => storage_path().'/sessions',
'connection' => 'mysql',
'table' => 'sessions',
'lottery' => array(2, 100),
'cookie' => 'laravel_session',
'path' => '/',
'domain' => null,
'secure' => false,
);
My sessions table schema:
CREATE TABLE `sessions` (
`id` varchar(32) NOT NULL,
`payload` text NOT NULL,
`last_activity` int(11) NOT NULL,
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
And my auth filter:
Route::filter('auth', function()
{
if (Auth::guest()) {
if ( Request::ajax() ) {
return Response::make("Your session has timed out. Please sign in again.", 401);
} else {
return Redirect::guest('login');
}
}
});
The Auth::guest() call in the auth filter always returns false.
I added some Logging to the user() method of Illuminate/Auth/Guard.php and found that in the POST from the login form, the authentication data was in the session when the user() method was called. However, when it is called from the auth filter on the redirect (Auth::guest() indirectly calls the user() method), the session data is gone.
Here is the user() method, for reference:
public function user()
{
if ($this->loggedOut) return;
// If we have already retrieved the user for the current request we can just
// return it back immediately. We do not want to pull the user data every
// request into the method because that would tremendously slow an app.
if ( ! is_null($this->user))
{
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
$user = null;
if ( ! is_null($id))
{
$user = $this->provider->retrieveByID($id);
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$recaller = $this->getRecaller();
if (is_null($user) && ! is_null($recaller))
{
$user = $this->getUserByRecaller($recaller);
}
return $this->user = $user;
}
When user() is called from the auth filter, $this->loggedOut is false, but $this->user is null and $this->session->get($this->getName()) returns null.
It does not appear that Auth::logout() is being called at any point.

The id field of the sessions table needs to have a length of at least 40 because Laravel uses a sha1 hash as the session id.

I had a similar issue where the session was not persisting between requests when I deployed my site to a digital ocean instance and I spent a few days searching for an answer to fix this issue since the site was working fine in a local vagrant machine.
The solution to my problem was that on the User model I had to change the getAuthPassword function return statement from "$this->password" to "$this->Password" which it is the column name in the database.
public function getAuthPassword()
{
return $this->Password;
}

Related

Are OTP for user registration supposed to store in session or datbase in laravel?

Are OTP supposed to store in session or database. Can anyone please tell the flow of OTP. As Far as i understood, when a user submits the necessary field the user details and the otp gets stored in database, and after register another form opens to enter otp and then the registration finally success. But I dont get the actual logic. To store the otp we need to store all the data in database, all the data gets stored (user info) only then we can verify the otp. I am using session but I am not sure if the code is correct,
public function otpVerify(Request $request)
{
$data = $request->validate([
'verification_code' => ['required', 'numeric'],
'phone_number' => ['required', 'string'],
]);
$otp = $request->session()->get('otp');
$enteredOtp = $request->session()->get('otp');
if ($otp == $enteredOtp) {
$user = tap(User::where('phone_number', $data['phone_number']));
// ->update(['isVerified' => true]);
return success([
$success,
$otp
], __('User created successfully'));
} else {
return problem([], 500, 'OTP Doesnt Match');
}
public function register(RegisterUserRequest $request)
{
$user = new User($request->validated());
$otp = rand(10000, 99999);
$otp_expires_time = Carbon::now()->addSeconds(20);
if (!env('APP_ENV') === 'local') {
$sms = AWS::createClient('sns');
$sms->publish([
'Message' => 'Your OTP code is:' + $otp,
'PhoneNumber' => $user->phone_number,
'MessageAttributes' => [
'AWS.SNS.SMS.SMSType' => [
'DataType' => 'String',
'StringValue' => 'Transactional',
]
],
]);
} else {
Log::channel('otplog')->info('Your OTP code is:'. $otp);
}
$status = $user->save();
$user->roles()->attach($request->role_id);
$user->brands()->attach($request->brand_id);
$user->appliances()->attach($request->appliance_id);
$success['token'] = $user->createToken('MyAuthApp')->plainTextToken;
$success['name'] = $user->name;
Session::put('OTP', $otp, 'expiry_time',$otp_expires_time);
if ($status) {
return success([
$success,
$otp_expires_time,
$otp
], __('User created successfully'));
} else {
return problem([], 500, 'USER_REGISTER_FAIL');
}
}
Store in database is a good option
Here's my take on this matter:
Session Storage
Use session storage if you want the user to only be able to use that session on their requesting device real time. The moment they close their session, it will dispose. In this case, the duration of holding such OTP is dependent on the Session lifecycle. Use this method for real time verification processes on the app, e.g 2 Factor-Authentication.
Database Storage
Database storage is great but can be rather an overkill for some scenarios, especially real time ones. Because you have to add records that will only last for few seconds and deleted again, in the case of 2FA. I would prefer using Database storage in cases where the OTP expiry has some "set" LifeSpan. For example, imagine a process where a user is filling in a registration form, where they have an option to save and continue later. In such scenarios, if part of the registration requires some OTP verification and the user can't verify real time, you normally give the user an OTP that expires after some lifespan e.g 3Hrs. So the user can come back later and use that OTP to continue with their registration. In this case, you delete the OTP from database as soon as the registration is completed.
in session put method you did wrong you need to do like put array in session the way you are sending the expiration time you will not get the expiration time when you call you need to do like this
Session::put([
'OTP' => $otp,
'expiry_time' => $otp_expires_time,
])

Login Wordpress user automatically on REST API call

I have a react application and currently the user authentication done with Wordpress. So I'm using JWT_AUTH! plugin to manage user login functionality.
So now the requirement has changed and the users should be able to login to both React and the Wordpress websites with one single login attempt. This means if the user login to the React application, he should automatically log in to his Wordpress application as well. Also if the user login on Wordpress application he should automatically log into his react application as well.
I have created a custom REST API endpoint to do this requirement. But it's not working as expected. This endpoint is working when I using this API link with a browser. That because it doesn't have the ability to store cookies with the REST API call.
I also tried to generate auth cookies with the rest API call but it gave me the "500" error.
add_action( 'rest_api_init', function () {
register_rest_route( 'user/v1', '/api-login-check', array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'rest_api_login_user',
'schema' => array(
'type' => 'array'
)
));
});
function rest_api_login_user($object, $request = null) {
$response = array();
$parameters = $request->get_json_params();
$creds = array();
$creds['user_login'] = $object['username'];
$creds['user_password'] = $object['password'];
$creds['remember'] = true;
$user = wp_signon( $creds, false );
if ($user) {
$response['code'] = 200;
$response['message'] = __("User logged in", "wp-rest-user");
return new WP_REST_Response($response, 123);
}else{
$response['code'] = 403;
$response['message'] = __("User not logged in", "wp-rest-user");
}
}
Is there any easy way to do this? Also if there a way to redirect user to different url from a rest api call, that also fine.

cakephp referrer after auth deny

How do I get the page that was denied access by the Auth component using CakePHP 2.x? If I use the referer() function, it gives me the page that linked to the denied action. Here's my code:
public function login() {
//get the current url of the login page (or current controller+action)
$currentLoginUrl = "/login";
if ($this->request->is('post')) {
$this->User->recursive = -1;
$user = $this->User->find(
'first',
array(
'conditions' => array(
'User.username' => $this->request->data['User']['username'],
'User.password' => AuthComponent::password($this->request->data['User']['password'])
)
)
);
if ($user && $this->Auth->login($user['User'])) {
//if the referer page is not from login page,
if( $this->referer() != $currentLoginUrl )
//use $this->referer() right away
$this->redirect($this->referer('/admin', true)); //if referer can't be read, or if its not from local server, use $this->Auth->rediret() instead
else
//if the user lands on login page first, rely on our session
$this->redirect( $this->Session->read('beforeLogin_referer') );
}
else
$this->Session->setFlash('Username or password is incorrect', 'default', array('class' => 'alert-danger'));
}
if( $this->referer() != $currentLoginUrl )
//store this value to use once user is succussfully logged in
$this->Session->write('beforeLogin_referer', $this->referer('/admin', true) ) ; //if referer can't be read, or if its not from local server, use $this->Auth->rediret() instead
}
So basically what happens is I'm not logged in, and I'm at this url:
'http://localhost/hotelguide/hotels/view/17/'
and I click on a link that would take me to
'http://localhost/hotelguide/hotels/review/17/'
but that requires a user to be logged in, so it redirects me to the login page, which, when I debug referrer(), it gives me this:
'http://localhost/hotelguide/hotels/view/17/'
What am I doing wrong?
When you use Auth component in CakePHP and try to access a restricted site, it redirects you to login page and saves the referring page in session. Session key Auth.redirect holds the value you're looking for - the page that you were trying to access.
Look at the __unauthenticated() method of AuthComponent. It includes the code responsible for writing session value to Auth.redirect. If you don't want to use AuthComponent, you can check how it is implemented in the component and write your own solution based on the method I have mentioned.
$this->referer() will not provide you the correct referrer url for you. If you want to get referrer url just use $this->Session->read('Auth.redirect');
You can find exact url you are looking for by $this->Session->read('Auth.redirect');
$this->referer() value update every time when you reload the page.

CakePHP 2.3.2 BasicAuthentication not working

I tried out the "Simple Acl controlled Application 1&2" tutorial located at http://book.cakephp.org/2.0/en/tutorials-and-examples/simple-acl-controlled-application/simple-acl-controlled-application.html .
After doing this, I tried to activate BasicAuth instead of FormAuth.
I reimplemented the login() function im my UsersController as follows:
public function login() {
if ($this->Auth->login()) {
return $this->redirect($this->Auth->redirect());
} else {
$this->Session->setFlash('Not able to login');
}
}
and changed the $components variable in my AppController to the following:
public $components = array(
'Acl',
'Auth' => array(
'authorize' => array(
'Actions' => array('actionPath' => 'controllers')
),
'authenticate' => array('Basic')
),
'DebugKit.Toolbar',
'Session'
);
The BasicAuth "popup" appears as expected, but when I'm trying to login, it reappers in an endless loop. I did not change anything after doing the tutorial except for including DebugKit.
What am I missing? I hope someone can help me, as I'd like to go with CakePHP coding my next Project!
Update
AppController
public function beforeFilter() {
//Configure AuthComponent
$this->Auth->allow('display');
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
$this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
$this->Auth->loginRedirect = array('controller' => 'posts', 'action' => 'add');
}
UsersController
public function beforeFilter() {
parent::beforeFilter();
}
I'm trying to access e.g. /users/ which works like a charm using the FormAuth described in the tutorial, so there can't be a permission problem. Logindata is pretty simple for testing (admin:admin) so there should be no problem either.
Update 2
In my Apache Log i get the following, so it says I'm not authorized:
IP - - [16/Apr/2013:18:08:37 +0200] "GET /users/login HTTP/1.0" 401 5179 "-" "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:23.0) Gecko/20130414 Firefox/23.0"
Update 3
For some reason it seems, that User and Password are either not sent or not saved in PHP. If I rewrite /lif/Cake/Controller/Auth/BasicAuthenticate to the following, it works!
public function authenticate(CakeRequest $request, CakeResponse $response) {
$_SERVER['PHP_AUTH_USER'] = $_SERVER['PHP_AUTH_PW'] = "admin";
$result = $this->getUser($request);
if (empty($result)) {
$response->header($this->loginHeaders());
$response->statusCode(401);
$response->send();
return false;
}
return $result;
}
Update 4
Don't know if thats helpful, but the Server is running Plesk 11, latest update, no special modifications.
Update 5
Okay, that answer of "thaJeztah" was useful, but now I'm getting more problems which can be subdivided.
Changed mode from fcgid to apache module
1.1. Results in working login, but logout does not work! After the redirect, the session seems to be cleared, but i can still access every restricted page until i clear my browsers "Active Logins" as it is called in Firefox.
var_dump($this->Session->read('Auth.User'));
NULL
When I access /users/login I am automatically logged in and redirected without having to enter login credentials.
print "<pre>";
print_r($this->Session->read('Auth.User'));
print "</pre>";
Array
(
[id] => 1
[username] => admin
[group_id] => 1
[created] => 2013-04-12 12:54:26
[modified] => 2013-04-16 14:27:24
[is_active] => 1
[Group] => Array
(
[id] => 1
[name] => Admin
[created] => 2013-04-12 12:46:42
[modified] => 2013-04-12 12:46:42
)
)
Using the .htaccess based solution works as well, it even looks like as if thats the only change needed (I removed the list() code as I did never get into it and it worked as well).
2.1. Same problem as above, no real logout possible.
Update 6
Probably the last or one of my last updates. :-)
Right now I'm trying to do a "fake logout" by logging the user in as a guest user I created who has only access to /users/login and /pages/home: http://guest:guest#my.domain/users/login
Accessing /users/logout might work too, as I'm using this piece of code there:
public function logout() {
$user = $this->User->find('first', array('conditions' => array('username' => 'guest')));
$this->Auth->login($user['User']['id']);
}
I simly don't believe, this will be consistent, since I believe the Session data will be deleted some time and the browser still got the active admin login and authenticates using these - am I right?
After that I can login a different User again using http://admin:admin#my.domain/users/login. Not perfect, but works at least for Firefox.
So basically one last question: Any suggestions on how to force a BasicAuth when accessing /users/login? This way I could easily switch users at any time using any client.
Update 7
I found a way to do exactly this with the idea in my accepted answer. I hope I caught all edge cases in this, feel free to correct me if not!
(P.s.: when using ACL and or basic authentication the isAuthorized() in at least the AppController seems to be ignored (it was recognized, but had no effect - when i deleted the method without changing $components, i got an error) which lead to me implementing this without using isAuthorized().)
AppController.php
public function beforeFilter($redirectlogin = true) {
//Configure AuthComponent
$this->Auth->allow('display', '/users/login');
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
$this->Auth->logoutRedirect = array('controller' => 'pages', 'action' => 'home');
$this->Auth->loginRedirect = array('controller' => 'pages', 'action' => 'home');
$this->Auth->unauthorizedRedirect = array('controller' => 'HTTPCODE', 'action' => 'c403');
if($redirectlogin && $this->Session->read('Auth.needs_reauthenticate')) {
if(!($this->request->params['controller'] == $this->Auth->loginRedirect['controller'] && $this->request->params['pass'][0] == $this->Auth->loginRedirect['action'])) {
$this->redirect('/users/login');
}
}
}
UsersController.php
public function beforeFilter() {
parent::beforeFilter(false);
}
public function login() {
$this->autoRender = false;
$this->Session->write('Auth.needs_reauthenticate', true);
if(!$this->Session->check('Auth.count')) {
$count = 1;
} else {
$count = $this->Session->read('Auth.count') + 1;
}
$this->Session->write('Auth.count', $count);
if($this->Session->read('Auth.needs_reauthenticate')) {
if((isset($_SERVER['HTTP_AUTHORIZATION']) && $this->Session->read('Auth.count') == 1) || (!isset($_SERVER['HTTP_AUTHORIZATION']) || empty($_SERVER['HTTP_AUTHORIZATION']) || !$this->Session->check('Auth.sent_header_step') || $this->Session->read('Auth.sent_header_step') < 1)) {
unset($_SERVER['HTTP_AUTHORIZATION']);
$this->Session->write('Auth.redirectTo', $this->Auth->redirect());
$this->response->header(sprintf('WWW-Authenticate: Basic realm="%s"', env('SERVER_NAME')));
$this->response->statusCode(401);
$this->response->send();
$this->Session->write('Auth.sent_header_step', 1);
}
if(isset($_SERVER['HTTP_AUTHORIZATION'])) {
$this->Session->write('Auth.sent_header_step', 0);
$base64string = base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6));
if(!(strlen($base64string) > 1 && substr($base64string, -1, 1) != ":")) {
$_SERVER['PHP_AUTH_USER'] = "";
$_SERVER['PHP_AUTH_PW'] = "";
}
$data = true;
}
$this->Auth->logout();
if(isset($data) && $this->Session->read('Auth.count') > 1) {
if($this->Auth->login()) {
$this->Session->write('Auth.needs_reauthenticate', false);
if($this->Session->check('Auth.redirectTo')) {
$redirectTo = $this->Session->read('Auth.redirectTo');
$this->Session->delete('Auth.redirectTo');
$this->Session->delete('Auth.count');
return $this->redirect($redirectTo);
} else {
return $this->redirect($this->Auth->redirect());
}
} else {
$this->response->statusCode(403);
// my 403 message
}
} else {
if(!isset($_SERVER['HTTP_AUTHORIZATION']) && $this->Session->read('Auth.count') > 1 && isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']) && trim($_SERVER['PHP_AUTH_USER']) != "" && trim($_SERVER['PHP_AUTH_PW']) != "") {
if($this->Auth->login()) {
$this->Session->write('Auth.needs_reauthenticate', false);
if($this->Session->check('Auth.redirectTo')) {
$redirectTo = $this->Session->read('Auth.redirectTo');
$this->Session->delete('Auth.redirectTo');
$this->Session->delete('Auth.count');
unset($_SERVER['HTTP_AUTHORIZATION']);
unset($_SERVER['PHP_AUTH_USER']);
unset($_SERVER['PHP_AUTH_PW']);
return $this->redirect($redirectTo);
} else {
return $this->redirect($this->Auth->redirect());
}
} else {
$this->response->statusCode(403);
// my 403 message
}
}
$this->response->statusCode(403);
// my 403 message
}
}
}
Thanks in advance
Adrian
Using Basic Authentication when running PHP as (Fast)CGI
It's possible that your website is configured to run PHP as (Fast)CGI, in which case the PHP_AUTH_USER and PHP_AUTH_PWD keys are not present in the $_SERVER variable. The BasicAuthenticate AuthComponent relies on these keys.
Either change the domain/webhosting settings in Plesk to run php as 'apache module' for this website/domain or extend the BasicAuthenticate Component to get these variables some other way.
More information on this subject can be found in this question:
PHP_AUTH_USER not set?
And for the Symfony framework, somebody seems to have written a workaround that may be useful in this situation as well;
https://github.com/symfony/symfony/issues/1813
update: loging out when using basic authentication
Loging out when using basic authentication is not really possible. Basic authentication is a 'stateless' authentication mechanism, which basically means that the browser is sending the user-credentials with every request. In other words; the server does not keep a 'state', the browser does. With Basic Authentication, you require the browser to send user credentials and as long as the browser sends valid credentials, you allow the browser access to the protected pages.
The only way to log out, is to close the browser, or tell the browser to close active sessions/logins.
Read more information here:
http://en.wikipedia.org/wiki/Basic_access_authentication
http basic authentication "log out"
Notes
Base Authentication is not a secure authentication mechanism; the username and password is sent to the server with every request. The password is sent unencrypted (only base64 encoded to prevent problems with special characters).
Although Form authentication also sends the password unencrypted, it is (a bit more) secure as it will only send the username/password when logging in. Subsequent requests will only send the Session-id, which can be set to expire and limited to a specific IP and/or Browser type.
In all cases, securing the connection via SSL is obviously important.
Forcing re-authentication on the login page
This is just 'thinking out loud', untested and highly experimental :)
Try this;
If no session is active, proceed the normal way. There is no way to differentiate 'already logged in' users from 'new users' in Basic Authentication - it is stateless
If a session is active, a user apparently has an active session going on. Don't destroy the session, but change the rules;
If the credentials sent are for the same user as the username inside the session (or, better: $this->Auth->user('username');?, Then invalidate the session (not destroy) and force the user to re-authenticate, by sending login headers;
You may copy the headers from the BasicAuthenticate behavior; see the source here BasicAuthenticate::authenticate()
Regarding 'copying the headers'; Maybe extending the BasicAuthenticate is a cleaner approach; handle all your custom code inside your customized version.
Additionally, check if the session is still 'valid' inside AppController::isAuthorized()(see Using ControllerAuthorize)
Something like this (Mockup code):
Login page/action:
if ("usercredentials sent by browser" === "current logged in user in session") {
// Mark session as 'needs-to-reauthenticate'
$this->Session->write('Auth.needs_reauthenticate', true);
// Need to find a clean approach to get the BasicAuth loginHeaders()
// *including* the right settings (realm)
$this->response->header(/*BasicAuth::loginHeaders()*/);
// Access denied status
$this->response->statusCode(401);
return $this->response->send();
}
AppController::isAuthorized()
if ($this->Session->read('Auth.needs_reauthenticate')) {
return false;
} else {
// Normal 'isAuthorized()' checks here
}
NOTE:
Once a browser has visited the 'login' page during an active session, the user will either have to log-in with different credentials, or close the browser to log in again.
This may be problematic if the session-cookie is still present after closing and re-opening the browser. Try to force the session-cookie to be a "real" session-cookie and have it deleted on browser close by setting Session.cookieTimeout to 0 (see Session Configuration

Unable to update user profile data in database

In my controller
public function profile() {
$UserInfo = $this->Auth->user()
if(!empty($this->data)) {
print_r($this->data);
$this->User->save($this->data);
}
if(!empty($UserInfo['id'])){
$this->data = $this->User->find('first',array('conditions'=>array('id'=>$UserInfo['id'])));
}
}
when i submit the data it is not submitted to db and i get only previous value.
Why are you querying the session here? of course this will always get you the old data again after the save.
Use the database as always, update the database again and only then overwrite the session maybe (You seem to be using cake 1.3):
public function profile() {
$uid = $this->Session->read('Auth.User.id');
if (!empty($this->data)) {
$this->data['User']['id'] = $uid;
if ($this->User->save($this->data, true, array('email', 'first_name', 'last_name', 'id', ...))) {
// if you rely on auth session data from the user, make sure to update that here
$this->Session->write('Auth.User.email', $this->data['User']['email']); // etc
...
// OK, redirect
} else {
// ERROR
}
} else {
$this->data = $this->User->find('first', ...);
}
}
As you can see I update the session keys that have been changed.
If you are using 2.x (which you did not specify as for now) you could also use
$this->Auth->login($this->request->data['User']); // must be the User array directly
although you will have to careful to pass all the data that has been in the session before.
If you plan on using login(), it would be better to find(first) the updated record again and pass this into login() then.
But personally, I prefer to only update the fields that actually changed.
see Editing own account/profile

Resources