Zend Framework 2 email token verification - php

I am trying to make a system (in Zend Framework 2) to validate a user registration's email by sending an email with a link with a token (for example: http://example.com/user/autenticate/verify/abG12Fdss67j3kgfdds4jdpa74FiP9) so if the token is found in the database the pre-registered account moves to VERIFIED status.
I am using a route in module.config.php like this:
'verify' => array(
'type' => 'Segment',
'options' => array(
'route' => '/user/autenticate/verify/:token',
'defaults' => array(
'__NAMESPACE__' => 'User\Controller',
'controller' => 'Autenticate',
'action' => 'verify',
),
'constraints' => array(
'token' => '[a-zA-Z0-9]{30}'
),
),
),
then in AutenticateController.php, the following action method:
public function verifyAction()
{
sleep(3); // Delay against brute attack (is it useful?)
$token = $this->params()->fromRoute('token');
$registerverification = new RegisterVerification();
try {
$registerverification = $this->getRegisterVerificationTable()->getRegisterVerification($token);
// If arrives here (no exception) means that the token was in the database
$aux = $this->getRegisterVerificationTable()->deleteRegisterVerification($token);
$user = new User();
$user = $this->getUserTable()->getUser((int)$registerverification->id);
$user->verified = date("Y-m-d H:i:s");
$this->getUserTable()->saveUser($user);
$this->flashMessenger()->addMessage("Now your account is active");
} catch (\Exception $e) { // Could not find row: $token
$this->flashMessenger()->addMessage($e->getMessage());
}
return array();
}
And a verify.phtml like this:
<?php
echo $this->flashMessenger()->render();
?>
This is working, but not on the first attempt, but only after refreshing the URL (http://example.com/user/autenticate/verify/abG12Fdss67j3kgfdds4jdpa74FiP9).
Can anyone help me on what do I have to do to make the method verifyAction() of AutenticateController.php being executed the first time the URL is called?

The flash messenger is designed to show messages on the next request, so you probably want to redirect to another URL after adding your success message. That may be the only issue (otherwise please let us know what happens on the first request).

Related

Silex / Symfony programmatically login

I am using the Silex / Symfony security service and try to implement a automatic login when the specific parameters are passed in the request query.
I've looked into the modules and also search on the internet for a solution and always found something like the following:
$user = (new \Portal\UserProvider($app['databases']['read']))->loadUserByUsername($subscriber_id);
$token = new \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken($user, $user->getPassword(), 'secured', $user->getRoles());
$app['security.token_storage']->setToken($token);
Unfortunately, this does not work for my app. I don't know whats wrong but the security module keeps redirecting me to /login/ as specified in the registration process:
/**
* Registers the security firewall.
*/
private function registerSecurity()
{
$this->register(new \Silex\Provider\SecurityServiceProvider(), array(
'security.firewalls' => array(
'login' => array(
'pattern' => '^/(login/|terms|imprint|animation|error)',
),
'secured' => array(
'pattern' => '^/',
'form' => array(
'login_path' => '/login/',
'check_path' => '/login_check'
),
'logout' => array(
'logout_path' => '/logout'
),
'users' => $this->share(function () {
return new \Portal\UserProvider($this['databases']['read']);
}),
),
'unsecured' => array(
'anonymous' => true
),
),
'security.encoder.digest' => $this->share(function () {
return new \Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder('sha1', false, 1);
}),
'security.access_rules' => array(
array('^/login', 'ROLE_GUEST'),
),
'security.role_hierarchy' => $this->share(function () {
return array();
})
));
$this->boot();
}
Is there anything I have to consider about
reloading
order of registering the SecurityServiceProvider, SessionServiceProvider
this manual token setting
?
You're using the 'form' authentication provider, but this won't work (or maybe I'm not understanding you correctly?). In order to be able to:
try to implement a automatic login when the specific parameters are passed in the request query
You need to hook into the Security service. In order to do that you have to create a Listener and register it. You'll also need a Provider
This is not an easy path as the security component works with many concepts.
You can see a working example in this repo (which implements an OAuth service)
If your security flow is easy and you don't need roles, you can just use a before middleware (and forget about the security component) like so:
<?php
$app->before(function (Request $request, Application $app) {
$session = $request->getSession();
if (false === $session->get('logged', false)) {
if (null !== $request->get('blah', null)) {
$session->set('logged', true);
}
else {
return new RedirectResponse('/login-url');
}
}
});
You could use Silex's guard. It works well with get Request. And standard form could be use as complement.
In your secured Security.firwall, add the guard parameter :
"guard" => array ("authenticator" => array("app.myauthenticator") )
And create your custom class, to validate login.
Just read the Silex cookbook.

allow a controller action without authentication in zend 2

I am new to Zend & working on Zend2, I have cron job functionality to make some automatic notifications. For this the functionality is ready & it was set up in Cron (Linux server).
Now when ever a call is made to these functions they are getting redirected to Login action. Now I should allow these specific notification functions to get rid of this authentication process.
In cakephp we have $this->Auth->allow('index') which allows to work without login action. Is there a way to do similar to this in zend 2?
I've a link similar to this. But it doesn't say where to mention the action name in the ACL
This is the way I do it, using custom acl management code:
In my User module, I place the Acl config to manage the access to the resources for 3 differents roles: guest, member, admin.
The module.config.php has the following attribute "acl":
'acl' => array(
'role' => array(
'guest' => null,
'member' => array('guest'),
'admin' => null,
),
// List of modules to apply the ACL. This is how we can specify if we have to protect the pages in our current module.
'modules' => array(
'User',
'Application'
),
'resource_aliases' => array(
'User\Controller\Account' => 'account',
...
),
'resource' => array(
// resource -> single parent
'account' => null,
'log' => null
...
),
'allow' => array(
array('guest', 'log', array('in', 'out')),
array('guest', 'account', array('register', 'verify', 'recovery', 'verificationprogress')),
...
array('admin', null, null), // the admin can do anything with the accounts
),
'deny' => array(
),
'defaults' => array(
'guest_role' => 'guest',
'member_role' => 'member',
'admin_role' => 'admin',
),
)
In the onBootstrap method of my Module.php:
...
$eventManager = $event->getApplication()->getEventManager();
$eventManager->attach(MvcEvent::EVENT_ROUTE, array($this, 'protectPage'), -100);
...
The protectPage function looks like this:
public function protectPage(MvcEvent $event) {
$match = $event->getRouteMatch();
if (!$match) {
//onDispatchError do the job
}
$controller = $match->getParam('controller');
$action = $match->getParam('action');
$namespace = $match->getParam('__NAMESPACE__');
$parts = explode('\\', $namespace);
$moduleNamespace = $parts[0];
$services = $event->getApplication()->getServiceManager();
$config = $services->get('config');
// check if the current module wants to use the ACL
$aclModules = $config['acl']['modules'];
if (!empty($aclModules) && !in_array($moduleNamespace, $aclModules)) {
return;
}
$auth = $services->get('auth');
$acl = $services->get('acl');
// get the role of the current user
$session = new Container("appData");
$role = "guest";
if (isset($session->user->role))
$role = $session->user->role;
// Get the short name of the controller and use it as resource name
// Example: User\Controller\Course -> course
$resourceAliases = $config['acl']['resource_aliases'];
if (isset($resourceAliases[$controller])) {
$resource = $resourceAliases[$controller];
} else {
$resource = strtolower(substr($controller, strrpos($controller, '\\') + 1));
}
// If a resource is not in the ACL add it
if (!$acl->hasResource($resource)) {
$acl->addResource($resource);
}
try {
//if the role is allow to pass
if ($acl->isAllowed($role, $resource, $action)) {
//do whatever you need since the use is allowed to access this resource
}else{
//send the user to log/in resource
}
} catch (AclException $ex) {
// #todo: log in the warning log the missing resource
}
}
I hope it helps.

Is it possible to add a subdomain to Route53 using the AWS PHP SDK?

I am working on a project where we will be creating both subdomains as well as domains in Route53. We are hoping that there is a way to do this programmatically. The SDK for PHP documentation seems a little light, but it appears that createHostedZone can be used to create a domain or subdomain record and that changeResourceRecordSets can be used to create the DNS records necessary. Does anyone have examples of how to actually accomplish this?
Yes, this is possible using the changeResourceRecordSets call, as you already indicated. But it is a bit clumsy since you have to structure it like a batch even if you're changing/creating only one record, and even creations are changes. Here is a full example, without a credentials method:
<?php
// Include the SDK using the Composer autoloader
require 'vendor/autoload.php';
use Aws\Route53\Route53Client;
use Aws\Common\Credentials\Credentials;
$client = Route53Client::factory(array(
'credentials' => $credentials
));
$result = $client->changeResourceRecordSets(array(
// HostedZoneId is required
'HostedZoneId' => 'Z2ABCD1234EFGH',
// ChangeBatch is required
'ChangeBatch' => array(
'Comment' => 'string',
// Changes is required
'Changes' => array(
array(
// Action is required
'Action' => 'CREATE',
// ResourceRecordSet is required
'ResourceRecordSet' => array(
// Name is required
'Name' => 'myserver.mydomain.com.',
// Type is required
'Type' => 'A',
'TTL' => 600,
'ResourceRecords' => array(
array(
// Value is required
'Value' => '12.34.56.78',
),
),
),
),
),
),
));
The documentation of this method can be found here. You'll want to take very careful note of the required fields as well as the possible values for others. For instance, the name field must be a FQDN ending with a dot (.).
Also worth noting: You get no response back from the API after this call by default, i.e. there is no confirmation or transaction id. (Though it definitely gives errors back if something is wrong.) So that means that if you want your code to be bulletproof, you should write a Guzzle response handler AND you may want to wait a few seconds and then run a check that the new/changed record indeed exists.
Hope this helps!
Yes, I done using changeResourceRecordSets method.
<?php
require 'vendor/autoload.php';
use Aws\Route53\Route53Client;
use Aws\Exception\CredentialsException;
use Aws\Route53\Exception\Route53Exception;
//To build connection
try {
$client = Route53Client::factory(array(
'region' => 'string', //eg . us-east-1
'version' => 'date', // eg. latest or 2013-04-01
'credentials' => [
'key' => 'XXXXXXXXXXXXXXXXXXX', // eg. VSDFAJH6KXE7TXXXXXXXXXX
'secret' => 'XXXXXXXXXXXXXXXXXXXXXXX', //eg. XYZrnl/ejPEKyiME4dff45Pds54dfgr5XXXXXX
]
));
} catch (Exception $e) {
echo $e->getMessage();
}
/* Create sub domain */
try {
$dns = 'yourdomainname.com';
$HostedZoneId = 'XXXXXXXXXXXX'; // eg. A4Z9SD7DRE84I ( like 13 digit )
$name = 'test.yourdomainname.com.'; //eg. subdomain name you want to create
$ip = 'XX.XXXX.XX.XXX'; // aws domain Server ip address
$ttl = 300;
$recordType = 'CNAME';
$ResourceRecordsValue = array('Value' => $ip);
$client->changeResourceRecordSets([
'ChangeBatch' => [
'Changes' => [
[
'Action' => 'CREATE',
"ResourceRecordSet" => [
'Name' => $name,
'Type' => $recordType,
'TTL' => $ttl,
'ResourceRecords' => [
$ResourceRecordsValue
]
]
]
]
],
'HostedZoneId' => $HostedZoneId
]);
}
If you get any error please check into server error.log file. If you get error from SDK library then there is might PHP version not supported.
if you run this code from your local machine then you might get "SignatureDoesNotMatch" error then Make sure run this code into same (AWS)server environment.

How to get Response from Facebook PHP SDK after Succesful login

I am using CakePHP 2.4 and Facebook PHP SDK 3.2.3 to create login form, which has two options: local login and login with facebook. I have been referring to How do I integrate Facebook SDK login with cakephp 2.x? on how to create login actions and view for Facebook login.
Now I have working local login, and when I press to facebook login, I am redirected to Facebook and back again to my login action of plugin Users. However I get no response in $_GET['code'] or $this->request->query['code'].
This is my login action:
public function login() {
if ($this->Auth->user()) {
$this->Session->setFlash(__('You are already registered and logged in!'), 'flash_info');
$this->redirect('/');
}
if ($this->request->is('post')) {
if ($_SERVER['HTTP_HOST'] == Configure::read('webakis.touchscreen_host')) {
$this->request->data['User']['username'] = $this->request->data['User']['name'] . ' ' . $this->request->data['User']['surname'];
}
if ($this->Auth->login()) {
$this->User->id = $this->Auth->user('id');
$this->User->saveField('last_login', date('Y-m-d H:i:s'));
if ($this->here == $this->Auth->loginRedirect) {
$this->Auth->loginRedirect = '/';
}
$this->Session->setFlash(sprintf(__('%s you have successfully logged in'), $this->Auth->user('name')), 'flash_success');
if (!empty($this->request->data)) {
$data = $this->request->data[$this->modelClass];
$this->_setCookie(array('name' => 'AKIS'), 'User');
}
if (empty($data['return_to'])) {
$data['return_to'] = null;
}
$this->redirect($this->Auth->redirect($data['return_to']));
} else {
$this->Session->setFlash(__('Invalid e-mail / password combination. Please try again'), 'flash_error');
}
}
// When facebook login is used, facebook always returns $_GET['code'].
if($this->request->query('code')){ //Execution doesn't get in here
Debugger::log($this->request->query);
// User login successful
$fb_user = $this->Facebook->getUser(); # Returns facebook user_id
if ($fb_user){
Debugger::log($fb_user);
$fb_user = $this->Facebook->api('/me'); # Returns user information
// We will varify if a local user exists first
$local_user = $this->User->find('first', array(
'conditions' => array('email' => $fb_user['email'])
));
// If exists, we will log them in
if ($local_user){
$this->Auth->login($local_user['User']); # Manual Login
$this->redirect($this->Auth->redirectUrl());
}
// Otherwise we ll add a new user (Registration)
else {
$data['User'] = array(
'username' => $fb_user['email'], # Normally Unique
'password' => AuthComponent::password(uniqid(md5(mt_rand()))), # Set random password
'role' => 'registered'
);
// You should change this part to include data validation
$this->User->save($data, array('validate' => false));
$this->redirect('/');
// After registration we will redirect them back here so they will be logged in
//$this->redirect(Router::url('/users/login?code=true', true));
}
}
}
if (isset($this->request->params['named']['return_to'])) {
$this->set('return_to', urldecode($this->request->params['named']['return_to']));
} else {
$this->set('return_to', false);
}
$allowRegistration = Configure::read('Users.allowRegistration');
$this->set('allowRegistration', (is_null($allowRegistration) ? true : $allowRegistration));
Configure::write('keyboard', 1);
}
Without getting response here, I can't handle my facebook login logic.
These are responses I get then facebook redirects me back to login page:
Two interesting requests (with Url params and location shown):
GET oauth?....
client_id=671394586273323
ext=1406709978
hash=AeYXzQgAjCbRdY4g
redirect_uri=http://127.0.0.1.xip.io/users/users/login
ret=login
sdk=php-sdk-3.2.3
state=9d030270aa50f2e52ac4aa66a37cd0fd
Location: http://127.0.0.1.xip.io/users/users/login?code=AQC_UtjKJU8a2leJMFAB4_1qx1mh1ww0-sRWdAD5vCocfKuZPTF4iSdKYwqxQUsm9N-_1tSPGfh3LYQXbXjYeY2onVBD6gTvJ5amvRZm5ZjI1OSYoLkqgjBsdfjWSdXTigCIQLf5d180keXTCf5jRiOXi8pWi0V2UxXqVl4K9QWWq2qGfSGuXlJMr32NZqKYR0Z93LyR1EiRFJPohfo6-j0kZJrNTkljCbY16Nrq1InqQLdYwGCOSg4IrbR0auaMIWTlnUCKFCr4DT3If_5HPFEDM6ZigeUvURM-q8y-CxDrRIctSmT4Bz1UevPqR-hOMbgKGzYUplatRywzjq_-R7bt&state=9d030270aa50f2e52ac4aa66a37cd0fd#_=_
GET login?code....
code=AQC_UtjKJU8a2leJMFAB4_1qx1mh1ww0-sRWdAD5vCocfKuZPTF4iSdKYwqxQUsm9N-_1tSPGfh3LYQXbXjYeY2onVBD6gTvJ5amvRZm5ZjI1OSYoLkqgjBsdfjWSdXTigCIQLf5d180keXTCf5jRiOXi8pWi0V2UxXqVl4K9QWWq2qGfSGuXlJMr32NZqKYR0Z93LyR1EiRFJPohfo6-j0kZJrNTkljCbY16Nrq1InqQLdYwGCOSg4IrbR0auaMIWTlnUCKFCr4DT3If_5HPFEDM6ZigeUvURM-q8y-CxDrRIctSmT4Bz1UevPqR-hOMbgKGzYUplatRywzjq_-R7bt
state=9d030270aa50f2e52ac4aa66a37cd0fd
Location: http://127.0.0.1.xip.io/eng/login
I find it strange with code is not URL parameter in first request, but it is in URL, second response shows inverse situation.
OK, so this might be routing issue.
$this->set('fb_login_url', $this->Facebook->getLoginUrl(array('redirect_uri' => Router::url(array('plugin'=> 'users', 'controller' => 'users', 'action' => 'login'), true))));
Router::url() returns: http://127.0.0.1.xip.io/users/users/login
My Router connects:
Router::connect('/:language/login', array('plugin' => 'users', 'controller' => 'users', 'action' => 'login'), array('language' => '[a-z]{3}'));
Router::connect('/:language/logout', array('plugin' => 'users', 'controller' => 'users', 'action' => 'logout'), array('language' => '[a-z]{3}'));
Router::connect('/:language/register', array('plugin' => 'users', 'controller' => 'users', 'action' => 'add'), array('language' => '[a-z]{3}'));
Router::connect('/:language/users', array('plugin' => 'users', 'controller' => 'users'), array('language' => '[a-z]{3}'));
Router::connect('/:language/users/index/*', array('plugin' => 'users', 'controller' => 'users'), array('language' => '[a-z]{3}'));
Router::connect('/:language/users/:action/*', array('plugin' => 'users', 'controller' => 'users'), array('language' => '[a-z]{3}'));
Router::connect('/:language/users/users/:action/*', array('plugin' => 'users', 'controller' => 'users'), array('language' => '[a-z]{3}'));
Al these urls redirects to: http://127.0.0.1.xip.io/eng/login . This might be problem, as Facebook SDK have different redirect URL, so then it redirects to http://127.0.0.1.xip.io/users/users/login , there is also request to redirect to http://127.0.0.1.xip.io/eng/login. The query parameter 'code' may get lost after this redirection.
As can be seen in the network console and the from the headers that you've posted, your server is doing an additional redirect, and as already mentioned in my comment this is where the query parameters are being lost.
It looks like the redirect URL that you are passing to Facebook is wrong, it is
http://127.0.0.1.xip.io/users/users/login
which contains most likely at least a surplus users (not sure where this stems from, you'll have to figure that on your own) unless you are using a plugin called Users which contains a controller named UsersController, so maybe it should be more like
http://127.0.0.1.xip.io/users/login
However, considering that your server redirects to /eng/login when accessing /users/users/login, it may be that the redirect URL passed to Facebook is wrong altogether.
So first you'll have to figure out the actual proper URL of your login, and then make sure that Router::url() (assuming you are using the code from the linked question) actually generates that exact URL.
Update
From looking at your updated question, I don't see why any of these routes should cause a redirect, they aren't redirect routes, but just routes. The actual redirect will stem from somewhere else.
In case the redirect to /eng/login is acutally indended, then you'll have to make sure that you pass that URL as the redirect URL to Facebook, that is properly invoke Router::url() with the language key set so that it will match your first route
Router::url(
array(
'plugin' => 'users',
'controller' => 'users',
'action' => 'login',
'language' => 'eng'
),
true
);
or use the persist option in your routes so that a call to Router::url() without the language key will match your route in case the current URL contains the language element, something along the lines of
Router::connect(
'/:language/login',
array('plugin' => 'users', 'controller' => 'users', 'action' => 'login'),
array('language' => '[a-z]{3}', 'persist' => array('language'))
);
// ...
// connect all default routes with a persistent language prefix
Router::connect(
'/:language/:controller/',
array('action' => 'index'),
array('language' => '[a-z]{3}', 'persist' => array('language'))
);
Router::connect(
'/:language/:controller/:action/*',
array(),
array('language' => '[a-z]{3}', 'persist' => array('language'))
);
or, as a last option, include the query string in the redirect to /eng/login
array(/* other route params */, '?' => $this->request->query)
Due to Router issues I changed redirect uri parameter in $this->Facebook->getLoginUrl() to 'redirect_uri' => Router::url('/', true).Configure::read('Config.language').'/login' , as it resolves to http://127.0.0.1.xip.io/eng/login .
Now I get $this->request->query('code') param, so my code should work for now.

How to use Auth in cakephp using mongodb

I have a mongo db structure for users with "username" and "password". I am trying to use the Auth in cakephp login but it seems like its not working for me. I tried removing the $this->data but still it did not work.
My password is hashed using Security::hash($this->data['User']['password'])
if(!empty($this->data))
{
if($this->Auth->login($this->data))
{
echo "yes";
}
else{
echo "no";
}
}
In my app controller I have this:
public $components = array('DebugKit.Toolbar', 'Session', 'Auth' => array(
'loginAction' => array(
'controller' => 'pages',
'action' => 'home'
),
'authenticate' => array(
'Form' => array(
'fields' => array('username' => 'username', 'password' => 'password')
)
)
));
Here is the result when I debug the login method:
array(
'User' => array(
'password' => '*****',
'username' => 'test#test.com',
'remember' => '0',
'auto_login' => '0'
)
)
I don't know why I cannot use Auth with mongodb. Thanks for the help in advance.
EDIT:
When i tried and take away the layout, it shows me a query at the bottom of the page saying:
db.users.find( {"username":"test#test.com","password":"2fdf49ffc396453960802df8fc2417655d1e8fca"}, [] ).sort( [] ).limit( 1 ).skip( 0 )
The hashed value of the password that I inputted from the form is different from the hash value that is being queried. The hashed value should be "a2374c309ab7823dcd9b4e21dae7511f7a9c7ec5". Why is it that cakephp is converting the password into another hash value?
There are two ways of using $this->Auth->login(). The CakePHP API documentation explains it:
If a $user is provided that data will be stored as the logged in user. If $user is empty or not specified, the request will be used to identify a user.
The manual also mentions:
In 2.0 $this->Auth->login($this->request->data) will log the user in with whatever data is posted ...
So for the login method of the users controller you shouldn't pass anything:
if($this->Auth->login()) {
// user is now logged in
}
Should you need to manually login a user you can pass the user data as an array:
if($this->Auth->login($this->request->data['User'])) {
// user is now logged in
}
Where $this->request->data['User'] is something like:
array(
'id' => 1,
'username' => 'admin',
'password' => '1234',
);
Note: In both cases you don't need to hash the password as it is done automatically.
I was able to find out the answer. Its because cakephp is automatically hashing the password when searching in the database.
The problem that I had was when I was saving the users' password, I am was using
Security::hash($this->data['User']['password'])
I should have used this one instead:
AuthComponent::password($this->data['User']['password'])
Thank you for all the help especially to #xgalvin

Categories