Turning YII_CSRF_TOKEN secure flag on - php

I have enabled CSRF Validation in Yii:
'enableCsrfValidation' => true,
Everything works as expected however I'd like for the session cookie to have the secure flag turned on.
With other cookies you can set the secure flag in the config:
'session'=>array(
'cookieParams' => array(
'httponly'=>true,
'secure' => true,
),
),
How do you do this for the YII_CSRF_TOKEN?

Add the following to your config:
'components' => array(
'request' => array(
'csrfCookie'=>array(
'secure'=>true,
),
),
),

You can't do that with the built in CHttpRequest component. You will need to derive from it and override the createCsrfCookie() to create a secure cookie as follows:
class CustomHttpRequest extends CHttpRequest {
protected function createCsrfCookie()
{
$cookie=new CHttpCookie($this->csrfTokenName,sha1(uniqid(mt_rand(),true)));
$cookie->secure = true; //Here is where you make your cookie secure
if(is_array($this->csrfCookie))
{
foreach($this->csrfCookie as $name=>$value)
$cookie->$name=$value;
}
return $cookie;
}
}
In your components configuration, specify your custom implementation:
'components'=>array(
....,
'request' => array(
'class' => 'CustomHttpRequest',
'enableCsrfValidation' => true,
),
IMPORTANT: For a new CSRF token to be generated, you will need to start a new browser session. Also, you will need to use HTTPS for a secure cookie to be in effect.
Delete all cookies for your development URI, or start a private session (in Chrome or Firefox) to start a new session.

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.

Yii2 Session persistance when redirecting from one action to another

Using Yii2 framework:
The code below creates an endless loop.
Can anyone please explain how I make the session data persist on redirect ?
I have checked and there is not data being transferred, but the session data is set inside searchuser correctly.
public function actionSearchUser()
{
$session = \Yii::$app->session;
$session->open();
$session->set('admin.currentuser.id', "This worked out ok");
return $this->redirect(['site/modify-user']);
}
public function actionModifyUser()
{
$session = \Yii::$app->session;
$session->open();
if( !($session->has('admin.currentuser.id')) )
{
return $this->redirect(['site/search-user']);
}
else return $this->render('modifyUser');
}
And here is where I setup my session:
'session'=>array(
'class' => 'yii\web\Session',
'name' => 'SESSIONNAME',
'timeout' => 86400,
'savePath' => '/path/to/sessions',
'useCookies' => true,
'cookieParams' => array(
'lifetime' => 86400,
'path' => '/',
'domain' => 'localhost',
),
),
My problem was the domain (I know, I'm stupid).
I have a custom domain (n099y.local) so I needed to change the cookie domain from localhost to n099y.local and everything was fine.
It was showing all the correct session data on the page until I went to another page when the data was again missing because the cookie domain did not match the domain I was on.

Silex: Get Authenticated User Information on Routes Outside of Firewall

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,
)
),
);

yii user is logged in on domain.com/x but isGuest on domain.com/y

I'm currently working in a project I toke over after other people that started this build.
When I first started the project was two different subdomains x.domain.com and y.domain.com
now we are moving it to be one domain but two sites domain.com/x and domain.com/y.
earlier the login functionality was only available on the x subdomain but now I want users too be able to be logged in on both sites.
Each site has there on main controller (xController and yController) that both extends the xyController.
If I log in on the x-site everything works great but as soon as I go to domian.com/y
yii:app()->user->isGuest returns true
If I go back to domain.com/x im logged in.
I can't figure out why this is happening the PHPSESSID cookie is the same for both sites.
This is my class that extends CUserIdentity:
class UserIdentity extends CUserIdentity{
private $_id;
public function authenticate(){
$user=User::model()->findByAttributes(array('user_name'=>$this->username));
if($user === null){
$this->errorCode=self::ERROR_USERNAME_INVALID;
}else{
if($this->comparePassword($user, $this->password)){
/** Do some other checks here **/
$this->_id=$user->id;
$this->errorCode=self::ERROR_NONE;
return !$this->errorCode;
}
$this->errorCode=self::ERROR_USERNAME_INVALID;
}
return !$this->errorCode;
}
public function getId(){
return $this->_id;
}
}
and here is the part from the config file
'components'=>array(
'user'=>array(
'class'=>'WebUser',
// enable cookie-based authentication
'allowAutoLogin'=>true,
),
...
[EDIT] I found what the problem was, I had to set the id option in the config array to the same value in both configs, the id was not set in any of them before
Enable cookie-based authentication in your config:
'user' => array(
'allowAutoLogin' => true,
),
Than configure your session component:
'session' => array(
'savePath' => '/some/writeable/path',
'cookieMode' => 'allow',
'cookieParams' => array(
'path' => '/',
'domain' => '.yourdomain.com',
'httpOnly' => true,
),
),
Make sure that /some/writeable/path is really writable!
Finally, and this is the crucial bit with Yii (the above cookie configuration is generic PHP), the Yii application ID must be set in the config file:
'id' => 'yourdomain',
That's it!

How to retrieve cookie value in CodeIgniter?

I can print the session values in codeigniter by print_r($this->session->userdata);
How can I print the cookies in codeigniter? I have just set a cookie:
$cookie = array(
'name' => 'test_cookie',
'value' => 'test',
'domain' => '/',
'secure' => TRUE
);
$this->input->set_cookie($cookie);
How can i print the above cookie?
Look at the documentation: Codeigniter Cookie Helper Guide
It says that you should use $this->input->cookie() to retrieve a cookie:
$this->input->cookie('test_cookie', TRUE);
This worked for me on localhost, security might need tightened for server
$this->load->helper('cookie');
$cookie = array(
'name' => 'data',
'value' => '23',
'expire' => 86500,
'secure' => false
);
$this->input->set_cookie($cookie);
var_dump($this->input->cookie('data', false));
Expire needs to be numeric, removed path and set secure to false
If you are using google chrome use inspect element to see if the cookie has been set... I think you can do it in FF, but I haven't used FF in a while... I only had one issue with cookies and that was I was setting domain to my live domain... So I have my cookie code like this:
$this->load->helper('cookie');
$cookie = array(
'name' => 'the_cookie',
'value' => 'test value here',
'expire' => '15000000',
'prefix' => ''
);
$this->input->set_cookie($cookie);
Here you can see it is showing up in Google Chrome "Inspect Element Tool"
'secure' => TRUE
This does not allow me to fetch the cookie.
just set
'secure' => FALSE
and see it may work.
setting the security => TRUE will not allow to print the cookie value in local, it only grant access to secure connections only so it will not print anything in localhost for you unless you set the security => FALSE
than using codeigniter CI_Input class you can get the value of cookie
$this->input->cookie('cookie_name', TRUE); //with xss filtering
$this->input->cookie('cookie_name'); //without xss filtering
If the code mentioned below does not provide any output, then modify the
application/config/config.php file and setting this:
$config['global_xss_filtering'] = TRUE;
$this->input->cookie('cookie_name', TRUE);
else just use this it will display the value
$this->input->cookie('cookie_name');
Load the cookie helper with:
$this->load->helper('cookie');
Then retrieve your cooking with:
$cookieData = get_cookie("cookie_name");
Note, these are aliases to using the input class, you can also retrieve and set cookies like so:
$cookieData = $this->input->get_cookie("cookie_name");
Source
http://ellislab.com/codeigniter/user-guide/helpers/cookie_helper.html

Categories