Yii SOAP Web Service Session ID is reset - php

I am building a SOAP Web Service with Yii, and I am trying to manage the authentications through the Session ID, but it keeps changing on every request.
This is the code I have written:
class MessagingController extends CController {
public function login($username, $password) {
$user = User::model()->find('username=:username AND password=:password', array(':username' => $username, ':password' => $password));
if($user) {
$session = new Session();
$session->id = Yii::app()->session->sessionID;
$session->user = $user->id;
$session->start = date('Y-m-d H:i:s', time());
$session->duration = $this->sessionDuration; //Session duration is 1hr.`
return $session->save() ?
$this->generateResponse(0, 'Session initiated successfully.', array('SessionId' => $session->id, 'Timestamp' => $session->start, 'Duration' => $session->duration)) : generateResponse(-1, 'Server error. #S', null);
}
return $this->generateResponse(-1, 'Unknown User. #UU', null);
}
}
Config File:
'session' => array(
'autoStart' => true,
'timeout' => 1440,
'cookieMode' => 'allow',
),

I believe you have to specify in your SoapServer that the session should be persistent.
http://php.net/manual/en/soapserver.setpersistence.php

Since soap requests are stateless, each request goes without cookies and as such PHP by default will always create new cookie when starting new session. If you want to use the same session on SoapServer side across multiple client requests, you should set two things:
first ensure that you always send the same session cookie from your requesting server (via SoapClient). This is done by __setCookie():
$soapClient->__setCookie('PHPSESSID', $_COOKIE['PHPSESSID']);
second - as stated above - set soapserver persistence:
$soapServer->setPersistence(SOAP_PERSISTENCE_SESSION);

Related

Quickbooks PHP API: Refresh OAuth 2 Token Failed

I'm trying to access Quickbooks API using the PHP SDK but getting the following error:
Refresh OAuth 2 Access token with Refresh Token failed. Body: [{"error":"invalid_grant"}].
My Tokens seem to work for 24 hours but after that I receive the error above. Each time I call the API, I am saving my updated tokens to my database:
//Client ID & Secret
$qbClientId = $this->scopeConfig->getValue('quickbooks/api/qb_client_id', $storeScope);
$qbClientSecret = $this->scopeConfig->getValue('quickbooks/api/qb_client_secret', $storeScope);
//Retrieve currently saved Refresh_Token from DB
$qbRefreshToken = $this->scopeConfig->getValue('quickbooks/api/qb_refresh_token', $storeScope);
$OAuth2LoginHelper = new OAuth2LoginHelper($qbClientId, $qbClientSecret);
$accessTokenObj = $OAuth2LoginHelper->refreshAccessTokenWithRefreshToken($qbRefreshToken);
$error = $OAuth2LoginHelper->getLastError();
if($error) {
throw new \Exception($error);
} else {
// The refresh token and access token expiration
$refreshTokenValue = $accessTokenObj->getRefreshToken();
$refreshTokenExpiry = $accessTokenObj->getRefreshTokenExpiresAt();
// Save new Refresh Token & Expiry to DB
$this->configInterface->saveConfig('quickbooks/api/qb_refresh_token', $this->encryptor->encrypt($refreshTokenValue), 'default', 0);
$this->configInterface->saveConfig('quickbooks/api/qb_refresh_token_expiry', $refreshTokenExpiry, 'default', 0);
// The access token and access token expiration
$accessTokenValue = $accessTokenObj->getAccessToken();
$accessTokenExpiry = $accessTokenObj->getAccessTokenExpiresAt();
// Save new Access Token & Expiry to DB
$this->configInterface->saveConfig('quickbooks/api/qb_access_token', $this->encryptor->encrypt($accessTokenValue), 'default', 0);
$this->configInterface->saveConfig('quickbooks/api/qb_access_token_expiry', $accessTokenExpiry, 'default', 0);
return DataService::Configure(array(
'auth_mode' => 'oauth2',
'ClientID' => $qbClientId,
'ClientSecret' => $qbClientSecret,
'accessTokenKey' => $accessTokenValue,
'refreshTokenKey' => $refreshTokenValue,
'QBORealmID' => 'MyRealmID',
'baseUrl' => 'Development'
));
}
So as you can see, on each API call, I'm using the refreshAccessTokenWithRefreshToken($qbRefreshToken) method to get new Refresh and Access Tokens and saving those to my DB for next use, however I still receive invalid_grant errors after 24hours.
Any ideas?
I struggled with the same problem. In my case, I had updated the tokens in the database, but not everywhere in memory (specifically in the $dataService object). So continuing to use the $dataService object to make API calls was using the old tokens. D'oh!
Although I think it would still work, you do not need to refresh the tokens with every API call (as David pointed out). My solution was to make the API call and if it fails, then I refresh the tokens and make the API call again. Here is a simplified version of my code:
$user = ...; // get from database
$dataService = getDataServiceObject();
$response = $dataService->Add(...); // hit the QBO API
$error = $dataService->getLastError();
if ($error) {
refreshTokens();
$response = $dataService->Add(...); // try the API call again
}
// ... "Add" complete, onto the next thing
////////////////////////////////
function getDataServiceObject() {
global $user;
return DataService::Configure(array(
'auth_mode' => 'oauth2',
'ClientID' => '...',
'ClientSecret' => '...',
'accessTokenKey' => $user->getQbAccessToken(),
'refreshTokenKey' => $user->getQbRefreshToken(),
'QBORealmID' => $user->getQbRealmId(),
'baseUrl' => '...',
));
}
function refreshTokens() {
global $dataService;
global $user;
$OAuth2LoginHelper = $dataService->getOAuth2LoginHelper();
$obj = $OAuth2LoginHelper->refreshAccessTokenWithRefreshToken($user->getQbRefreshToken());
$newAccessToken = $obj->getAccessToken();
$newRefreshToken = $obj->getRefreshToken();
// update $user and store in database
$user->setQbAccessToken($newAccessToken);
$user->setQbRefreshToken($newRefreshToken);
$user->save();
// update $dataService object
$dataService = getDataServiceObject();
}
https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/faq
Why does my refresh token expire after 24 hours?
Stale refresh tokens expire after 24 hours. Each time you refresh the access_token a new refresh_token is returned with a lifetime of 100 days. The previous refresh_token is now stale and expires after 24 hours. When refreshing the access_token, always use the latest refresh_token returned to you.
Are you sure that the latest refresh token is used?
So as you can see, on each API call, I'm using the refreshAccessTokenWithRefreshToken($qbRefreshToken) method to get new Refresh and Access Tokens and saving those to my DB for next use...
Why are you requesting a new access token and refresh token every time you make a request? Why are not checking if the old access token is stil valid?
As you stated above, you are even storing the expiration time of the access token. So you should know if it is still valid or not.
So when you are making the API request and you access token has expired, you will get a error message. In return, you can now request a new one.

Different Token Expiration Depending of Client Laravel Passport

I have a Laravel application that uses Passport authentication.
Login
public function authenticate(Request $request)
{
$params = [
'grant_type' => 'password',
'client_id' => 1,
'client_secret' => "secret",
'username' => request('username'),
'password' => request('password'),
'active' => 1,
'scope' => '*'
];
$request->request->add($params);
// verify the credentials and create a token for the user
$proxy = Request::create('oauth/token', 'POST');
return Route::dispatch($proxy);
}
I have settled the expiration on AuthServiceProvider:
Passport::routes(function ($router) {
$router->forAccessTokens();
});
Passport::tokensExpireIn(now()->addMinute(1));
Passport::refreshTokensExpireIn(now()->addDays(30));
It works but after 1 minute the token expires. I want a different expiration date for token depending on where I'm trying to make login because I have a website, desktop app and an Android app.
For example:
web app: 8 hours
desktop app: 1 Year
android app: 5 months
I was thinking send me from where I'm trying to make the login, but is that a good way? Are there any other possible ways to do it?
For now I have tried this:
-) deleted From AuthServiceProvider:
Passport::tokensExpireIn(now()->addMinute(1));
And added in Login function:
if (request('from') == 'something') {
Passport::tokensExpireIn(now()->addYears(1));
} else {
Passport::tokensExpireIn(now()->addHours(8));
}
$proxy = Request::create('oauth/token', 'POST');
You can get help from below link please find
For getting user agent detail and apply condition base on agent
for example you can use code like below
if ( Agent::isDesktop() ) {
Passport::tokensExpireIn(now()->addYears(1));
} else if(Agent::isMobile()){
Passport::tokensExpireIn(now()->addMonth(5));
}else{
Passport::tokensExpireIn(now()->addHours(8));
}

Outlook - OAuth 2 - Fail to retrieve access token, grant_type=password is unsupported, only authorization_code and refresh_token are

I'm currently trying to implement a way to synchronize my PHP App calendar with the Outlook calendar of my clients, using Azure API.
I use OAuth2 and the custom Microsoft provider by Steven Maguire.
I currently run in an issue where I get an error in my response :
{"error":"unsupported_grant_type","error_description":"The provided value for the input parameter 'grant_type' is not valid. Expected values are the following: 'authorization_code', 'refresh_token'."}
I'm having trouble understanding why the grant_type password is not supported, even though it says on the documentation of Azure that it is.
The request looks like this :
client_id=44bef79b-**********************&client_secret=H****************&redirect_uri=https%3A%2F%2F192.168.1.123%2Fmapeyral%2Fcalendarsync.php&grant_type=password&username=******************&password=***********&scope=openid%20profile%20offline_access%20Calendars.ReadWrite
The Authorize url used is : https://login.live.com/oauth20_token.srf
as defined in the Steven Maguire provider.
The header contains the content-type application/x-www-form-urlencoded (I've seen a lot of post where this was what caused the error).
Some of my code :
$this->provider = new Microsoft([
'clientId' => MicrosoftGraphConstants::CLIENT_ID,
'clientSecret' => MicrosoftGraphConstants::CLIENT_SECRET,
'redirectUri' => MicrosoftGraphConstants::REDIRECT_URI,
'urlAuthorize' => MicrosoftGraphConstants::AUTHORITY_URL . MicrosoftGraphConstants::AUTHORIZE_ENDPOINT,
'urlAccessToken' => MicrosoftGraphConstants::AUTHORITY_URL . MicrosoftGraphConstants::TOKEN_ENDPOINT,
'urlResourceOwnerDetails' => MicrosoftGraphConstants::RESOURCE_ID,
'scope' => MicrosoftGraphConstants::SCOPES
]);
if ($_SERVER['REQUEST_METHOD'] === 'GET' && !isset($_GET['code']))
{
// Try getting access token from Database
$workingAccount = $GLOBALS['AppUI']->getState('working_account');
if (isset($workingAccount))
{
// DB access
$DB = new DatabaseConnection();
$dbAccess = $DB->getConnection();
$contactData = DBUserUtils::getContactDataFromEmail($GLOBALS['AppUI']->getState('working_account'), $dbAccess);
// If at least one user contact found
if (!is_null($contactData))
{
// If has refresh token => fill session variables using refresh token
if (!is_null($contactData['contact_refreshToken']))
{
log_msg('debug.log', 'Has refresh token');
$GLOBALS['AppUI']->setState('preferred_username', $contactData['contact_email']);
$GLOBALS['AppUI']->setState('given_name', $contactData['contact_first_name']." ".$contactData['contact_last_name']);
// Get new tokens
$newAccessToken = $this->provider->getAccessToken('refresh_token', [
'refresh_token' => $contactData['contact_refreshToken']
]);
// Update tokens and DB
$GLOBALS['AppUI']->setState('refresh_token', $newAccessToken->getRefreshToken());
$GLOBALS['AppUI']->setState('access_token', $newAccessToken->getToken());
DBOAuthUtils::updateTokenForUser($contactData['contact_id'], $GLOBALS['AppUI']->getState('refresh_token'), $dbAccess);
$this->redirectTo($redirectURL);
}
else
{
$this->getAccessToken();
}
}
else
{
$this->getAccessToken();
}
}
else
{
$this->getAccessToken();
}
function getAccessToken(){
$accessToken = $this->provider->getAccessToken('password', [
'username' => '*************',
'password' => '********',
'scope' => MicrosoftGraphConstants::SCOPES
]);
}
During the first try it doesn't pass the if (isset($workingAccount)) condition (as expected) and go straight to the last else.
Code is a bit ugly for now but I don't think it has an impact on my problem.
Any help would be appreciated !
Thanks
Edit : added code
That helped me, the problem was that I need to use Azure Active Directory and not Azure AD 2.0.
Problem solved !

How to log user in and out in slim rest framework using slim session middleware?

I am using Slim framework to build a REST API and this is a rough application that I am using for development purposes. I want to log user in and out , and I set the session variable to the user id. The user is able to login perfectly fine in rest API but the remote device doesnt recognize the session (which means my $SESSION['id'] is empty) where as I clearly started this session in my host rest service. Here is my code:
require 'lib/Slim/Slim.php';
use lib\Slim\Middleware\SessionCookie;
\Slim\Slim::registerAutoloader();
$app = new \Slim\Slim(
array(
'cookies.encrypt' => true,
'cookies.secret_key' => 'my_secret_key',
'cookies.cipher' => MCRYPT_RIJNDAEL_256,
'cookies.cipher_mode' => MCRYPT_MODE_CBC
)
);
$app->add(new \Slim\Middleware\SessionCookie(array(
'expires' => '20 minutes',
'path' => '/',
'domain' => '',
'secure' => false,
'httponly' => false,
'name' => 'slim_session',
'secret' => '',
'cipher' => MCRYPT_RIJNDAEL_256,
'cipher_mode' => MCRYPT_MODE_CBC
)));
$app->get("/login/:string", function($string) use ($app)
{
$input = json_decode($string);
try
{
if ($input->username && $input->password)
{
$user = Model::factory('Users')->where("username",$input->username)->where("password",md5($input->password))->find_one();
//$app->setCookie('user_id',$user->id);
session_cache_limiter(false);
session_start();
$_SESSION['id'] = $user->id;
$status = 'success';
$message = 'Logged in successfully.';
}
else
{
$status = false;
$message = 'Could not log you in. Please try again.';
}
}
catch (Exception $e)
{
$status = 'danger';
$message = $e->getMessage();
}
$response = array(
'status' => $status,
'message' => $message
);
$app->response()->header("Content-Type", "application/json");
echo json_encode($response);
});
$app->get("/logout",function() use ($app)
{
try {
unset($_SESSION['id']);
session_destroy();
session_start();
//$app->getCookie('user_id');
$status = 'success';
$message = 'You have been logged out successfully';
}
catch (Exception $e)
{
$status = 'danger';
$message = $e->getMessage();
}
$response = array(
'status' => $status,
'message' => $message
);
$app->response()->header("Content-Type", "application/json");
echo json_encode($response);
});
It is returning 'Logged in successfully' but isn't actually logging me in so in my application when I check isset($_SESSION['id']) , there is nothing in the variable. Does anyone know whats going on? I am really confused because according to the slim documentation , it says :
The session cookie middleware will work seamlessly with the $_SESSION superglobal so you can easily migrate to this session
storage middleware with zero changes to your application code.
If you use the session cookie middleware, you DO NOT need to start a
native PHP session. The $_SESSION superglobal will still be available,
and it will be persisted into an HTTP cookie via the middleware layer
rather than with PHP’s native session management.
The issue would seem to be that you are not starting your session soon enough not anything with session middleware I would place session_start() at the top of the index
require 'lib/Slim/Slim.php';
use lib\Slim\Middleware\SessionCookie;
session_start();
Now it is started every time your application routes. So in login and logout remove your session_start() calls. Now in logout route redirect to your landing page or somewhere like:
$app->redirect('/yourawesomepage');
that recalls session_start() so you can remove that from here your logout route.

Understanding the Session Manager and Zend\Authenticate in ZF2

In the login action I'm having the following code:
public function login($sEmail, $sEncryptedPassword, $bIsClear = true)
{
$manager = $this->getServiceLocator()->get('session_manager');
$manager->start();
Container::setDefaultManager($manager);
$this->auth = new AuthenticationService();
$this->auth->setStorage(new Session('FSP'));
$dbAdapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
$this->authAdapter = new AuthAdapter(
$dbAdapter,
'fsp_user',
'email',
'password'
);
$this->authAdapter
->setIdentity($sEmail)
->setCredential($sEncryptedPassword);
$authAuthenticate = $this->auth->authenticate($this->authAdapter);
if ($authAuthenticate->isValid()) {
$user = $this->authAdapter->getResultRowObject();
$storage = $this->auth->getStorage();
$storage->write(
array(
'email' => $user->email,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'id' => $user->id
)
);
}
I have two problems with this code:
1) I'm saving the session in the database, and the session SaveHandler is configured in a service manager. I don't know if once I'm using Zend\Authenticate I should use the session manager too. In the documentation is saying that
"Unless specified otherwise, Zend\Authentication\AuthenticationService
uses a storage class named Zend\Authentication\Storage\Session, which,
in turn, uses Zend\Session."
So my first question is: can I configure the sessionHandler using just Zend\Authenticate or do I have to use the session manager?
2)I can't figured out how session storage is working in ZF. After login, the session data is not persisted in the DB. If I'm doing some debugging I get the following data:
$session = new Container("FSP");
//this returns the session data
var_dump($session->getIterator());
//this returns empty
var_dump($this->auth->getStorage());
//this returns null, but I do have a FSP named cookie with an Id, showing in Chrome's developer tool
$cookie = $this->getServiceLocator()->get('request')->getHeaders()->get('cookie');
$sessionId = $cookie->FSP;
var_dump($sessionId);
However, if I'm doing a refresh on the login (the login action is run again) the data from the previous session is wrote in the DB and not the data from the current one.
So the second question is, why the session data is not persisted in the database at login and at what step in the session instantiation process is the cookie with the session ID created?

Categories