47 Responses to Build a secure login with Zend Framework

Avatar

Zend Framework in Action » Secure login with Zend Framework

July 4th, 2009 at 8:23 am

[...] 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 [...]

Avatar

Joe Devon

July 11th, 2009 at 9:54 am

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.

Avatar

Enrico Zimuel

July 11th, 2009 at 6:02 pm

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.

Avatar

Michael Krapf

July 15th, 2009 at 7:38 pm

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.

Avatar

Enrico Zimuel

July 16th, 2009 at 10:30 am

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.

Avatar

Stephen Karl Lang

July 16th, 2009 at 7:45 pm

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

Avatar

Kalpesh

July 20th, 2009 at 10:28 am

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.

Avatar

Enrico Zimuel

July 20th, 2009 at 11:08 am

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
}

Avatar

Kalpesh

July 27th, 2009 at 1:48 pm

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

Avatar

Hari K T

July 27th, 2009 at 7:48 pm

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

Avatar

Joe Devon

July 28th, 2009 at 5:47 pm

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.

Avatar

David

August 3rd, 2009 at 8:39 am

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

Avatar

Justin Hendrickson

August 26th, 2009 at 2:59 pm

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

Avatar

Enrico Zimuel

August 26th, 2009 at 6:48 pm

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.

Avatar

Anurag Jain

August 28th, 2009 at 11:36 am

Hello ,
Let me go straight.

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

Anurag

Avatar

Enrico Zimuel

August 31st, 2009 at 5:29 pm

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

Avatar

Fozzy

September 29th, 2009 at 3:34 pm

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?

Avatar

Chris UK

September 30th, 2009 at 11:02 am

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

Avatar

01kuzma

September 30th, 2009 at 12:51 pm

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!

Avatar

Enrico Zimuel

October 3rd, 2009 at 8:41 am

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.

Avatar

Enrico Zimuel

October 3rd, 2009 at 8:50 am

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.

Avatar

Refactoring the ZF Secure Login example with Zend_Application - Zimuel’s blog

October 22nd, 2009 at 6:26 am

[...] 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 [...]

Avatar

Ankit

November 5th, 2009 at 6:22 pm

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.

Avatar

Heshan Peiris

November 6th, 2009 at 7:10 am

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

Avatar

Enrico Zimuel

November 6th, 2009 at 2:20 pm

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.

Avatar

Ankit

November 6th, 2009 at 7:24 pm

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.

Avatar

Enrico Zimuel

November 9th, 2009 at 1:59 pm

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

Avatar

Ankit

November 10th, 2009 at 11:54 pm

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.

Avatar

Enrico Zimuel

November 11th, 2009 at 9:47 am

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.

Avatar

Rodrigo Ferrari

November 11th, 2009 at 2:28 pm

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.

Avatar

Snowcore

November 11th, 2009 at 5:01 pm

Thanks for the great tutorial!

Avatar

Enrico Zimuel

November 11th, 2009 at 5:11 pm

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

Avatar

sasikumar

November 17th, 2009 at 4:11 pm

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

Avatar

Enrico Zimuel

November 18th, 2009 at 8:49 am

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

Avatar

Charles

January 21st, 2010 at 3:39 pm

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

Avatar

Gins

February 17th, 2010 at 3:36 pm

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…

Avatar

Enrico Zimuel

March 1st, 2010 at 9:09 am

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.

Avatar

pzajdel

March 1st, 2010 at 9:12 am

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)’ (…) Zend\Controller\Dispatcher\Standard.php on line 242″

or

“Fatal error: Uncaught exception ‘Zend_Controller_Action_Exception’ with message ‘Action “whatever” (…) thrown in E:\php\Zend\library\Zend\Controller\Action.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?

Avatar

Enrico Zimuel

March 1st, 2010 at 9:18 am

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.

Avatar

pzajdel

March 1st, 2010 at 9:34 am

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!

Avatar

PHPGangsta

March 18th, 2010 at 12:10 pm

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.

Avatar

Enrico Zimuel

March 18th, 2010 at 1:00 pm

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.

Avatar

DNoe

May 10th, 2010 at 2:41 pm

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

Avatar

Enrico Zimuel

May 11th, 2010 at 11:11 pm

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.

Avatar

Luca

May 20th, 2010 at 7:48 pm

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

Avatar

Enrico Zimuel

May 20th, 2010 at 9:52 pm

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.

Avatar

Luca

May 21st, 2010 at 12:38 am

Ok Enrico, thank you for advice.

Comment Form

About this blog

This is my personal blog about computer programming in PHP for business and passion. I'm Enrico Zimuel a Software Engineer since 1996. I work as Senior Consultant & Architect at Zend Technologies. For more info about me visit my web site.

PHP 5 Zend Certified Engineer Zend Framework Certified Enginner

My next conference


WebTech Conference 2010
follow me on twitter