My goal is to require login for certain pages. I am using Zend Framework MVC, and I'm trying to find examples regarding best practices.
Some notes on what I'm looking for:
I want non-logged in users to get a login box, and then return to logged in version of the page, once authenticated
I want to use dependency injection, and avoid singletons
Small code footprint - tie into Zend mvc structure
Should login box be a separate controller and do header redirect? How to return to landing page after auth success? An idea to simply call the login controller action to display the login box in the landing page, or is this a disadvantage regarding search engine indexing?
Be able to use external library for handling cookies
Or something completely different. I'm fairly new to the Zend framework, and I want to do it 'the right way'.
I want non-logged in users to get a login box, and then return to logged
in version of the page, once
authenticated
Use a FrontController plugin and redirect or forward them to your loginAction.
I want to use dependency injection, and avoid singletons
Zend Framework, doesn't currently ship any DI system, however, the Zend_Application_Resource_* actually replace it. What kind of dependency would you need here?
Small code footprint - tie into Zend mvc structure
That's up to you.
Should login box be a separate controller and do header redirect? How
to return to landing page after auth
success? An idea to simply call the
login controller action to display the
login box in the landing page, or is
this a disadvantage regarding search
engine indexing?
I mostly use a special AuthController with LoginAction & LogoutAction. To redirect the user to the page is was trying to view, I always add a returnUrl element in my forms, and I inject the value of the requested URL to be able to redirect the user, and if none, I redirect him to the index/dashboard, depends.
Be able to use external library for handling cookies
Zend_Auth allows you to set your own storage mechanism, so just implement the interface.
$auth = Zend_Auth::getInstance();
$auth->setStorage(new My_Auth_Storage());
But never store authentication result in a cookie, it's so easy to modify it and access your website.
You may also take a look to one of my previous answer.
You could use the combination of Zend_Auth and Zend_Acl. To extend the other answers I give a short example of how you can manage authentication using zend framework:
First you need to setup a plugin to predispatch all requests and check if the client is allowed to access certain data. This plugin might look like this one:
class Plugin_AccessCheck extends Zend_Controller_Plugin_Abstract {
private $_acl = null;
public function __construct(Zend_Acl $acl) {
$this->_acl = $acl;
}
public function preDispatch(Zend_Controller_Request_Abstract $request) {
//get request information
$module = $request->getModuleName ();
$resource = $request->getControllerName ();
$action = $request->getActionName ();
try {
if(!$this->_acl->isAllowed(Zend_Registry::get('role'),
$module . ':' . $resource, $action)){
$request->setControllerName ('authentication')
->setActionName ('login');
}
}catch(Zend_Acl_Exception $e) {
$request->setControllerName('index')->setActionName ('uups');
}
}
}
So every user type has certain permissions that you define in your acl library. On every request you check if the user is allowed to access a resource. If not you redirect to login page, else the preDispatch passes the user to the resource.
In Zend_Acl you define roles, resources and permission, that allow or deny access, e.g.:
class Model_LibraryAcl extends Zend_Acl {
public function __construct() {
$this->addRole(new Zend_Acl_Role('guests'));
$this->addRole(new Zend_Acl_Role('users'), 'guests');
$this->addRole(new Zend_Acl_Role('admins'), 'users');
$this->add(new Zend_Acl_Resource('default'))
->add(new Zend_Acl_Resource('default:authentication'), 'default')
->add(new Zend_Acl_Resource('default:index'), 'default')
->add(new Zend_Acl_Resource('default:error'), 'default');
$this->allow('guests', 'default:authentication', array('login'));
$this->allow('guests', 'default:error', 'error');
$this->allow('users', 'default:authentication', 'logout');
}
}
Then you have to setup acl and auth in your bootstrap file:
private $_acl = null;
protected function _initAutoload() {
//...your code
if (Zend_Auth::getInstance()->hasIdentity()){
Zend_Registry::set ('role',
Zend_Auth::getInstance()->getStorage()
->read()
->role);
}else{
Zend_Registry::set('role', 'guests');
}
$this->_acl = new Model_LibraryAcl ();
$fc = Zend_Controller_Front::getInstance ();
$fc->registerPlugin ( new Plugin_AccessCheck ( $this->_acl ) );
return $modelLoader;
}
Finally in your authentication controller you have to use a custom auth adapter and setup actions for login and logout:
public function logoutAction() {
Zend_Auth::getInstance ()->clearIdentity ();
$this->_redirect ( 'index/index' );
}
private function getAuthAdapter() {
$authAdapter = new Zend_Auth_Adapter_DbTable (
Zend_Db_Table::getDefaultAdapter ());
$authAdapter->setTableName('users')
->setIdentityColumn('email')
->setCredentialColumn ('password')
->setCredentialTreatment ('SHA1(CONCAT(?,salt))');
return $authAdapter;
}
In your login action you need to pass login data to the auth adapter which performs the authentication.
$authAdapter = $this->getAuthAdapter ();
$authAdapter->setIdentity ( $username )->setCredential ( $password );
$auth = Zend_Auth::getInstance ();
$result = $auth->authenticate ( $authAdapter );
if ($result->isValid ()) {
$identity = $authAdapter->getResultRowObject ();
if ($identity->approved == 'true') {
$authStorage = $auth->getStorage ();
$authStorage->write ( $identity );
$this->_redirect ( 'index/index' );
} else {
$this->_redirect ( 'authentication/login' );
}
And that's all. I recommend you this HOW TO on youtube on zend auth and zend acl.
Not sure if this is best practice, but if I were to implement what you are after then I'ld do the following:
Create a LoginForm with a uniquely identifiable submit button (explained later)
Create an AuthService with methods getLoginForm, login, logout and one or all of: getIdentity, hasIdentity, and similar methods.
Create a viewhelper that has an instance of this service, and depending on what the outcome of hasIdentity is, either render the LoginForm, or render the identity of logged in user, in the views that need this functionality.
Create a front controller plugin that also has an instance of the service and implement the preDispatch plugin hook. Give it some config of which actions to probe. If AuthService->hasIdentity() returns true, do nothing. If request is a post request look for the uniquely identifiable submit button name, i.e.: $request->getPost( 'loginSubmitButton', null ); defaulting to null if not available. If not null do a login( $request->getPost() ) on the service. If successful, redirect to the same action, if unsuccessful fall through and render the form in the viewhelper again (automatically displaying the errors).
The service could be fetched by both the viewhelper and the front controller plugin with some service locator doing something like ServiceAbstract::getService( 'Auth' ) by registering the service to ServiceAbstract in the bootstrap.
Related
im doing this;
example core/MY_CONTROLLER.php
private $action_user=null;
public function __construct()
{
parent::__construct();
##listen for post attempts;
$this->validate();
##set action_user; return null if no session else return user object
$this->action_user = $this->session->userdata('loged_user');
##extra check step
if($this->user->pwd_has_changed($this->action_user)){
$this->session->sess_destroy();
alerts('error','the password you used to login has changed ! please relogin');
return $this->failed_login();
}
}
public function alerts(){return die(json_encode(alerts()));}#a helper function.. just ignore it for this example
public function logout(){$this->session->sess_destroy();redirect();}
#AUTH
private function failed_login(){
//$callers=debug_backtrace();
alerts('warning','failed login');//.' '.$callers[1]['function']);
ob_clean();//clear flush just to make sure !
if($this->input->is_ajax_request())$this->load->view('base/ajax/landing');
else $this->load->view('base/landing');
die($this->output->get_output());//kill request and load landing in same uri. this in case he attempt login again he will be at same url; also helps with partials views
}
private function success_login(){
unset($_POST['login'],$_POST['password']);
alerts('success','welcome '.$this->action_user->login);
//nothin much to do.. just dont die
}
private function validate(){
//listen to posts if so logout and relog in
if( !$this->input->post('login') || !$this->input->post('password'))return FALSE;
$this->session->sess_destroy();#destroy session
#1. validation
$this->form_validation->set_rules('login', 'User Login', 'required|min_length[4]|max_length[12]|xss_clean');
$this->form_validation->set_rules('password', 'Password', 'required|min_length[4]|max_length[12]|xss_clean');
#1.2 Failed validation
if( ! $this->form_validation->run() )return alerts('error',$this->form_validation->error_string(),false);#set message and return false
#2. Login
$this->user->login(set_value('login'),set_value('password'));
//i dont want it to return anything ! $this->user->login should set messages of success OR fail + set user session
}
public function auth($role = null){
if(!isset($this->action_user->id))
return alerts('error',"this is a users restricted area",$this->failed_login());
//ACCESS LEVELS CONDITIONS
if($this->user->in_group($this->action_user->id,$role))return $this->success_login();
return alerts('error',"this is a {$role} restricted area",$this->failed_login());
}
#END AUTH
now in my controller constructor; since MY_CONTROLLER constructor is called first; so i should hv retrieved $action_user object; or already attempted to log him in.
if i want to restrict a page i just add
$this->auth();
//or $this->auth('admin');
to its constructor and if user is not allowed page will die and send him my view page without redirect;
the reason im using such approach is let user be able to login,logout from any controller;
if he visit http://localhost/RANDOMECONTROLLER/logout he will still logout.. same for login.
also its helpful that sometimes when i get page partials by ajax; it will just return a landing page into this div with login form.
example
a statistics page have 4 widgets, 1 of them is only viewable by admin;
then when ajax fitch 4 widgets, it will show 3 and a div with login form saying you need to be an admin to login..
...
so do you think this is a good way to do this ? or is it bug gable spaghetti ** ?
This is not a good practice. The best practice is to create a Secure_Controller that extends MY_Controller. If you have a controller with auth you need to extend Secure_Controller, but if you have another without auth you need yo extend MY_Controller.
There are lots of libraries for codeigniter auth easy to extend and adapt to your requirements, for example Ion Auth.
I am having trouble finding a method for authenticating API users in my application. I set out to initially build a system that users could access and authenticate through the web, but requirements have changed and I need to implement some additional actions that can be available in a RESTful manner using a POST API call.
I have created a class that extends CBehaviour and forced a redirect to the login page for all unauthenticated users (found on the yii framework forum here). Problem is that all API calls are forced through the same logic and any POST requests simply spit out the HTML to the login page.
class ApplicationBehavior extends CBehavior {
private $_owner;
public function events() {
return array(
'onBeginRequest' => 'forceGuestLogin',
);
}
public function forceGuestLogin() {
$owner = $this->getOwner();
if ($owner->user->getIsGuest())
$owner->catchAllRequest = array("site/login");
}
}
How would I go about separating the authentication of API users from the Web users?
I would follow this guide on creating a REST API in Yii. After modifying the config urlManager entries, all of your API requests will use the APIController. You can then place the following code in the beforeAction of your APIController to return nothing if the user is a guest (Or an error message)
protected function beforeAction($event) {
if (Yii::app()->user->isGuest) {
echo "Invalid credentials";
Yii::app()->end();
}
}
Note: The code above works for my purposes because all REST requests are sent via the same browser. (Which is already logged in and has a login cookie)
If you replace that behavior with a new base controller placed in protected/controllers to force login, it will only apply to your pages that require a login and not your APIController. Here is an example of mine:
//Make sure all Controllers which require a login inherit from this
class ControllerLoginRequired extends CController {
public function runAction($action) {
if (Yii::app()->user->isGuest && 'site' != $this->route) {
Yii::app()->user->returnUrl = $this->route;
parent::redirect(array('site/login'));
} else {
parent::runAction($action);
}
}
}
Everything explained will work for REST requests via the same browser in which the user has logged onto Yii. If you will have the need to expose your REST service to consumers that are not a browser logged into Yii, I believe you would have to come up with a custom authentication/token scheme.
I am new in Zend Framework 2.
I have created a "Admin" module and also created "UserController" and "AlbumController".
UserController contains login and logout actions.
AlbumController contains normal CRUD and welcome action.
Now, How can i redirect page on welcome page when i directly access http://localhost/websites/zendtest/public/admin/login when i already loggedin.
And, same question is How can i redirect page on login page when i directly access http://localhost/websites/zendtest/public/admin/album/welcome when i already not loggedin.
Can any one suggest me solution for that?
I also have another question, how can i use controller action value in layout.phtml, because i have MenuContoller for creating menu. So i need return array from MenuController in layout.phtml for create dynamic menu.
So, how can i do that?
I think that you wan't is explained in the ZF2 doc. To resume, you have to test for session, and to redirect, using redirect plugin :
$this->redirect()->toRoute('actionname');
The redirect plugin is used like this:
->toRoute($route, array $params = array(), array $options = array());
Redirects to a named route, using the provided $params and $options to assembled the URL.
To authenticate a user like the acl plugin for older ZF, go to this page
And for the last question, you can past some value in the view using (for the ZF2.1.3)
$layout = $this->layout();
$layout->myvar = $mymenuarray;
And retrieve it in the view using
$myvar...
I don't know how you are authenticating users, but if you are using Zend\Auth, then you can do something like this:
public function loginAction() {
$authService = new \Zend\Authentication\AuthenticationService();
$authService->setStorage(new \Zend\Authentication\Storage\Session('user', 'details'));
if ($authService->hasIdentity()) {
// User is already logged in; redirect to welcome page
return $this->redirect()->toRoute('welcome'); // Assumes that you have a 'welcome' route
}
}
And for the welcome action:
public function welcomeAction() {
$authService = new \Zend\Authentication\AuthenticationService();
$authService->setStorage(new \Zend\Authentication\Storage\Session('user', 'details'));
if (!$authService->hasIdentity()) {
// User is not logged in; redirect to login page
return $this->redirect()->toRoute('login'); // Assumes that you have a 'login' route
}
}
The above can be quite repetitive if you wish to do it on many pages, so you should consider making it reusable, e.g. by fetching the authentication service from the service manager (a factory).
i'm trying to work out how i can stop zend or redirect zend to go to a different zend controller and action if a check within the boot strap fails.
for example a get variable does not exist or more likely a session does not exist meaning the user must log in.
so the user had originally requested index/someaction
but i want them to go to login/index
within my boot strap i would place the condition and then change the controller action to view.
if i'm doing this in a way that is not standard can anyone direct me to documentation on the best practice ?
zend novice
From Zend documentation (Dispatcher)
// forward to an action in another controller:
// FooController::bazAction(),
// in the current module:
$this->_forward('baz', 'foo', null, array('baz' => 'bogus'));
I'd sugest you to do with plugins for access check on each page and for login create an authentication controller.
Here you'll find out how to do this http://alex-tech-adventures.com/development/zend-framework/61-zendauth-and-zendform.html
An example:
class Plugin_AccessCheck extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
// ...
if(!$auth->hasIdentity())
{
$request->setControllerName('authentication')
->setActionName('login');
}
}
}
I don't usually put authentication in the bootstrap, that should have it's own controller.
Create an AuthController() to set up your auth adapter and and set up your instance.
Then in a common view (for secure pages), just check your instance with something like:
$auth = Zend_Auth::getInstance();
if(!$auth->hasIdentity())
{
#re-direct to login page
}
I am playing about with the Zend Framework at the moment and I have a written the authentication code using Zend_Auth. I am trying to find a way to ensure that the user is logged in before they can view anything but I don't want to do it on a per controller basis.
I am thinking a plugin, but all the books I have on it are pretty rubbish in this respect.
Zend_Auth::getInstance()->hasIdentity()
You can use this to determine if the user is logged in, and then use the redirector to redirect to the login page if not.
However, the easier way is to use a Redirector Zend Controller Action Helper.
A plugin is a good idea.
I answered to a similar question here:
How do i centralize code from my init functions in all controllers ?
Also check the documentation page Zend Controller Plugins
Look into Zend___Acl which can be used to define whether a user has access to certain resources. A resource can be pretty much anything, but in this context you can use the ACL to define your controllers and actions as resources. Each logged in user is then assigned a number of roles (we store them in a database). In a plugin you check the request for the Controller and Action, after routing is complete. Gather the roles of the user through the Zend_Auth and check them against the ACL. If the ACL says the user has permission to access the resource, do nothing, else you can forward/redirect to your error controller and print the error.
// Pseudo-code. You need to define the ACL and roles somehow.
class AclPlugin extends Zend_Controller_Plugin {
public function routeShutdown(Zend_Controller_Request_Abstract $request)
{
$controller = $request->getControllerName();
$action = $request->getActionName();
$roles = Zend_Auth::getInstance()->getRoles();
$acl = new MyAcl();
if($acl->hasAccess($roles, $controller, $action)) { return; }
// None of the user's roles gave her access to the requested
// controller/action, so re-write the request to the error controller
$request->setControllerName('error')
->setActionName('authorizationFailed')
->setParam('resource', array('controller' => $controller
'action' => $action));
}
}
class MyAcl extends Zend_Acl {
public function hasAccess($roles, $controller, $action) {
foreach($roles as $role) {
if($acl->isAllowed($role, $controller, $action)) {
return true; // Simplified. Here we say if one of the user roles can
// access a resource, that is good enough.
// Might want to do something a bit more complicated.
}
}
return false;
}
}
How about setting up a global controller that extends Zend_Controller_Action and then have your controllers extend from the global controller. Then your global controller puts the Zend_Auth::getInstance()->hasIdentity() in the init() or preDispatch?
I was pondering the same subject lately (ZF newbie), and thought about doing the authentication checks in the bootstrap (maybe with a list of "bypassed" controllers/actions).