I have an Android app in which I've implemented AWS Cognito. I'm hoping to use this as a means for controlling access to PHP scripts on my web root which connect to an RDS instance with a MySQL db. So far, I've set the registration process in my app to use a developer authenticated id to register the user in a cognito identity pool. Now, what I would like to do is have a method for checking whether the user trying to access the various scripts I've exposed in my web root is indeed a verified user. What I was thinking of doing is implementing a script like this:
use Aws\CognitoIdentity\CognitoIdentityClient;
$identityId = $_POST['identityId'];//sending cached identity id from client
$client = CognitoIdentityClient::factory ( array (
'profile' => 'profile',
'region' => 'region'
) );
$result = $client->lookupDeveloperIdentity(array(
'IdentityPoolId' => 'IdentityPoolId',
'IdentityId' => $identityId,
'MaxResults' => 1,
));
if ($result != null) {
//connect to db and do whatever operation/query needs to be done
}
However, checking this every time I need to make some kind of transaction on my db seems to be pretty inefficient and slow.
a) Am I using Cognito in the intended fashion?
b) If not, what is a better way of going about this?
Please let me know if I'm way off base here. Thanks!
Related
To be clear, We have created the EC2 policy, so my site can directly access the services like Parameter store, S3, Amazon SES etc.
As of now, all of my credentials are stored on AWS Parameter Store and then site is using those credentials i.e. DB credentials, diff. API keys etc. So only hard coded credentials are the one which fetch the parameters from Parameter Store. Now client want to remove those hard coded credentials as well, that's why we have created the EC2 Policy.
Till now, we have code like below to fetch the parameters:
$config = array(
'version' => 'latest',
'region' => '*****',
'credentials' => array(
'key' => '*******',
'secret' => '******',
)
);
$s3_instance = new \Aws\Ssm\SsmClient($config);
$result = $s3_instance->getParameters([
'Names' => $credential_group,
'WithDecryption' => true
]);
//converting S3 private data to array to read
$keys = $result->toArray();
var_dump($keys);
Now the question is what i have to change in above code, so it should work without passing those credentials.
Note: I am using AWS PHP library to perform above.
Update
Further reading the documentation, https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_credentials.html
Using Credentials from Environment Variables
If you don't provide credentials to a client object at the time of its instantiation, the SDK attempts to find credentials in your environment. The first place the SDK checks for credentials is in your environment variables. The SDK uses the getenv() function function to look for the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN environment variables. These credentials are referred to as environment credentials.
So after that, i have tried the below:
var_dump(getenv('AWS_ACCESS_KEY_ID'));
But it returns the bool(false). So does i need to manually setup those in environment credentials?
Which things i need to change in above code?
Update
Based on this doc: https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_configuration.html#credentials
I had made below change (Removed the credentials part from array):
$config = array(
'version' => 'latest',
'region' => '*****'
);
Then system throws the below warnings:
Warning: include(Test_Role_Cognitoaccess_from_instanceRole.php): failed to open stream
Warning: include(): Failed opening 'Test_Role_Cognitoaccess_from_instanceRole.php' for inclusion (include_path='.:/usr/share/pear:/usr/share/php')
Warning: include(Test_Role_Cognitoaccess_from_instanceRole.php): failed to open stream
Warning: include(): Failed opening 'Test_Role_Cognitoaccess_from_instanceRole.php' for inclusion (include_path='.:/usr/share/pear:/usr/share/php')
As you already mentioned that you attached the policy to EC2 IAM role to access other AWS services.
You should try to create a default credential provider, this will automatically pick keys from the role.
$provider = CredentialProvider::chain(CredentialProvider::env(), CredentialProvider::ini(), CredentialProvider::instanceProfile(), CredentialProvider::ecsCredentials());
When you pass credentials directly to SsmClient and same time you have defined a role to the EC2 machine then you are making confusion for the AWS. If you have defined the permission for the EC2 instance then just do as follow:
use Aws\Ssm\SsmClient;
$client = new SsmClient(['version' => 'latest', 'region' => 'ap-southeast-2']);
$result = $client->getParameters(['Names' => ['My-SECRATE-KEY'], 'WithDecryption' => true]);
print_r($result);
Please keep in mind that permissions take a little time to propagate and in this period you will get permission error for the specific user. If you wait and let the changes take effect then mentioned code will work without any error. In my case I attached AmazonSSMReadOnlyAccess to the EC2 role and to EC2 instance. If you key/value in Parameter store is not encrypted then you can remove 'WithDecryption' => true or change it to false.
I've created a rather simple multi-tenant application using separate schemas in a Postgresql database. I keep a public schema, which only a few model use and then the rest of my models use the tenant. I determine the Client from the subdomain through a middleware then, call the following function on my Client model to set the credentials for the connection.
public function logInAsClient()
{
$settings = $this->settings;
// set tenant db
config([
'client' => $settings,
'client.id' => $this->id,
'database.connections.tenant.schema' => $this->schema,
'schema' => $this->schema,
'domain' => $this->domain,
]);
DB::disconnect('tenant');
DB::reconnect('tenant');
return true;
}
It's working great in normal functions, but I have several tasks which require me to queue up hundreds of jobs at a time. Since they are all running on the same app, I pass the client_id to each job and then at the start of the handle run:
Client::find($this->client_id)->logInAsClient();
However, when running this code I get the following error:
PDOException: SQLSTATE[08006] [7] FATAL: sorry, too many clients already
FATAL: sorry, too many clients already
I don't there there is any other part of my app where I am connecting or reconnecting to databases so I'm not sure where else this issue might be coming from. If there are any hints on how to debug an issue like this it would be greatly appreciated.
I am also following what this guy's asking storage of client credential on OAuth2 server about oauth2-server-php-docs. Although it answered one thing for the client secret, I wanna ask about the users credentials. Check the following code sample from oauth2-server-php-docs:
// create some users in memory
$users = array('bshaffer' => array('password' => 'brent123', 'first_name' => 'Brent', 'last_name' => 'Shaffer'));
// create a storage object
$storage = new OAuth2\Storage\Memory(array('user_credentials' => $users));
// create the grant type
$grantType = new OAuth2\GrantType\UserCredentials($storage);
// add the grant type to your OAuth server
$server->addGrantType($grantType);
It says that the user credentials will be stored in memory and that it doesn't say anything about password encryption. This is my use case -- I already have user credentials stored in MySQL with password encrypted using PHP password_hash(). So how can I match or use the line $storage = new OAuth2\Storage\Memory() if the $user['bshaffer']['password'] is just a plain text?
I have this question too. But if you read the notes in the doc, just below the code snippet you mentioned, it says:
Note: User storage is highly customized for each application, so it is highly recommended you implement your own storage using OAuth2\Storage\UserCredentialsInterface
I think we can implement our own user storage for the users model with the “ OAuth2\Storage\UserCredentialsInterface” interface.
I did this by having my own network architecture and extending the Pdo storage object implementing user-defined password_verify(). So what I did with this one I created mytoken.php that receives the post from the front-end request then sends a request again to 127.0.0.1 to the token.php with the AUTH user and password (client_secret). I did this way so that client_secret is not sent from the front-end request.
I'm currently writing an app in PHP that connects to a MongoDB database.
I've managed to successfully authenticate a user with the dbAdmin role in the MongoDB Database that I've setup, and I've created a user "TestUser" with no roles that I want to get information about while authenticated as the dbAdmin User. however I can't figure out how to run the "db.getUser()" command from PHP.
My understanding is that I should be able to use the "MongoDB::command()" method to run any actual mongoDB commands directly, however, the following code...
$mongo = new MongoClient("localhost:27017");
$mongoDb = $mongo->{'test'}
$mongoDb->authenticate($username, $password); //This returns a successful response of array('ok' => 1)
print_r($mongoDb->command(array("getUser" => "TestUser")));
Generates this response: -
(
[ok] => 0
[errmsg] => no such command: getUser
[code] => 59
[bad cmd] => stdClass Object
(
[getUser] => TestUser
)
)
The "getUser" is a command of a Database in MongoDB though, right? If so, why am I getting this response, and how do I construct a correct statement to be able to get information about a User from MongoDB in PHP?
I looked into this more, by fetching a list of all possible methods available to the database by using...
$mongoDb->command(array("listCommands" => 1));
This informed me that "getUser" apparently is not an existing command. I'm not sure why not, I guess it's because it's a User Management command and not a typical Database Command.
At any rate, "usersInfo" is a command that is available. By doing the following...
$mongoDb->command(array("usersInfo" => array("user" => $username, "db" => $database)));
I was able to get the data about the user that I wanted, so I guess that answers my question. Hopefully this helps anyone else whose encountering the same problem trying to setup their PHP app with MongoDb.
In our intranet application(s) we use SSO (single sign on) login while the sessions both on client and auth origin applications are stored in memcached.
The sessions are set to live for 12h before the garbage collector may consider them as for removal. Both applications are written using ZF2.
Unfortunately, the problem is, that after certain period of time (I don't have the exact value) the browser loses the session which causes the redirection to auth origin, where the session is still alive thus user is redirected back to client and the browser session is refreshed. This is not a big deal if the user has no unsaved work as these two redirects happen within 1 second and user even may not notice them.
But it really is a big deal when user has unsaved work and even an attempt to save it leads to redirects and the work is gone.
Here is the configuration of session in Bootstrap.php:
class Module
{
public function onBootstrap(MvcEvent $e)
{
// ...
$serviceManager = $e->getApplication()->getServiceManager();
$sessionManager = $serviceManager->get('session_manager_memcached');
$sessionManager->start();
Container::setDefaultManager($sessionManager);
// ...
}
public function getServiceConfig()
{
return array(
'factories' => array(
// ...
'session_manager_memcached' => function ($sm) {
$systemConfig = $sm->get('config');
$config = new SessionConfig;
$config->setOptions(array(
'phpSaveHandler' => 'memcache',
'savePath' => 'tcp://localhost:11211?timeout=1&retry_interval=15&persistent=1',
'cookie_httponly' => true,
'use_only_cookies' => true,
'cookie_lifetime' => 0,
'gc_maxlifetime' => 43200, // 12h
'remember_me_seconds' => 43200 // 12h
));
return new SessionManager($config);
},
// ...
);
}
}
The authentication service is defined as
'authService' => function ($sm) {
$authService = new \Zend\Authentication\AuthenticationService;
$authService->setStorage(new \Zend\Authentication\Storage\Session('user_login'));
return $authService;
},
the session storage uses the same memcached session manager.
Then anywhere within the application a session value needs to be retrieved or set I just use a \Zend\Session\Container like this:
$sessionContainer = new \Zend\Session\Container('ClientXYZ');
$sessionContainer['key1'] = $val1;
// or
$val2 = $sessionContainer['key2'];
The SSO is requested for the active session at any action using the token from session which contains PHPSESSID from the auth origin. It's quite complicated to describe here within this question.
Additionally an authentication service stores a user identity (with roles for ACL) also in memcached session - using the same settings. Obviously this is now the place which causes confusion. Apparently the session storage of authentication service times out prematurely causing the ACL to retrieve no user identity to check leading into SSO logout sequence (but because user didn't really log out, SSO redirects the user back as described above).
I'm not sure how much code should I (and can I) share here, maybe you'll lead me to the solution straight away or just by asking me some questions. I am quite helpless right now after many hours of debugging and trying to identify the problem.
Somewhere I have read that memcached wipes out the memory once the session cookie gets 1MB in size - may this be the case? For the user identity we save just general user information and array of roles, I'd guess this could be max. up to few kb in size...
EDIT 1: To dismiss all guesses and to save your time, here few facts (to keep an eye on):
only memcached is used
cookies serve only to transport the PHPSESSID between the browser and server and it's value is the key for memory chunk in memcached where the data is stored
client and SSO auth apps are running on one server (be it integration, staging or live environment, still just one server)
session on client app goes off randomly causing it to redirect to SSO auth app, but here the session is still alive thus user is redirected back to client app which gets new session and user stays logged in
this should dismiss discussion about memcached being wiped off or restarted
also observation on telneted memcached directly shows both data chunks (for client and auth apps) are established almost at the same time with the same ttl
I am going to implement some dies in PHP and returns in JS parts to catch the moment when the session is considered gone and further inspect the browser cookie, memcached data, etc. and will update you (unless somebody comes with explanation and solution).
public function initSession()
{
$sessionConfig = new SessionConfig();
$sessionConfig->setOptions([
'cookie_lifetime' => 7200, //2hrs
'remember_me_seconds' => 7200, //2hrs This is also set in the login controller
'use_cookies' => true,
'cache_expire' => 180, //3hrs
'cookie_path' => "/",
'cookie_secure' => Functions::isSSL(),
'cookie_httponly' => true,
'name' => 'cookie name',
]);
$sessionManager = new SessionManager($sessionConfig);
// $memCached = new StorageFactory::factory(array(
// 'adapter' => array(
// 'name' =>'memcached',
// 'lifetime' => 7200,
// 'options' => array(
// 'servers' => array(
// array(
// '127.0.0.1',11211
// ),
// ),
// 'namespace' => 'MYMEMCACHEDNAMESPACE',
// 'liboptions' => array(
// 'COMPRESSION' => true,
// 'binary_protocol' => true,
// 'no_block' => true,
// 'connect_timeout' => 100
// )
// ),
// ),
// ));
// $saveHandler = new Cache($memCached);
// $sessionManager->setSaveHandler($saveHandler);
$sessionManager->start();
return Container::setDefaultManager($sessionManager);
}
This is the function I use in order to create a cookie for X user. The cookie lives for 3 hours, no matter if there are redirects or if the user has closed the browser. It's still there. Just call this function in your onBootstrap() method from Module.php.
While logging, I use The ZF2 AuthenticationService and the Container to store and retrieve the user data.
I suggest you to install these module for easier debugging.
https://github.com/zendframework/ZendDeveloperTools
https://github.com/samsonasik/SanSessionToolbar/
Memcached & gc_maxlifetime
When using memcached as session.save_handler, garbage collection of session will not be done.
Because Memcached works with a TTL (time to live) value, garbage collection isn't needed. An entry that has not lived long enough to reach the TTL age will be considered "fresh" and will be used. After that it will be considered "stale" and will not be used any longer. Eventually Memcached will free the memory used by the entry, but this has nothing to do with session garbage collection of PHP.
In fact, the only session.gc_ setting that's actually used in this case is session.gc_maxlifetime, which will be passed as TTL to Memcached.
In short: garbage collection is not an issue in your case.
Memcached & Cronjobs
As you are using Memcached as storage for your sessions, any cronjobs provided by the OS that will manually clean session folders on disk (like Ubuntu does) will have no effect. Memcached is memory storage, not disk storage.
In short: cronjobs like this are not an issue in your case.
Issue of app, not SSO
You state that the SSO server/authority is on the same machine as the SSO client (the application itself), is using the same webserver / PHP configuration, and is using the same instance of Memcached.
This leads me to believe we have to search in how session management is done in the application, as that is the only difference between the SSO authority and client. In other words: we need to dive into Zend\Session.
Disclaimer: I've professionally worked on several Zend Framework 1 applications, but not on any Zend Framework 2 applications. So I'm flying blind here :)
Configuration
One thing I notice in your configuration is that you've set cookie_lifetime to 0. This actually means "until the browser closes". This doesn't really make sense together with remember_me_seconds set to 12 hours, because a lot of people will have closed their browser before that time.
I suggest you set cookie_lifetime to 12 hours as well.
Also note that remember_me_seconds is only used when the Remember Me functionality is actually used. In other words: if Zend\Session\SessionManager::rememberMe() is called.
Alternative implementation
Looking at the way you've implemented using Memcached as session storage, and what I can find on the subject, I'd say you've done something different than what seems to be "the preferred way".
Most resources on this subject advise to use Zend\Session\SaveHandler\Cache (doc, api) as save-handler, which gives you the ability to use Zend\Cache\Storage\Adapter\Memcached (doc, api). This gives you much more control over what's going on, because it doesn't rely on the limited memcached session-save-handler.
I suggest you try this implementation. If it won't immediately resolve your issue, there are at least a lot more resources to find on the subject. Your chances of finding a solution will be better IMHO.
This answer might not immediately address the cause of your memcache issue, but because of the unreliable nature of memcache I would suggest to make a backup of your memcached data in some persistent storage.
Memcaching your data will help you to improve performance of your application but it is not fail-safe.
Maybe you can make a fallback (persistent) storage in your AuthenticationService instance. Then first you try to get your authentication data from your memcache and if nothing is found you check if there is something available in your persistent storage.
This will at least solve all issues with unexpected memcache loss issues.