SitePoint Sponsor

User Tag List

Results 1 to 13 of 13
  1. #1
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Front Controller Authentication

    I feel like this should be common knowledge, but I can't seem to find a good example. I have a front controller setup, and a single level of controllers (no konstrukt-type hierarchy, yet... ). So, for example, my routes look like this:

    $routes = array (
    '/page1/' => 'PageController',
    '/admin/' => 'Admin_HomeController',
    '/admin/pages/' => 'Admin_PagesController'
    );

    Now, I'm wondering how to set up authentication so that every page that begins with /admin/ will redirect to the login page if the user is not currently logged in, or does not have sufficient access. (This may involve authorization as well, I don't know). Since I haven't seen examples of this, I am assuming most people just have a check in every admin controller, but I'd like to avoid that if possible. This would be easy with a hierarchy of controllers, since I could dispatch to an admin controller prior to dispatching to the final handler, but when doing simple mapping like this I have no idea how to start. Any ideas?

  2. #2
    PHP/Rails Developer Czaries's Avatar
    Join Date
    May 2004
    Location
    Central USA
    Posts
    806
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    You can achieve this best with filters. For most projects, I usually define one base 'User' before filter that gets called right before the controller action is executed. It checks a session variable and throws an Authorization exception if the user is not logged in or does not have the proper viewing permissions. The exception is caught in the bootstrap file and the user is re-directed to a login page. The login controller has filters disabled so the user won't get caught in a loop. This setup works very well for me.

  3. #3
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Czaries View Post
    You can achieve this best with filters. For most projects, I usually define one base 'User' before filter that gets called right before the controller action is executed. It checks a session variable and throws an Authorization exception if the user is not logged in or does not have the proper viewing permissions. The exception is caught in the bootstrap file and the user is re-directed to a login page. The login controller has filters disabled so the user won't get caught in a loop. This setup works very well for me.
    Do you have a code example? How do you specify the auth to happen for all /admin/ controllers but not other controllers?

  4. #4
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by allspiritseve View Post
    Since I haven't seen examples of this, I am assuming most people just have a check in every admin controller, but I'd like to avoid that if possible. This would be easy with a hierarchy of controllers, since I could dispatch to an admin controller prior to dispatching to the final handler, but when doing simple mapping like this I have no idea how to start. Any ideas?
    You could create an abstract base class and let your admin-controllers extend from it. Then implement the auth code in the base class. It's not very flexible, but it's simple.

  5. #5
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken View Post
    You could create an abstract base class and let your admin-controllers extend from it. Then implement the auth code in the base class. It's not very flexible, but it's simple.
    I would prefer something more flexible.

  6. #6
    SitePoint Enthusiast
    Join Date
    Feb 2004
    Location
    Montreal
    Posts
    77
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    This may be a good time to consider the idea of modules. That way, all request's to that module can have a common set of 'Filters' or whatever you want to call them.

    Without breaking things too much, you could simplify this by allowing hierarchical routers so that at the top-level, all '/admin/' routes are passed into an AdminRouter. That way the whole admin module is self-contained.

  7. #7
    SitePoint Addict
    Join Date
    Aug 2007
    Location
    St. Louis, MO.
    Posts
    206
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    The way I set mine up just after I instantiate the controller I make check to see if authorization is required by checking if authorization is required.

    Code PHP:
    <?
    $page = new trendyFrameWorkName();
     
    if( $page->authRequired() )
       $page->auth();
     
    // not required or they are authorized
    $page->process();
     
    exit;
     
    ?>


    The auth() method takes them to the login page so they can create the session if they aren't logged in. Otherwise it processes the page. Then for each page I can decide if someone needs to be logged in or even set up restrictions on user levels or user groups. You can also specify what page they land on once logged in based on permissions.

    Usually I break permissions into levels that are just integers. So I can show the same page for an authenticated user but different information. Or maybe put restriction of the min level that can view the page.

    Which I just realized is what everyone else just said

  8. #8
    PHP/Rails Developer Czaries's Avatar
    Join Date
    May 2004
    Location
    Central USA
    Posts
    806
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by allspiritseve View Post
    Do you have a code example? How do you specify the auth to happen for all /admin/ controllers but not other controllers?
    Sure, Here's some code from the front controller dispatchModule() function related to the filters:
    PHP Code:
    /**
     * Call module action
     * 
     * @param string $moduleName Name of module to be called
     * @param optional string $action function name to call on module
     * @param optional array $params parameters to pass to module function
     *
     * @return object Back40_View
     */
    public function dispatchModule($moduleName$action 'index', array $params = array())
    {
        
    // ... (snip)
        
        // Call actual module function
        
    if(is_callable(array($moduleClass$action))) {
            
    $callable true;
            
    $filterAction $action;
            
            
    // If this is callable with __call instead of action name
            
    if(!method_exists($moduleClass$action)) {
                
    // Filter action will be '__call', the actual function name
                
    $filterAction '__call';
            }
        } else {
            
    $callable false;
        }
        
        if(
    $callable) {
            
    // Run filters for current module
            
    if(isset($moduleClass->filters['before']) && $filterAction != $this->filterFunction) {
                
    $this->runModuleFilters('before'$moduleClass$filterAction);
            }
            
            
    // Log function/module call
            
    if(!FW_STAGE_PRODUCTION) {
                
    $this->addDispatchLogMessage('Calling: ' $moduleName.'::'.$action.' (Used memory: '.calcSize(memory_get_usage()).')');
            }
            
            
    // If module is authenticated to run
            
    if($moduleClass->authenticated) {
                
    $moduleClass->preDispatch();
                if(
    count($params) > 0) {
                    
    // Call with function arguments
                    
    $return call_user_func_array(array($moduleClass$action), $params);
                } else {
                    
    // Call function directly (no arguments)
                    
    $return $moduleClass->$action();
                }
                
    $moduleClass->postDispatch();
            }
            
            
    // Run filters for current module
            
    if(isset($moduleClass->filters['after']) && $filterAction != $this->filterFunction) {
                
    $this->runModuleFilters('after'$moduleClass$filterAction);
            }
        } else {
            throw new 
    Back40_Exception_User($moduleClassName "::" $action " failed:<br> Module '" $moduleName "' does not have a callable method named '" $action "'.");
        }

        
    // ... (snip)

    And the code for the runModuleFilters() function:
    PHP Code:
    /**
     * Run any set filters for current module
     * 
     * @param string $type - Filter type (ususally 'before' or 'after')
     * @param string $module - Name of the module to be loaded
     * @param string $action - Name of the action to run filters for
     */
    public function runModuleFilters($type$module$action)
    {
        
    $return true;
        if(isset(
    $module->filters[$type]) && count($module->filters[$type]) > 0) {
            foreach(
    $module->filters[$type] as $filter) {
                
    $filterClass $filter.'Controller';
                
                
    // Load the module file (Locator will prevent double-loading)
                
    $loaded $this->locator->loadInModule($filterClass$filter);
                if(
    $loaded) {
                    
    // Call filter function with arguments
                    
    if(is_callable(array($filterClass$this->filterFunction))) {
                        
    // Log function/module call
                        
    if(!FW_STAGE_PRODUCTION) {
                            
    $this->addDispatchLogMessage('+ Running filter ('.$type.' '.$module->getName().'): ' $filterClass.'::'.$this->filterFunction.' (static) (Used memory: '.calcSize(memory_get_usage()).')');
                        }
                        
                        
    $return call_user_func_array(array($filterClass$this->filterFunction), array($module$action));
                    } else {
                        throw new 
    Back40_Exception(ucfirst($type) . ' filter could not be called: ' $filterClass.'::'.$this->filterFunction.' does not exist or is not within the public scope');
                    }
                } else {
                    throw new 
    Back40_Exception('Unable to load module ' $filter ' for processing: ' $filterClass ' not found');
                }
            }
        }
        return 
    $return;

    And the abstract base controller has the filters defined:
    PHP Code:
    abstract class Back40_Controller_Module extends Back40_Controller
    {
        
    // ... (snip)

        // Filters for current module - Modules to call before/after module execution
        
    public $filters = array(
            
    'before'    => array('User'),
            
    'after'        => array(),
            );

        
    // ... (snip)

    The filter property can be overridden by extending classes or certain specific filters can be added or 'unset' in the init() function for each controller (always called) if it needs to be more dynamic.

    The filter function will call a static function on the 'User' module (or whatever other modules are in the filter array) named 'filter', passing in the loaded module and action name. In that function, we can check basically anything we want. In your case, you would either put that 'User' or 'Auth' filter only on the modules that serve /admin/ content, or put it in all your modules and check the REQUEST_URI for '/admin/'.

    Good luck!

  9. #9
    <?php while(!sleep()){code();} G.Schuster's Avatar
    Join Date
    Mar 2007
    Location
    Germany
    Posts
    428
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    To be really flexible you may want to implement a plugin that is called on each request after the routing process has finished but before any other actions are executed.
    This way you should have all parameters you need and can match them against a database or whatever.
    For documented examples or code have a look at Zend Framework or http://www.extsend.com (Controller plugins / Guardian).

  10. #10
    SitePoint Evangelist
    Join Date
    Aug 2005
    Location
    Winnipeg
    Posts
    498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Which framework are you using or custom?

    1. Implement a base class that checks an authentication value in the registry or similar. Derive all controllers from this base controller and "forward" (not redirect) to the appropriate login controller.

    2. Check authentication in the pre-filter phase and dispatch (if you can) or redirect to the appropriate login page.

    The problem with the latter is that you might need some kind of additional check to only perform the authentication for /'admin' pages whereas it's implied via inheritence with the former solution.

    Of course neither is perfect and the former introduces a hierarchy which isn't strictly nessecary.

    Personally, in my own framework I use a pre-dispatch plugin and redirect to the login page.

    The check for '/admin' is a non-issue because I use a series of lookup files to resolve requests -- this implicitly hides that extraneous check from me as a developer.

    Cheers,
    Alex
    The only constant in software is change itself

  11. #11
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by PCSpectra View Post
    Which framework are you using or custom?
    Custom.

    Quote Originally Posted by PCSpectra View Post
    Derive all controllers from this base controller and "forward" (not redirect) to the appropriate login controller
    Why forward and not redirect?

  12. #12
    SitePoint Evangelist
    Join Date
    Aug 2005
    Location
    Winnipeg
    Posts
    498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    A forward is usually prefered, because it's more efficient.
    The only constant in software is change itself

  13. #13
    SitePoint Addict Mastodont's Avatar
    Join Date
    Mar 2007
    Location
    Czech Republic
    Posts
    375
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I have a front controller setup, and a single level of controllers ... how to set up authentication so that every page that begins with /admin/ will redirect to the login page if the user is not currently logged in, or does not have sufficient access
    If you have front controller, this should be IMO part of it. You should analyze URL and set context (module, action, user ...) for following processing - the rest is a bunch of IFs


Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •