"La sicurezza non è un prodotto ma un processo" Bruce Schneier

(English) Build a secure login with Zend Framework

Posted: luglio 3rd, 2009 | Author: | Filed under: Zend Framework | Tags: , , , , , , , , , , , | 54 Comments »

Ci spiace, ma questo articolo è disponibile soltanto in English.


54 Comments on “(English) Build a secure login with Zend Framework”

  1. 1 Zend Framework in Action » Secure login with Zend Framework said on luglio 4th, 2009:

    [...] Zimuel has posted an article on how to build a secure login with Zend Framework: After a long pause i’m come back on my blog with a post about the development of a secure web [...]

  2. 2 Joe Devon said on luglio 11th, 2009:

    Thanks for sharing your interesting approach and glad to have you back writing.

    I wanted to add a couple points. The MD5 password field length is fixed, so char is probably more appropriate than varchar.

    Re: strength of MD5:
    http://it.slashdot.org/article.pl?sid=07/11/20/1914209

    If you salt the MD5, then you’ve got a more secure setup. Add a column for each user and a random unique salt for each and it’s even better.

  3. 3 Enrico Zimuel said on luglio 11th, 2009:

    Thank you Joe, i just updated the post with your suggestions. You are absolutely right, a random salt definitely improves the security of the MD5.

  4. 4 Michael Krapf said on luglio 15th, 2009:

    Your tutorial is top notch and I appreciate the effort.

    Please bear with me as I am a php novice in training.

    Is it necessary to install the entire Zend backage in the library folder?

    How do I add new users? I am familiar with phpMyAdmin, but if the passwords are protected from the admin, shouldn’t the user have the ability to submit their own credentials?

    Any additional information for a newbie would be very helpful and appreciated.

  5. 5 Enrico Zimuel said on luglio 16th, 2009:

    If you want to use only some classes of the Zend Framework (ZF), without the MVC feature, you can install only these classes into the /library directory of the ZF.
    Anyway my suggestion is to install all the files of the /library folder of the ZF. In this way you can use the same ZF installation for all the php applications of your server.
    Related to the question on how add new users into the MySQL table ‘users’, you can use this simple query:

    SET @salt=SUBSTRING(MD5(RAND()),-20);
    INSERT INTO users (username,password,salt,email) VALUES (‘user’,MD5(CONCAT(@salt,’password’)), @salt, ‘email’);

    where ‘user’ is the username, ‘password’ is the password in plain and ‘email’ is the email address of the user.
    As you can imagine there are a lot of others way to create a random salt, this one is very easy and it uses directly the MySQL engine to generate the salt.
    You can create a simple php page to add a user account directly from the user subscription page (better if with SSL protection). In this way the admin of the web pages doesn’t know the password of the users.

  6. 6 Stephen Karl Lang said on luglio 16th, 2009:

    Great post, Enrico. I especially appreciate how these methods can also be applied to other frameworks and/or technologies. You have a new subscriber :)

  7. 7 Kalpesh said on luglio 20th, 2009:

    Hi,
    I like your tutorial.
    I use this in my project.
    I am using Model_Db_Table to access database.
    To insert user name and password there is a function DbTable
    called $this->insert($data);

    How can i create password with md5 salt.

    If you help me with this then i will be much thankful to you.

    Again thanks for this tutorial.

  8. 8 Enrico Zimuel said on luglio 20th, 2009:

    For instance you can use this code:

    Zend_Loader::loadClass(‘Users’);
    $user= new Users();
    $salt=substr(md5(mt_rand()),0,20);
    $md5Password=md5($salt.$password);
    $data= array (
    ‘username’ => $username,
    ‘salt’ => $salt,
    ‘password’ => $md5Password,
    ‘email’ => $email
    );
    if (!$user->insert($data)) {
    // @todo error
    }

  9. 9 Kalpesh said on luglio 27th, 2009:

    Hi, thanks for your reply,
    IT works
    But how can i store user name in session.
    I try to use Zend_Session_Namespace in submit Action before it redirects to home page

    $sess = new Zend_Session_Namespace(‘MyNamespace’);
    $sess->username =’Kalpesh’ ;

    but when i go home page i try to print session value called username.
    It doesn’t print it.

    Is there anothe way to store username in session.

    And again thanks for reply

  10. 10 Hari K T said on luglio 27th, 2009:

    Good one . Really a nice effort to help the newbie like me .
    Wish u good luck .
    Bye till we see for nxt post . :)

  11. 11 Joe Devon said on luglio 28th, 2009:

    Something else to consider….a pagewide or sitewide salt as well (depending on the situation). The reason being, if someone breaks into your database and has the random salts that were generated and stored in the dB, he can brute force the entire database at his leisure and pull out passwords…whereas adding a pagewide-salt into the code, he’d have to break into both the database and the source code. Just an extra layer of protection.

    P.S. I use Zend_Config_Ini to pull in the full form into Zend_Form, including the csrf, in a class I wrote to automate it….. Something like so:
    $config = new Zend_Config_Ini(APPLICATION_PATH
    . ‘/configs/’. $form . ‘.ini’
    ,APPLICATION_ENV);
    $formConfig[$form] = new Zend_Form($config->$form);

    Obviously I can’t use a function to set a random salt in the config. Is there any way of doing this as well as the ttl in the config without invoking any methods on the token as in your code above?

    Also, you’re time to live is set to 60. Is that minutes?! The ttl in my forms seem pretty short. Maybe I should increase it too. I wonder what the default is.

  12. 12 David said on agosto 3rd, 2009:

    Excellent tutorial, especially for a ZF noob like me. Very helpful, thanks.

  13. 13 Justin Hendrickson said on agosto 26th, 2009:

    One quick pet peeve correction:
    uniqid() creates a unique identifier. Hashing that value with md5() makes it “less unique”. http://us.php.net/manual/en/function.uniqid.php#91126

  14. 14 Enrico Zimuel said on agosto 26th, 2009:

    Thanks for your suggestion. I read only the first part of the php manual where is written that md5(uniqid(mt_rand(), true)); is better because difficult to guess. Using the md5 function there is a probability (very low) to generate the same output from different inputs.

  15. 15 Anurag Jain said on agosto 28th, 2009:

    Hello ,
    Let me go straight.

    Will this work on 1.9, if no then what are the possible changes?

    Anurag

  16. 16 Enrico Zimuel said on agosto 31st, 2009:

    I just tried with the last version 1.9.2 of the Zend Framework and it works fine.

  17. 17 Fozzy said on settembre 29th, 2009:

    Hey,

    I just stumbled up on your tutorial. Thanks for taking the time to write it.

    One quick question, is it safe to separate the salt from the password? Most security stuff I have seen mentions prepend/append the salt to the password or some cleaver similar thing.

    However, I don’t see how one could make an easy SQL statement that would work in this fashion, so I would guess a custom auth_adaptor would be needed.

    Curious as to there being any real reason do combine the salt with the password? Of if it’s doesn’t really provide any more security as someone who could hack your DB prob. hacked your source code and could figure out the salt, either way?

  18. 18 Chris UK said on settembre 30th, 2009:

    Finally a Auth example app that works for Zend Framework 1.9!! Thank you so much.

  19. 19 01kuzma said on settembre 30th, 2009:

    Hi!
    It’s great tutorial and thank you for it!
    I’m novice in ZF so maybe it will sound a bit stupid :)
    The checkSession() function checks if the user is authorized to see the secured content. So this function must be called each time from each controller?
    Or it should be called only from Auth controller in index action like this:
    public function indexAction()
    {
    $this-> checkSession();
    $this->view->auth=Globals::getConfig()->authentication->active;

    }
    Than you!

  20. 20 Enrico Zimuel said on ottobre 3rd, 2009:

    You don’t have to call the checkSession() in every controller. This function is called into the Plug-in Controller Initializer for every http request.
    In this way if someone is able to see a controller that means that he is authorized.

  21. 21 Enrico Zimuel said on ottobre 3rd, 2009:

    In my example i use a salt random value to increase the entropy of the password. This method is used to prevent dictionary attacks of the hashed passwords. If you see in the code i prepended the salt with the password and after i generate the MD5. This is built with the MySQL query: MD5(CONCAT(salt,’password’)) where ‘password’ is the password string value.
    If you want to improve more the security of the login system you can store the salt values in a different way and not in the same table.

  22. 22 Refactoring the ZF Secure Login example with Zend_Application - Zimuel’s blog said on ottobre 22nd, 2009:

    [...] this post i’m going to refactoring the ZF Secure Login application, that i provided in my previous post, using the new Zend_Application class, out from the version 1.8 of Zend [...]

  23. 23 Ankit said on novembre 5th, 2009:

    I think there is a problem with your statement:

    $this->_response->setRedirect(‘/index/login’)->sendResponse();

    in checkSession() function. Above method does redirect the user, but it also performs and underlying request. I believe you need to also have exit; afterwards.

  24. 24 Heshan Peiris said on novembre 6th, 2009:

    I’m new to zend framework and this article is really worth for me to get familiar with framework. Thank you for the article.

  25. 25 Enrico Zimuel said on novembre 6th, 2009:

    Hi Ankit,
    generally speaking your comment is right, but in this case there are not other instructions after the sendResponse() so it works fine without the exit statement.

  26. 26 Ankit said on novembre 6th, 2009:

    Hi Enrico,

    Thanks for your reply. Are you saying that Zend will not go ahead with processing the request after preDispatch() function returns?

    I am asking because I have seen that if I do not have “exit;” ZF actually processes the request and then forwards the user to the redirect location.

    This is a problem because, an authorized user can actually perform an action without actually logging in.

    I don’t know if this is due to not having exit; or having one of my settings set incorrectly.

  27. 27 Enrico Zimuel said on novembre 9th, 2009:

    Hi Ankit,
    if you use the sendResponse() you force the flow of the dispatcher sending the output to the browser.
    In this way an unauthorized user cannot execute actions without login.
    For more info: http://framework.zend.com/manual/en/zend.controller.response.html

  28. 28 Ankit said on novembre 10th, 2009:

    Hi Enrico,

    It is correct that the user will be eventually forwarded to the login page. However, I believe that ZF still processes the underlying request. Suppose instead of going to index, an unknown user (not logged in) was going to let’s say…

    /customer/deleteCustomer/?customer_id=12.

    Here, unless we exit(), ZF would execute the deletion and then forward the user to the login page.

  29. 29 Enrico Zimuel said on novembre 11th, 2009:

    Hi Ankit,

    you are absolutely right! Without the exit() an authorized user can execute actions.
    When i built it I was focused only on the view side, that is not rendered without authorization.
    It was my fault, sorry.
    I modified the source code of the example.
    Thank you very much Ankit for your suggestion.

  30. 30 Rodrigo Ferrari said on novembre 11th, 2009:

    Hello,

    Thanks for this greate share, but I´m not able to make it run. Just Copied all the files and add ZF 1.8 and tryed to run, the response is that 404 no found the page http://localhost/index/login And the response is The requested URL /index/login was not found on this server.

    How can I fix it?! I´m new to ZF.

    Thanks.

  31. 31 Snowcore said on novembre 11th, 2009:

    Thanks for the great tutorial!

  32. 32 Enrico Zimuel said on novembre 11th, 2009:

    Hi Rodrigo,
    I think you have a problem of configuration of your apache.
    Check these points:
    - the .htaccess file in the public folder
    - the configuration of apache about the mod_rewrite
    For more info: http://framework.zend.com/wiki/display/ZFDEV/Configuring+Your+URL+Rewriter

  33. 33 sasikumar said on novembre 17th, 2009:

    when run your secure login script the I got the follwing error

    Not Found

    The requested URL /index/login was not found on this server.

    the htacces file available in public folder

    here this our htacces

    RewriteEngine on
    RewriteRule !.(js|ico|gif|jpg|png|css)$ index.php

    please tell me what is the solutions

  34. 34 Enrico Zimuel said on novembre 18th, 2009:

    You have to check the configuration of your web server.
    If you use Apache you have to switch on the mod_rewrite and check the directive AllowOverride.
    Moreover if you use a virtual host you have to insert the directive AccessFileName .htaccess.
    For more info visit this link: http://framework.zend.com/wiki/display/ZFDEV/Configuring+Your+URL+Rewriter

  35. 35 Charles said on gennaio 21st, 2010:

    A really well written and useful tutorial which has helped me loads.

  36. 36 Gins said on febbraio 17th, 2010:

    HI…

    A good article……

    I do have a doubt..i am using zend-auth for authentication.It works fine.what i want to do is increase timeout.Since the default session is used i can’t or i dont know how to increase the timeout..Any help would be great!!

    I tried to initalize a object of Zend_auth and set expiration seconds to custom didn’t wrk. Also set gcmaxlife time…no way??please help me…

  37. 37 Enrico Zimuel said on marzo 1st, 2010:

    Hi Gins,
    in order to modify the timeout of the session you can use the method setExprirationSeconds() of Zend_Session.
    You can specify to use a different Zend_Session namespace for the Zend_Auth adapter using this code:
    $auth->setStorage(new Zend_Auth_Storage_Session(‘someNamespace’));
    where $auth is an instance of Zend_Auth and ‘someNamespace’ is the name of your session.
    After this you can use the setExpirationSeconds to modify the timeout on the session.

  38. 38 pzajdel said on marzo 1st, 2010:

    Hi.

    First of all, excellent article. Many thanks for sharing.

    But of course there always has to be some ‘but’.
    Any idea why when I type a wrong url in browser:

    http://localhost/zfsecurelogin/public/whatever
    http://localhost/zfsecurelogin/public/index/whatever

    What I get is:

    “Fatal error: Uncaught exception ‘Zend_Controller_Dispatcher_Exception’ with message ‘Invalid controller specified (whatever)’ (…) ZendControllerDispatcherStandard.php on line 242″

    or

    “Fatal error: Uncaught exception ‘Zend_Controller_Action_Exception’ with message ‘Action “whatever” (…) thrown in E:phpZendlibraryZendControllerAction.php on line 485″

    Instead of:

    “HTTP/1.1 404 Not Found”

    ErrorController line 29.

    I’m using new ZF 1.10 and for some reason (guess different Apache configuration or no vhost?) I needed to change Initializer.php line 216 to:

    $this->_response->setRedirect(‘index/login’)->sendResponse();

    I would use some help here. So I hope thanks in advace! xD

    Btw. what do you think about using another table in DB just for salt and username (more often id) foreign key?

  39. 39 Enrico Zimuel said on marzo 1st, 2010:

    Did you tried to use the new example of Secure Login of my post “Refactoring the ZF Secure Login example with Zend_Application”?
    I tried the example with the ZF 1.10.2 and it works fine, with 404 Not Found managed by ErrorController.
    To manage the redirect issue you have to use a virtual host to point the root of the application or you can define a PHP constant and add it to the redicrect calls to manage the relative web path.
    Moreover you can use the RewriteBase directive in the .htaccess to redicrect the url in your folder path.

  40. 40 pzajdel said on marzo 1st, 2010:

    New example: not yet, but I will.

    As to the ‘not working’ ErrorController, I just figured it out. I didn’t pay attention to the lines 74-82 in Initializer. Comment does the thing of course. Silly me.

    Thanks again!

  41. 41 PHPGangsta said on marzo 18th, 2010:

    Nice article and some very important information in it.

    But I missed an important protection against brute-force-attacks. With your code it is still possible to write a little script which gets the html page (to get the token) and then try to login. Because there is no protection it is possible to do this from many computers (bot net?) and as fast as the internet connection is, so the attacker could try hundreds of usernames/passwords per second.

    Would be nice if you could add some information there.

  42. 42 Enrico Zimuel said on marzo 18th, 2010:

    In order to improve the security of this Secure Login against brute-force-attacks you have to store the IP address of the request and block it for a while after n failed login.
    This is the most common approach to limit the brute-force-attacks. You can use the $_SERVER['REMOTE_ADDR'] variable of PHP to get the IP address and store it in a temporary file/db with a timestamp. For instance, you can block the access after 5 failed login for 5 minutes and increase this time if the same IP fails again.

  43. 43 DNoe said on maggio 10th, 2010:

    Greetings -

    I am curious as to why you do not use numerical user IDs. Almost all implementations of user login systems I have worked on use a unique user ID as a primary key. I realize that the username field will be unique, so perhaps this is more of a habit than a necessity. Is this eliminated here for simplicity or is there more to it than that?

    Thanks for the tutorial!

    DNoe

  44. 44 Enrico Zimuel said on maggio 11th, 2010:

    I used the username as primary key because it’s a unique key for the table. In general if I have a table with a field that is unique I use this field as primary key. In this way you can implement the data integration in a easy way. Think about the duplication of id, in this way the DBMS will check it and not the PHP application.

  45. 45 Luca said on maggio 20th, 2010:

    Hi! Thanks for the post… App works fine for me but I have a problem. I run the app in a subdirectory with no virtual host. I ask you for a little help, maybe you can resolve my trouble quickly….

    thats my htaccess in public folder…

    RewriteEngine on
    RewriteBase /workspace/zfsecurelogin/public/
    RewriteRule !.(js|ico|gif|jpg|png|css)$ index.php

    my app run if I specify controller/action like this:

    http://localhost/workspace/zfsecurelogin/public/index/login

    if I open http://localhost/workspace/zfsecurelogin/public/
    _redirect takes me to localhost with 404 error.

    >>>> BAD >>>>> http://localhost/index/login

    I added the setBaseUrl in Front controller, mod rewrite works and I tried to use also Zend Redirector Helper with no success… I tried to solve this but I can’t see the end….

    why this???

    Thanks

  46. 46 Enrico Zimuel said on maggio 20th, 2010:

    You have to use a virtual host to run the example in the correct way. For security reason you have to expose only the public folder to the web server. In your example you published also the application folder and this is dangerous.

  47. 47 Luca said on maggio 21st, 2010:

    Ok Enrico, thank you for advice.

  48. 48 ninon said on agosto 26th, 2010:

    Hi!!
    thanks for your article, it’s been so useful to me. I’m novice in zend and I’ve a doubt. I have a vhost configuration, the host name is ‘testhost’, and I don’t understand how the request http://testhost is redirected to http://testhost/index/login. Who is calling the loginAction() action that renders the view?
    thanks in advanced!

  49. 49 seracettins said on maggio 1st, 2011:

    thanks for your great sharing. Very simple , very flexible and comprehensible code.At first, every new developers can use this solution.

  50. 50 emmanuel said on giugno 8th, 2011:

    Thanks Enriko.
    i new in php i got this error when after i login

    Fatal error: Uncaught exception ‘Zend_Db_Statement_Mysqli_Exception’….

    in ../zfsecurelogin/library/Zend/Db/Statement/Mysqli.php on line 215

  51. 51 Alexander said on settembre 22nd, 2011:

    Some additional information about ZF and CSRF you can find in http://plutov.by/post/zf_csrf

  52. 52 J. Pang said on novembre 16th, 2011:

    Hi there,

    thanks for a wonderful tutorial.
    however, the soucecode zip file is corrupted! Please have a check.

    JP

  53. 53 Matt said on novembre 25th, 2011:

    Thanks for the tutorial. Very informative.

    Per JP’s comment, the source code compressed file is corrupted. Can you please repost?

    Thanks again!

  54. 54 Alexander said on dicembre 14th, 2011:

    Good article about ZF and CSRF http://plutov.by/post/zf_csrf


Leave a Reply

  • Anti-Spam Protection by WP-SpamFree