In cakephp 2.5.4, with $this->Auth->user() I can access to user object
I want when user logged in, set some user detail in user object and with $this->Auth->user() function, get all info in all controller
$user = $this->Auth->user();
$this->loadModel('Resource');
$resource = $this->Resource->find('threaded', array('order' => 'index'));
// here, i want add $resource to user object
How can I do this?
Try this, untested but should work.
$this->Session->write($this->Auth->sessionKey, array_merge(
$resource,
$this->Auth->user()
));
It is unlikely that the session key changes in most apps but it can happen. I don't consider it as good practice to hardcode the key everywhere like most people do.
Also keep in mind that this won't work with state less authentication systems!
You can also contain additional data when configuring the Auth adapters. Read this section of the manual. Taken from the manual and added the contain:
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form' => array(
'fields' => array('username' => 'email'),
'contain' => array('Foo', 'Bar')
)
)
)
);
Related
I'm using this JWTAuth adapter to use JWT authentication instead of cookie-based auth in my CakePHP 2.8 app. It works great, except for one hitch:
Normally for one of my REST endpoints, I can use $this->Auth->user("id") to get the currently logged-in users' ID.
When I try to make a controller action accessible to non-members using $this->Auth->allow(), a problem occurs. If I do this, using $this->Auth->loggedIn() in the controller returns false, meaning I can not add additional logic for logged-in users.
When using standard cookie auth:
$this->Auth->user('id') is available in
Controller::beforeFilter().
$this->Auth->loggedIn() is true in
Controller::beforeFilter().
$this->Auth->user('id') is available in controller actions, public
and members-only.
$this->Auth->loggedIn() is true in controller actions, public
and members-only.
When using JWT auth:
$this->Auth->user('id') is null in Controller::beforeFilter().
$this->Auth->loggedIn() is false in
Controller::beforeFilter().
$this->Auth->user('id') is available in members-only controller actions, and null in public controller actions.
$this->Auth->loggedIn() is true in members-only controller actions, and false in public controller actions.
Is there any way I can get Auth to include information returned by the JWTAuth component on actions that have been made public by $this->Auth->allow()?
Example controller here:
public function visible(){
// This will always be false, even if a valid JWT token is sent
$this->set("loggedIn", $this->Auth->loggedIn());
}
public function members_only(){
// This will be unavailable if not logged in, and a true if logged in
$this->set("loggedIn", $this->Auth->loggedIn());
}
public function beforeFilter($options = array()){
parent::beforeFilter();
$this->Auth->allow("visible");
}
And for reference, my AppController::components array;
public $components = array(
'DebugKit.Toolbar',
'Auth' => array(
'authorize' => array(
'Actions' => array(
'actionPath' => 'controllers'
),
),
'authenticate' => array(
'Form' => array(
'fields' => array('username' => 'email'),
'contain' => array(
'UserProfile',
)
),
'JwtAuth.JwtToken' => array(
'fields' => array(
'username' => 'email',
'token' => 'password',
),
'header' => 'AuthToken',
'userModel' => 'User',
),
),
'unauthorizedRedirect' => false
),
"Acl",
"RequestHandler",
"Session"
);
For stateless adapters authentication process is triggerd in AuthComponent::startup(). Component's startup() methods are run after Controller::beforeFilter(), that is why AuthComponent::user() doesn't return any info.
For other adapters when user is authenticated the identity info is stored in session. Getting that info doesn't require any authentication process which is why AuthComponent::user() give you the user info in case of standard cookie based auth.
I'm a bit late, but I found a solution to this problem, but (warning!) it involved updating the code for AuthComponent.
I took a copy of Lib/Controller/Component/AuthComponent.php and placed it under app/Controller/Component/AuthComponent.php.
I then added one line to the file:
public function startup(Controller $controller) {
$methods = array_flip(array_map('strtolower', $controller->methods));
$action = strtolower($controller->request->params['action']);
// One-line modification from the ordinary CakePHP file.
// This lets us have info on the logged-in user in public actions when using stateless auth.
$this->_getUser();
Voila, you can now access user info on the server while accessing a non-protected controller function with stateless auth!
I'm new to Silex and Symfony and decided to use Silex to build a (really) small app which uses an authentication. The auth process is working. I'm currently working on a little setup page which allows the user to define his username and password. The auth looks like this:
$app['security.firewalls'] = array(
'admin' => array(
'pattern' => '/private',
'form' => array('login_path' => '/login', 'check_path' => '/private/login_check'),
'logout' => array('logout_path' => '/private/logout', 'invalidate_session' => true),
'users' => array(
$username => array('ROLE_ADMIN', $passwd)
)
)
);
$app->register(new Silex\Provider\SecurityServiceProvider());
No other type of user is defined appart from admin. The setup is meant to be run once by an admin.
I've tried customizing the username and the password the following ways:
First thing (this is common to everything I tried)):
// Default password and username
$username='admin'; $passwd='5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==';`
The following didn't work (no magical update):
`$app->post('/setup', function(Request $request) use ($app, $username, $passwd) {
$username = $request->get('username');
$passwd= $request->get('passwd');
});
so I tried something like:
$app->post('/setup', function(Request $request) use ($app) {
$username = $request->get('username');
$passwd= $request->get('passwd');
$app['security.firewalls']['admin']['users'] = array($username => array('ROLE_ADMIN', $passwd));
$app->register(new Silex\Provider\SecurityServiceProvider());
});
with and without registering a new SecurityServiceProvider, and redifining the "users" part of the array. Merging didn't help either.
I'm a bit out of idea here :S How can I perform that?
Dynamic Way
You are not saving your User anywhere. I assume you define $username and $passwd before you define $app['security.firewalls'], otherwise that should already throw an error.
What you need is an User Provider. Right now you are not saving the new user(s), and you are trying to just re-define your two static variables - without checking the given values if I may add - and that is wrong in multiple ways :)
Either you dive in a little bit and check out how to Define a Custom User Provider in Silex and if you have special requirements to an User-Object also probably how to create an User-Entity, or you use some already written user provider for the Silex security service, like:
Silex-SimpleUser by Jason Grimes
Static Way
If you don't want to dynamically add/edit/delete users or you just have a handful of users, you can always just add your static users to the users array:
'users' => array(
'admin1' => array('ROLE_ADMIN', 'encodedAndSaltedPW1'),
'admin2' => array('ROLE_ADMIN', 'encodedAndSaltedPW2'),
'admin3' => array('ROLE_ADMIN', 'encodedAndSaltedPW3'),
)
In this case you would need to encode and salt the PWs like described in the docs:
// find the encoder for a UserInterface instance
$encoder = $app['security.encoder_factory']->getEncoder($user);
// compute the encoded password for foo
$password = $encoder->encodePassword('foo', $user->getSalt());
Also see the docs for the SecurityServiceProvider for more information.
Your Way: Single-User-Provider
After your comments I understand your needs better.
You could of course have an own ordinary "Single-User-Provider" - this could go something like this:
$app->post('/setup', function(Request $request) use ($app) {
$username = $request->get('username');
$passwd= $request->get('passwd');
$file = 'config.json';
$config = array($username => array('ROLE_ADMIN', $passwd));
file_put_contents($file, json_encode($config));
return $app->redirect($app['url_generator']->generate('login'));
});
Be aware that a json-file, without proper permissions, could be read by anyone - so make sure to put the encoded/salted password in it, by no means the readable one.
And then you would need to read the config.json into your Firewall-Configuration:
$app['security.firewalls'] = array(
'admin' => array(
'pattern' => '/private',
'form' => array('login_path' => '/login', 'check_path' => '/private/login_check'),
'logout' => array('logout_path' => '/private/logout', 'invalidate_session' => true),
'users' => json_decode(file_get_contents('config.json'))
)
);
Please notice, that this code is untested and should more likely give you an idea. I hope this helps.
I am using Silex 2.0 (I know - it's development version and not fully released yet) along with CNAM's JWT security provider (see: https://github.com/cnam/security-jwt-service-provider) to write an API for an open source application I am writing.
In short, there are three types of users that I care about:
Sitewide admins (ROLE_ADMIN) that have complete access
Commissioners (ROLE_COMMISH) who create objects they own, and can edit their own objects
Anonymous users who access read-only information.
As such, there are three sections of routes that go along with these "roles":
/admin/* where administrators can perform their uber actions
/commish/* where commissioners or admins can perform their actions on their objects
/* where all users can read information
The issue that I've come across is that while I can setup 3 firewalls, one for each, there are times in the 3rd route category (GET /object/1 for instance) where it needs to be accessibly anonymously, but if the user provides a valid JWT token, I need to access that user in order to perform some additional logic on the data I hand back in the response.
As I have it setup currently (more on my config below), it's all-or-nothing: I either restrict an entire firewall to only authenticated users with a certain role, or I open it up to anonymous users (and therefore cannot view user information).
Is it possible to have a route that anyone can hit, but logged in users can also be seen?
Current security configuration:
$app['users'] = function () use ($app) {
return new UserProvider($app);
};
$app['security.jwt'] = [
'secret_key' => AUTH_KEY,
'life_time' => 86400,
'algorithm' => ['HS256'],
'options' => [
'header_name' => 'X-Access-Token'
]
];
$app['security.firewalls'] = array(
'login' => [
'pattern' => 'login|register|verify|lostPassword|resetPassword',
'anonymous' => true,
],
'admin' => array(
'pattern' => '^/admin',
'logout' => array('logout_path' => '/logout'),
'users' => $app['users'],
'jwt' => array(
'use_forward' => true,
'require_previous_session' => false,
'stateless' => true,
)
),
'commish' => array(
'pattern' => '^/commish',
'logout' => array('logout_path' => '/logout'),
'users' => $app['users'],
'jwt' => array(
'use_forward' => true,
'require_previous_session' => false,
'stateless' => true,
)
)
);
$app['security.role_hierarchy'] = array(
'ROLE_ADMIN' => array('ROLE_MANAGER'),
);
$app->register(new Silex\Provider\SecurityServiceProvider());
$app->register(new Silex\Provider\SecurityJWTServiceProvider());
Additionally, I've attempted another approach where I match all routes under a single firewall, but then protect certain ones by using securty.access_rules configuration, but it does not work. An example of what I've tried:
$app['security.firewalls'] = array(
'api' => array(
'pattern' => '^/',
'logout' => array('logout_path' => '/logout'),
'anonymous' => true,
'jwt' => array(
'use_forward' => true,
'require_previous_session' => false,
'stateless' => true
)
)
);
$app['security.access_rules'] = array(
array('^/admin', 'ROLE_ADMIN'),
array('^/commish', 'ROLE_MANAGER'),
array('^/', 'IS_AUTHENTICATED_ANONYMOUSLY')
);
You can use $app['security.jwt.encoder'] to decode jwt and either create a custom trait and extending the route object or using midddlewareeeither on the route level or an easier way would be to use a middleware on the application level. I had similar issue and this is how i solved it, something like below
ex.
$app->before(function (Request $request, Application $app) {
$request->decodedJWT = $app['security.jwt.encoder']->
decode($request->headers->get('X-Access-Token'));
});
and then you can access the decoded jwt form any route by doing this
$app->get('/object/1', function(Request $request) {
$decodedJWT = $request->decodedJWT;
// do whatever logic you need here
})
So: so far I have not found this to be possible through the "normal" way, which is disappointing. I will not mark what I detail below as the "answer" for a few days, hoping that someone can chime in and offer a better, more "official" way to solve the dilemma.
TL;DR: I manually check the request headers for the access token string, then decode the token using the JWT classes in order to load the user account in routes outside of the firewall. It's incredibly hacky, it feels downright dirty, but it's the only solution to the issue that I see at the moment.
Technical Details: First, you must acquire the token value from the request header. Your controller method will have been handed a Symfony\Component\HttpFoundation\Request object, from which you can access $request->headers->get('X-Access-Token'). In most instances the user will not be authenticated, so this will be empty, and you can return null.
If not empty, you must then use Silex's instance of JWTEncoder to decode the token contents, create a new token instance of JWTToken, set the context to the decoded value from the encoder, and finally you can access the username property from said token - which can then be used to grab the corresponding user record. An example of what I came up with:
$request_token = $request->headers->get('X-Access-Token','');
if(empty($request_token)) {
return null;
}
try {
$decoded = $app['security.jwt.encoder']->decode($request_token);
$token = new \Silex\Component\Security\Http\Token\JWTToken();
$token->setTokenContext($decoded);
$userName = $token->getTokenContext()->name;
//Here, you'd use whatever "load by username" function you have at your disposal
}catch(\Exception $ex) {
return null;
}
And obviously, any code calling this function would need to know that because the request is outside of the firewall, there is zero guarantee that a user will be returned (hence the hacky try-catch that silences exceptions by just returning null).
Edit: I've updated the code here to use Silex's built-in DI container (provided by Pimple) so there's no need to create a new instance of the JWT encoder by hand. I'm also marking #user5117342 's answer as the correct one, as using some sort of Silex middleware approach is far more robust.
Edit (April 2016): Using the updated cnam/security-jwt-service 2.1.0 along with symfony/security 2.8, there's a slight update that makes the code above a little simpler:
$request_token = $request->headers->get('X-Access-Token','');
if(empty($request_token)) {
return null;
}
try {
$decodedToken = $app['security.jwt.encoder']->decode($request_token);
$userName = $decodedToken->name;
//Here, you'd use whatever "load by username" function you have at your disposal
}catch(\Exception $ex) {
return null;
}
The issue with the newer dependencies is that the JWTToken constructor requires 3 parameters which are difficult to obtain in most service layers, not to mention is quite out of place. As I was updating my Composer dependencies, I ended up finding out that I didn't actually need to create a JWTToken in order to get the username I needed.
Of course, it's to be noted I'm only using this method on public (anonymous) API routes to provide some niceties to users who are logged in - my app doesn't deal with sensitive data so I'm not overly concerned with this avenue outside of the firewalls. At worst a black hat user would end up seeing non-sensitive data that they normally wouldn't, but that's it. So YMMV.
Your are must be use regular expression e.g.
$app['security.firewalls'] = array(
'login' => [
'pattern' => 'login|register|oauth',
'anonymous' => true,
],
'secured' => array(
'pattern' => '^/api|/admin|/manager',
'logout' => array('logout_path' => '/logout'),
'users' => $app['users'],
'jwt' => array(
'use_forward' => true,
'require_previous_session' => false,
'stateless' => true,
)
),
);
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
with cake 1.3 for autologin facebook users i get the user info from database:
$userInfo = $this->User->find('first', array(
'fields' =>array('User.username','User.password'),
'conditions' => array(
'source_id' => $fb_user_id,
'source' => "facebook",
)
));
password comming from this select is hashed.
and i use this method for authentication
$this->Auth->login($userInfo);
but now with cake 2
var_dump($this->Auth->login($userInfo));
always return false;
i 'm not sure but i think that now the login() method need a clear password??
any solution? and excuse my English
Try $this->Auth->login($userInfo['User']), to login user in cakephp 2.0 you should use array with users data, without mention model
If its will not help then check if your Auth component configuration is right...
I use
$this->Auth->authenticate = array(
'all' => array(
'userModel' => 'User',
),
'Form'
);
$this->Auth->loginAction = {url};
$this->Auth->logoutAction = {url}