Yii Framework Forum: Registration Forms little tips - Yii Framework Forum

Jump to content

  • (3 Pages)
  • +
  • 1
  • 2
  • 3
  • You cannot start a new topic
  • You cannot reply to this topic

Registration Forms little tips Rate Topic: ***** 11 Votes

#1 User is offline   whoopass 

  • Advanced Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 119
  • Joined: 27-May 09
  • Location:Ajax, Ontario, Canada

Posted 10 July 2009 - 02:57 PM

[Edit] fixed up and all processing well :) . In a span of 5 to 10 minutes you can now create and customize a registration setup for your site to your likings :) .

Just figured I'd post a couple tips to help people out because in the process of establishing my own registration form which works nicely now, I had to read through a variety of other threads to come to conclusions.

Ok to start, my registration form acts as an AR class model. Now to be honest, I don't know why anyone would want to make a registration class extending CFormModel unless you're not using a database.

You just don't get the same options not to mention the easier efficiency in coding.

So to lay out what I was doing:

I was getting users to upon registering provide...

A username, that must be unique, between x length, and converted to lowercase.
A password, that must be converted to md5 hashing and then compared to a repeated password value also converted to md5 (however this 2nd password is not located in the database)
An email that must be unique and a valid email
A secret question for forgotten password usage (later) that is converted to lowercase and checks if a "?" is at the end, if false append a "?" at the end
A secret answer for forgotten password usage that is converted to lowercase.

Ok so it's simple enough but here are some things to understand.

The reason why I would suggest using AR as opposed to CFormModel is because AR allows you to use more rule sets and as such, validation is done automatically, you don't have to query the db, nor do you have to catch the exceptions and all that heavy work.

Also, in my case having to change all these inputs to valid inputs before saving, well by using the AR model and using the rules I do, it auto validates those values for me and then if that passes true, it will save all the values to the database. Simple - pretty much 3 or 4 lines of code.

Another thing to note, before figuring out filter rules I was trying to use $model->username=strtolower($model->username); to edit my values along with using md5 on password and strtolower for the other values. While this does work, you cannot and I will repeat this, you <strong class='bbc'>cannot</strong> modify a value after validation has been processed. So you'd have to punch in all that tedious coding for every input field (including the 2nd password into a hash) just to make sure the validation passes and then saves. Unfortunately that means you couldn't just validate user input and then modify the needed values as you see fit before saving. (ie: comparing the two passwords the user submits before converting only the one to md5, and then trying to convert the password to md5 after they validate - this will deny the save process to the db, even though it will look like it went through). So the solution? Just use AR Rules :)

Why not just use the rules defined and then have validation do all the work for you? Here's a look:

---> Please note that within your model class, any values that you define for registration that are not located in the database table you must define as variables within the class <--- See the top of the user model rules code

My User model rules:
<?php
/*
	 * define repeat password variable & verify code variable
	 */
	public $password2;
	public $verifyCode;

...

return array(
                        /* Due to a fellow user's observation this note is being 
                        * posted for users. Using filters, you can combine certain
                        * variables together to simplify the processing. Where this
                        * was pointed out was for the question and answer filtering
                        * to lowercase. In fact the username as well. Technically
                        * the more efficient way is to process them as followed:
                        * array('username,question,answer', 'filter', 'filter'=>'strtolower'),
                        */
			array('username','length','max'=>32),
			// convert username to lower case
			array('username', 'filter', 'filter'=>'strtolower'),
			array('password','length','max'=>64, 'min'=>6),
			array('password2','length','max'=>64, 'min'=>6),
			// compare password to repeated password
			array('password', 'compare', 'compareAttribute'=>'password2'), 
			array('email','length','max'=>256),
			// make sure email is a valid email
			array('email','email'),
			// make sure username and email are unique
			array('username, email', 'unique'), 
			array('question','length','max'=>256),
			// convert question to lower case
			array('question', 'filter', 'filter'=>'strtolower'),
			array('answer','length','max'=>128),
			// convert answer to lower case
			array('answer', 'filter', 'filter'=>'strtolower'),
			array('username, password, password2, email, question, answer, verifyCode', 'required'),
			// verifyCode needs to be entered correctly
			array('verifyCode', 'captcha', 'allowEmpty'=>!extension_loaded('gd')),
		);
?>


Now in discovering a flawed issue with using the filters to hash a password, I determined that the filter method of hashing does not hash properly – either it adds a salt or trims the string or something. At any rate during discussion with other wise members ;) I was informed that it’s probably not a good idea to do this anyway and rightly so. Let’s assume that you did hash a password before a validation occurred – and then by odd chance something happens and the validation is returned to the user because it didn’t go through. That hashed password is not information you want your users to have access to. Since this is an undesired behaviour – it’s wiser to use the beforeSave() setup overriding the default function. Which is what I did.

<?php
/**
	 * @return actions to perform before saving ie: hash password
	 */
	public function beforeSave()
	{
		$pass = md5($this->password);
      	$this->password = $pass;
      	return true;
	}


Now some people believe that a simple md5 hash is not enough and that’s fair, considering you are in control of how secure your site is, so why not make it more secure. I found an excellent example of a simple salt addition to a hashed password and that’s what I used instead. You’re welcome to use it – it just means you have to remember to use it everything you access the password information.

Just replace the appropriate code in beforeSave() with this:
<?php
$pass = md5(md5($this->password).Yii::app()->params["salt"]);
$this->password = $pass;


Now one thing important to note, if you do chose to do this, you need to go to your UserIdentify Component and modify the line there as well that authorizes the user login access. Since you’re now using a different hashing method you have to make sure you’re whole site is compliant with this or else you’ll have dysfunction throughout your site, and you wouldn’t want that ;)

UserIdentity Componet » /protected/components/UserIdentity.php
<?php
// change this....
else if(md5($this->password)!==$user->password)

// to this...
else if(md5(md5($this->password).Yii::app()->params["salt"])!==$user->password)


And that’s that :)
Now to continue...

My SiteController actionRegister() method
<?php
public function actionRegister()
	{
		$form=new User;
		// collect user input data
		if(isset($_POST['User']))
		{
			$form->attributes=$_POST['User']; // set all attributes with post values
			
			// NOTE Changes to any $form->value have to be performed BEFORE $form-validate() 
			// or else it won't save to the database. 
			
			// Check if question has a ? at the end
			$last = $form->question[strlen($form->question)-1]; 
			if($last !== "?")
			{
				$form->question .= '?';
			}
			
			// validate user input and redirect to previous page if valid
			if($form->validate())
			{				
				// save user registration
				$form->save();
				$this->redirect($this->render('finished',array('form'=>$form))); // Yii::app()->user->returnUrl
			}
		}
		// display the registration form
		$this->render('register',array('form'=>$form));
	}
?>


As you can see I used a finished page render just to simulate the registration process working - however later I'll be modifying that to make it like an email activation link.

I also commented about changing values BEFORE validating them and there you see the example I use to append the ? to the question value. You don’t see the beforeSave() function being called because Yii automatically calls that function, part of the basic model script. So stuff like beforeSave, afterSave, beforeValidation and default functions like that will run before those given actions – you don’t need to call them twice, or you’ll likely screw up your coding somewhere ;) .

Basically the code works like this:
$variable = define new model instance
if condition -> check if form values ($_POST) are set
true -> proceed to set all $variable->attributes to the values set in $_POST
----->make any changes you need to any attribute before validating
----->call the $variable->validate() function in an if condition
---------->If Validation passes then call $variable->save() and process the url redirection or new page render.
---------->If Validation fails, it returns to the registration page render with the errors.
if the post values condition is false (they aren't set) just render the registration page as normal.

And Voila, very simple.

So with that, aside from just customizing how the register form looks which I'll post for people as well anyway, that was basically all I had to do to set up a user registration. Has to be the easiest user registration app I've ever done, for all the complexity it includes.

Here's the rendered view:

<?php $this->pageTitle=Yii::app()->name . ' - Register'; ?>

<h1>Register</h1>

<div class="yiiForm">
<?php echo CHtml::beginForm(); ?>

<?php echo CHtml::errorSummary($form); ?>

<div class="simple">
<p class="hint" style="margin-left:70px;">
Please Note: 
<br/>
Your login name, Secret Question and Answer will all be converted to lower case characters.
<br/>
This is to insure later use of these are not case-sentively forgotten.
</p>
<br/>
<p class="hint" style="margin-left:70px;">
Note: Your login name must be unique and a max of 32 characters.
</p>
<?php echo CHtml::activeLabel($form,'username', array('style'=>'width:150px;')); ?>
<?php echo CHtml::activeTextField($form,'username') ?>
</div>

<div class="simple">
<?php echo CHtml::activeLabel($form,'password', array('style'=>'width:150px;')); ?>
<?php echo CHtml::activePasswordField($form,'password') ?>
</div>

<div class="simple">
<?php echo CHtml::activeLabel($form,'password2', array('style'=>'width:150px;')); ?>
<?php echo CHtml::activePasswordField($form,'password2') ?>
</div>

<div class="simple">
<?php echo CHtml::activeLabel($form,'email', array('style'=>'width:150px;')); ?>
<?php echo CHtml::activeTextField($form,'email') ?>
</div>

<br/>

<div class="simple">
<p class="hint" style="margin-left:70px;">
Note: Your secret question that will pop up for you to reset your password (if needed).
</p>
<?php echo CHtml::activeLabel($form,'question', array('style'=>'width:150px;')); ?>
<?php echo CHtml::activeTextField($form,'question') ?>
</div>

<div class="simple">
<p class="hint" style="margin-left:70px;">
Note: Your secret answer you'll need to type in when asked your secret question.
</p>
<?php echo CHtml::activeLabel($form,'answer', array('style'=>'width:150px;')); ?>
<?php echo CHtml::activeTextField($form,'answer') ?>
</div>

<br/>

<?php if(extension_loaded('gd')): ?>
<div class="simple">
	<?php echo CHtml::activeLabel($form,'verifyCode', array('style'=>'width:150px;')); ?>
	<div>
	<?php $this->widget('CCaptcha'); ?>
	<?php echo CHtml::activeTextField($form,'verifyCode'); ?>
	</div>
	<p class="hint">Please enter the letters as they are shown in the image above.
	<br/>Letters are not case-sensitive.</p>
</div>
<?php endif; ?>

<div class="action">
<?php echo CHtml::submitButton('Register'); ?>
</div>

<?php echo CHtml::endForm(); ?>

</div><!-- yiiForm -->


Any questions feel free to post otherwise hope this helps people :)
2

#2 User is offline   whoopass 

  • Advanced Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 119
  • Joined: 27-May 09
  • Location:Ajax, Ontario, Canada

Posted 10 July 2009 - 03:30 PM

I just want to post a little issue I've found.

The registration page does an MD5 hash, however once it's done you can't login properly the password is invalid. So just for kicks in another field on the same row of data I tried to hash the password straight in the DB of mysql. And to my surprise it was a different hash. So When I figure out this issue I'll post a solution.

but just an idea - the password I used was mypassword, looks like using the yii filter md5 gives
this hash: a89254264ad2beee785797ae7fbc4ffc
And using mysql md5 gives
this hash: 34819d7beeabb9260a5c854bc85b3e44
0

#3 User is offline   whoopass 

  • Advanced Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 119
  • Joined: 27-May 09
  • Location:Ajax, Ontario, Canada

Posted 11 July 2009 - 10:46 PM

Ok established a proper solution - refer to the original post for the guides but to brief everyone, it's not recommended to do those sort of actions (hashing passwords) before validation because if an error or validation occurs and it returns that information to the user, this is not a desired behaviour. So the simple case is to override the default beforeSave() function in your model.
1

#4 User is offline   konichiwa 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 23
  • Joined: 03-August 09

Posted 03 August 2009 - 08:50 PM

your tutorial very good. I look it and do the same. :) i got it . thanks you
0

#5 User is offline   whoopass 

  • Advanced Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 119
  • Joined: 27-May 09
  • Location:Ajax, Ontario, Canada

Posted 03 August 2009 - 08:53 PM

View Postkonichiwa, on 03 August 2009 - 08:50 PM, said:

your tutorial very good. I look it and do the same. :) i got it . thanks you


Thanks, right now I'm doing some reading up on Ajax and ActionScript but after I'm done with those I'll be continuing my exploration with Yii and I'll hopefully go through a nice tutorial on how to build the whole process for a decent site with average features. :)

Glad it helped though :)
0

#6 User is offline   adrian 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 14
  • Joined: 04-August 09

Posted 05 August 2009 - 01:24 PM

I've followed the exact steps and put public $password2; and public $verifyCode; in the User model...except it seems when I do $form->attributes=$_POST['User']; in the controller, it doesn't actually pass the variables $password2 and $verfiyCode.

Any idea why? ???


// Site Controller
	....
	public function actionRegister()
	{
		$form=new User;
		// collect user input data
		if(isset($_POST['User']))
		{
			$form->attributes=$_POST['User'];
			// validate user input and redirect to previous page if valid
			// print_r($form);
			
			if($form->validate())
			{
				$form->save();
				$this->redirect(Yii::app()->user->returnUrl);
			}
		}
		// display the login form
		$this->render('register',array('form'=>$form));
	}
	....


// User Model
	....
class User extends CActiveRecord
{

	public $confirmPassword;
	public $verifyCode;

....
....

	public function rules()
	{
		return array(
			array('username','length','max'=>32),
			array('username, email', 'unique'),
			array('password','length','max'=>128),
			array('password', 'compare', 'compareAttribute'=>'confirmPassword'),
			array('email','length','max'=>128),
			array('email','email'),
			array('username, password, confirmPassword, email', 'required'),
			array('verifyCode', 'captcha', 'allowEmpty'=>!extension_loaded('gd')),

		);
	}

....
....

	public function attributeLabels()
	{
		return array(
			'id'=>'Id',
			'username'=>'Username',
			'password'=>'Password',
			'confirmPassword'=>'Confirm Password',
			'email'=>'Email',
			'profile'=>'Profile',
		);
	}


// Register View
....
<?php echo CHtml::activeLabel($form,'confirmPassword'); ?>
<?php echo CHtml::activePasswordField($form,'confirmPassword') ?>
....

0

#7 User is offline   smclark89 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 119
  • Joined: 20-June 09
  • Location:NY, USA

Posted 05 August 2009 - 02:45 PM

Great tutorial! I'm implementing it now.

Two questions though, out of curiosity:

1) Is there any reason you used
// convert question to lower case

                        array('question', 'filter', 'filter'=>'strtolower'),
                       ...
                        // convert answer to lower case
                        array('answer', 'filter', 'filter'=>'strtolower'),


rather than

 array('answer,question', 'filter', 'filter'=>'strtolower'),


2) This is a topic that seems to be debated all around, but why use md5 for encryption instead of sha1? From what I gathered, sha1 is more secure, but the people making Yii are vastly more clever than me (and they use md5), so I'd like to hear their reasoning :)
0

#8 User is offline   adrian 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 14
  • Joined: 04-August 09

Posted 05 August 2009 - 03:14 PM

sclark, have you managed to implement this and have it fully working?

Also would it be possible to not populate the password field if there is an error raised?
0

#9 User is offline   whoopass 

  • Advanced Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 119
  • Joined: 27-May 09
  • Location:Ajax, Ontario, Canada

Posted 05 August 2009 - 04:52 PM

sclark said:

Great tutorial! I'm implementing it now.

Two questions though, out of curiosity:

1) Is there any reason you used
// convert question to lower case

                        array('question', 'filter', 'filter'=>'strtolower'),
                       ...
                        // convert answer to lower case
                        array('answer', 'filter', 'filter'=>'strtolower'),


rather than

 array('answer,question', 'filter', 'filter'=>'strtolower'),



There is no benefit at all, in fact the way you have shown would be the more "code efficient" way. So I'm going to change that up. Originally I was playing around with the values around the filters to see what all the filters can do, and as a rule of thumb, when you're debugging problems you should always look to solve one step at a time and if you can isolate changes, that works better. It's also easier for people to see it being done twice with two different values, they can grasp "oh ok, so he's making this lower and that lower" No one might understand why, but knowing that you implement in multiple places (when you could just call it all in one) can of reiterates what you're doing. So in a way, the benefit is just for the readers to question why it was done (just like you have).

My method is not the most efficient there is or if it is then that there always will be. And you'll find as you expand on this tutorial (my next step is implement a verification system and that), it'll get more complex and you'll have to change around what you do when. But this is something simple that works and gets you familiar with Yii and I did the research into the problems and posted proper reasons for how to avoid and fix them. This leads me to your next question

sclark said:

2) This is a topic that seems to be debated all around, but why use md5 for encryption instead of sha1? From what I gathered, sha1 is more secure, but the people making Yii are vastly more clever than me (and they use md5), so I'd like to hear their reasoning :)


MD5 on the short, was a more "effective" security style because it couldn't be decrypted. Which meant that it was literally up to a hacker to obtain a hash code, crack it, then use that key everywhere on your server to crack all other areas and codes, which at a 1 in 65 million possibility of a collision (I think that's roughly the number) it was a fairly secure way of doing things because you can't decrypt a hash. But then dictionary attacks built up and now MD5 (plain) security is compromised fairly easy for those who know what they are doing and have the processing power (which gets easier every day computers increase in their hardware power).

SHA1 can be a far more effective tool now because the encryption spreads over a longer array of characters (meaning you can group more patterns in larger or smaller groups depending on the size of the password) and make it more of a challenge (on it's own) to crack. But it's style possible and if you purely just use SHA1, it's no more effective than MD5, in fact I would deem it less effective because someone could actually just decrypt what you have secured on your server and then have the plain text version without having to work through a dictionary to get it and now can manipulate your site with that credential information and not have to "hack" just pop in your admin password and lock you out through proper manipulation, making everything seem ok but it's not.

That's why when looking over through the forums and I came across a simple yet more effective solution, I used it. Now with this setup there's a couple things to note:

md5(md5($this->password).Yii::app()->params["salt"]);

This makes the MD5 hash far more effective, because there is likely not many if any dictionary attacks that will be able to take a word as a hash, have it hashed again with a salt of some random setup by you or the site. And the fact of that 1 in 65 million collisions kicks in now, because you're not bound to find two duplicating hashes, although it has happened, it doesn't happen often, specially when you use a different hashing formula.

Now if you replaced SHA1, you actually wouldn't necessairly make the encryption stronger for 2 reasons. 1 it's an encryption, which means it can be reversed and this is always a liability specially if you're unsure of how your data is travelling. For example, using a program like WireShark (or other packet sniffers) you can grab a communication line between a client and the host server making say a remote ftp connection, or logging to their account and you'll get their user name in the packet, and then the password "encrypted" which they can now work on to break.

Due to SHA1 having a fixed length, adding more characters from 1 SHA1 to the 2nd SHA1 actually makes it so the 2nd one just modifies each character, because you have the same length of a password at this point. It's still secure and hard to breach, but I'd say the odds of breaking that are more likely than with the MD5.

The reason? MD5 is "random" (nothing is truly random but lol ignore the point) but it's consistently random, meaning using the same formula to hash a string, you'll end up with the same hash. But how it gets it, is pretty random.

SHA1 is based off a formula for encryption and because of this, it is also usable in a formula of decryption. You have 2 places to attempt your "code breaking" to hack it. And since there are two, the two have to have something in common, this just makes it a little easier.

Think of it this way

Say you had H = B + C and that's a hash
But then you have E = B + C and D = C + A. This is an encryption, where one is to decrypt and the other to encrypt, but they share a common value, you could substitute these two equations and work out one bigger equation to try and crack the commonality.

I think I'll just leave it there, encryption isn't necessairly worst than hashing, and it's not insecure if you feel that, but everything eventually gets cracked, so you have to take more creative steps to using the formula. The reason why high tech places (like banks, government locations) have such good security is because they use sync timing algorithms that change every 60 seconds. Or some other time frame. In which case, the key that's presented to you on your login device, has to match that on the server, along with all your other credential information. It's pretty neat, I've seen it in action from a buddy of mine for remote work with a bank. It's pretty intense.

Hope that helps you a little bit. To get the best understanding, you'd have to look at MD5 and then SHA1 and then look up security articles. I would say a good place to start is wikipedia.

adrian said:

sclark, have you managed to implement this and have it fully working?

Also would it be possible to not populate the password field if there is an error raised?


What do you mean? Like make it so that the password feel clears in the event some error arises or in the event an error arises with the password field?

By the way to your previous problem did you ever fix it, if you're getting errors want to give any specifics? In order to use the additional variables (that aren't part of the User db table if you're using a db) you have to not just define them, but give them characteristics. Defining them via public allows you to use the terms, but then in the rules and attributes and that you'll have to add them, which it looks like for the more part you did. However in your case you'll want to define the confirmPassowrd like I did with password2

array('password2','length','max'=>64, 'min'=>6), you might not have a minimum setup, but the length should be considered and equal to what you want your password to be. There shouldn't be any issues.

But the error you're more than likely having is that public terms you define, are not considered safe attributes by default (like the way the Yii framework) sets up all the values it imported from the db table.

Here's my code

public function attributeLabels()
	{
		return array(
			'id'=>'Id',
			'username'=>'Username',
			'password'=>'Password',
			'password2'=>'Repeat Password',
			'email'=>'Email',
			'question'=>'Secret Question',
			'answer'=>'Secret Answer',
			'verifyCode'=>'Verification Code',
		);
	}

	public function safeAttributes()
	{
		return array(
			'username, password, password2, email, question, answer, verifyCode',
		);
	}


Make a safeAttributes function to overide the default, only place values you want to be safe for the user to manipulate (ie you wouldn't want a user ID tampered with.) and make sure you add the verifyCode and your confirmPassword. That should fix any issues you get.

Also looks like you're missing a semi colon the the password attribute of the confirm password text box in the view file.

Hope this helps everyone.
0

#10 User is offline   adrian 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 14
  • Joined: 04-August 09

Posted 05 August 2009 - 05:47 PM

Your post has certainly helped me. Adding the safe attributes solves the problem indeed, and as you say, I should consider the length of password2.

Thanks!
1

#11 User is offline   smclark89 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 119
  • Joined: 20-June 09
  • Location:NY, USA

Posted 05 August 2009 - 06:38 PM

Thanks for the great response, that cleared both things up. I quickly implemented the registration form today and zero problems :) (it was something I expected to take a lot more time).

Just a suggestion though, you may want to have an email confirmation box, like the password. Actually, one more question. If we're making the question and answer lowercase, shouldn't we be making the email and username lowercase as well?
0

#12 User is offline   whoopass 

  • Advanced Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 119
  • Joined: 27-May 09
  • Location:Ajax, Ontario, Canada

Posted 05 August 2009 - 07:01 PM

View Postsclark, on 05 August 2009 - 06:38 PM, said:

Thanks for the great response, that cleared both things up. I quickly implemented the registration form today and zero problems :) (it was something I expected to take a lot more time).

Just a suggestion though, you may want to have an email confirmation box, like the password. Actually, one more question. If we're making the question and answer lowercase, shouldn't we be making the email and username lowercase as well?


Yeah I know, that's what makes this framework so sweet. I'm new to frameworks and coding in strickly OOP from a php standpoint. But the transition (while a little challenging being new to MVC and frameworks) has been remarkable with what you can accomplish and in such little code. My hat tips off to development because they really did a good job.

As for the questions, question I made lower case just for simplicity of using the filter and test purposes (because I'm modifying the string based on a condition, adding the ? at the end if the user didn't put it. So it was just a cool thing that you never know when you'll need.) But the answer is imperative so that later use in the event if a user forgets it's not like a 2nd password, it doesn't have the length restrictions nor the case complexity.

Remember, the secret question and answer is something I set up for further development of a forget password sort of gig (because I'm using MD5 hashing, I can't just send them the decrypted password, that's not possible). So in my situation, and all MD5's hashing, you need to replace the password with a new one. Most sites do some generations of a random pass and then get the user to alter it themselves. But that's double the work, why not just have the user change their own password and then you can implement that system to change the password anytime (kill two birds with 1 stone).

So because of that, I want it to be specific and secure to the user however I don't want it to be near next to impossible, should be something simple that they remember and don't need to worry about case-capitalization, makes it easier to compare later as well.

As for emails, they are not case sensitive so you don't need to worry about it, you can't have two emails with duplicate identities (even with caps) so Test@test.com and test@test.com are really the same email. Since it doesn't matter, putting the lower caps filter on it is just an added operation used for good method but unnecessary processing. It's not a big deal if you have a small site, but on a large site that may process emails all the time (say for a newsletter or even a check out application) you won't want to be doing mindless work that bottlenecks your server lol.

As for user names, I did include a filter to lowercase :). I don't bother with the email verification because that's meant to stop bots from auto submitting (since you can't usually copy and paste into those fields, and the bot can't "type"). But with the captcha setup, there's no need for it. This just becomes one of those extra added security items that add to what you're doing but once again, for a largely used site, might not be necessary to process.

These are all perfect examples of just how you want to implement your site and it's registration (among other security) features. You have to evaluate what you're doing and just how much to determine how secure you want to be. You also have to consider what you're holding to evaluate how secure you need to be. The tutorial is just a template that explains where to go about editing stuff and how easy it is to implement this. With this, you could probably develop other classes and options (like a project management tasks list or something) using the addition of the CRUD operations. Because you now have experience with forms, filters, classes and controllers.

By no means do you have to name everything the way I did, use the variables I did. And all that stuff. I'll hopefully soon get back to this project and progress it further with implementation of verification through email of account setups - which will also set up the forgot password at the same time.

My goal is to do that, then move on to a serious and customizable (from a user's standpoint, err admin) RBAC setup. RBAC is a much better way of applying security settings and permissions now a days for sites (all major forums have moved on to that) and a site with that customization would be very powerful.

You could take a template, already coded and just modify the permissions and groups based on what the end user wants. For a company, you might have the CEO, then Management, then employees, then clients sort of gig. For a development team you might have, Web Administration, project management, coders, artists (think maybe a video game). These different classes can then be later played to all areas of your site - setting up new schedules (on say a calendar application) or new tasks specific to their group (yet stuff like project management gets a copy of everyone's tasks and when they are complete). The list is endless.

Anyway getting a little off topic lol.
0

#13 User is offline   whoopass 

  • Advanced Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 119
  • Joined: 27-May 09
  • Location:Ajax, Ontario, Canada

Posted 05 August 2009 - 07:10 PM

Also figured I'd add I used strtolower for the username because it was already implemented in the system that way (with the blog demo), so it was just something to tact on and use. It's not necessary either however, it would cause conflicts with users having Sclark and sclark being two different users. So to make it simple, I just make them both one and the same (just the same way emails already work). This will help keep usernames unique, without capitalization being the "unique" part. I use a capital for my first letter in a lot of my usernames and it would run into problems on some sites (it has where it was not specific to the case and I as boggled lol) but as you can see, even the yii site makes it lower case.
0

#14 User is offline   Jaime 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 33
  • Joined: 15-August 09

Posted 16 August 2009 - 12:51 PM

Hello! very goofd tutorial but I have problem concerning password_repeat field.

The rule says that field is required, but even if I add a value to it, framework continues sending the error that password_repeat is required. Why is that?

this is the code for the model:

<?php

class Usuario extends CActiveRecord
{
	/**
	 * The followings are the available columns in table 'Usuario':
	 * @var integer $usr_id
	 * @var string $username
	 * @var string $password
	 * @var string $nombres
	 * @var string $apellidos
	 * @var integer $administrator
	 * @var string $email
	 * @var string $ultima_conexion
	 * @var string $fecha_creacion
	 * @var string $fecha_modificacion
	 */

    public $password_repeat;    
    
	/**
	 * Returns the static model of the specified AR class.
	 * @return CActiveRecord the static model class
	 */
	public static function model($className=__CLASS__)
	{
		return parent::model($className);
	}

	/**
	 * @return string the associated database table name
	 */
	public function tableName()
	{
		return 'Usuario';
	}

	/**
	 * @return array validation rules for model attributes.
	 */
	public function rules()
	{
		return array(
		    array('username, password, password_repeat, nombres, apellidos, email', 'required'),
			array('username','length','max'=>50),
			array('password','length','max'=>50, 'min'=>6),
			array('password_repeat','length','max'=>50, 'min'=>6),
			array('username', 'filter', 'filter'=>'strtolower'), 
			array('email','length','max'=>80),
			array('email','email'),
			array('password_repeat', 'compare', 'compareAttribute'=>'password'),
		);
	}

	/**
	 * @return array relational rules.
	 */
	public function relations()
	{
		// NOTE: you may need to adjust the relation name and the related
		// class name for the relations automatically generated below.
		return array(
		);
	}

	/**
	 * @return array customized attribute labels (name=>label)
	 */
	public function attributeLabels()
	{
		return array(
			'usr_id'=>'ID',
			'username'=>'Usuario',
			'password'=>'Contraseña',
		    'password_repeat'=>'Repita Contraseña',
		    'nombres'=>'Nombres',
		    'apellidos'=>'Apellidos',
			'email'=>'Email',
		    'administrador'=>'Administrador',
		);
	}
	
    public function safeAttributes()
    {
        return array(
            parent::safeAttributes(),
            'login' => 'username, password',
        );
    }
    
	/** 
     * @return actions to perform before saving ie: hash password 
     */ 
    public function beforeSave() 
    { 
        $pass = md5(md5($this->password).Yii::app()->params["salt"]);  
        $this->password = $pass; 
        return true; 
    }
}


Thanks
Jaime
0

#15 User is offline   whoopass 

  • Advanced Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 119
  • Joined: 27-May 09
  • Location:Ajax, Ontario, Canada

Posted 16 August 2009 - 12:58 PM

View PostJaime, on 16 August 2009 - 12:51 PM, said:

Hello! very goofd tutorial but I have problem concerning password_repeat field.

The rule says that field is required, but even if I add a value to it, framework continues sending the error that password_repeat is required. Why is that?

this is the code for the model:

  <?php
  
  class Usuario extends CActiveRecord
  {
  	/**
  	 * The followings are the available columns in table 'Usuario':
  	 * @var integer $usr_id
  	 * @var string $username
  	 * @var string $password
  	 * @var string $nombres
  	 * @var string $apellidos
  	 * @var integer $administrator
  	 * @var string $email
  	 * @var string $ultima_conexion
  	 * @var string $fecha_creacion
  	 * @var string $fecha_modificacion
  	 */
  
      public $password_repeat;    
      
  	/**
  	 * Returns the static model of the specified AR class.
  	 * @return CActiveRecord the static model class
  	 */
  	public static function model($className=__CLASS__)
  	{
  		return parent::model($className);
  	}
  
  	/**
  	 * @return string the associated database table name
  	 */
  	public function tableName()
  	{
  		return 'Usuario';
  	}
  
  	/**
  	 * @return array validation rules for model attributes.
  	 */
  	public function rules()
  	{
  		return array(
  		    array('username, password, password_repeat, nombres, apellidos, email', 'required'),
  			array('username','length','max'=>50),
  			array('password','length','max'=>50, 'min'=>6),
  			array('password_repeat','length','max'=>50, 'min'=>6),
  			array('username', 'filter', 'filter'=>'strtolower'), 
  			array('email','length','max'=>80),
  			array('email','email'),
  			array('password_repeat', 'compare', 'compareAttribute'=>'password'),
  		);
  	}
  
  	/**
  	 * @return array relational rules.
  	 */
  	public function relations()
  	{
  		// NOTE: you may need to adjust the relation name and the related
  		// class name for the relations automatically generated below.
  		return array(
  		);
  	}
  
  	/**
  	 * @return array customized attribute labels (name=>label)
  	 */
  	public function attributeLabels()
  	{
  		return array(
  			'usr_id'=>'ID',
  			'username'=>'Usuario',
  			'password'=>'Contraseña',
  		    'password_repeat'=>'Repita Contraseña',
  		    'nombres'=>'Nombres',
  		    'apellidos'=>'Apellidos',
  			'email'=>'Email',
  		    'administrador'=>'Administrador',
  		);
  	}
  	
      public function safeAttributes()
      {
          return array(
              parent::safeAttributes(),
              'login' => 'username, password',
          );
      }
      
  	/** 
       * @return actions to perform before saving ie: hash password 
       */ 
      public function beforeSave() 
      { 
          $pass = md5(md5($this->password).Yii::app()->params["salt"]);  
          $this->password = $pass; 
          return true; 
      }
  }
  


Thanks
Jaime


When you make your model class through the Yiic shell tool, it pulls the information from the database table (assuming it's setup properly and found). When it does this, the framework adds all the fields found to the default Safe Attributes function.

When you create new variables to the model class (like captcah or a secondary password, email whatever.) you have to not only define them publicly as well as set the rules for them but you also have to define them as an attribute that's safe for the user to manipulate. Otherwise the framework will not allow the user to change the value.

In your case, the user is not able to enter a value for password 2 because it's not deemed safe for the user to do so. However the page makes the 2nd password a requirement, thus it won't process the form until it's inputted. A death loop if you want some humour haha.

So to fix this just add your password_repeat to your safe attributes
 public function safeAttributes()
      {
          return array(
              parent::safeAttributes(),
              'login' => 'username, password, password_repeat',
          );
      }
 


If you have any other values the user needs to input on the form, make sure they are included in the safe attributes function you override, because by overriding it, all the normal allowed actions would be gone. For example, with a normal default safe attributes function, the ID (if you called it that) would be valid for the user to edit. Most of the time we don't want this. However when you set the safe attributes function by overriding the default, that action is no longer valid to the user unless you specifically state so in the safe attributes.

Hope that helps.
0

#16 User is offline   Q-efx 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 62
  • Joined: 12-April 09

Posted 16 August 2009 - 01:24 PM

Difference in md5 crypting in php and mysql, is that php use a hash key from the "word"

so for example something like this:

md5('secretpass');

is actually : md5('secretpass', 'secret');

And mysql uses a different "auto" hash.

so using this:

$pass = md5(md5($this->password).Yii::app()->params["salt"]);
$this->password = $pass;

is kinda useless. I would recommend to use sha1. Cause of less conflicts of the hash.

If you want a better system, you could use xtea ( A "fast" encryption ) and combine it with sha1. Cause using double, ( It doesnt matter which method,sha1 or md5 "doubled" ) dont produce better encryption.

And the most security risk is not "the database". ( However, you should crypt the password ) It is user input. And stupid passwords :)

I have created a "small" password checker extension:

http://www.yiiframew...hecker-for-yii/

It returns a "password" level :)
----------------------------------------------------
United-Regions-OFP

A social development project  ( In development ;-) )
----------------------------------------------------
0

#17 User is offline   whoopass 

  • Advanced Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 119
  • Joined: 27-May 09
  • Location:Ajax, Ontario, Canada

Posted 16 August 2009 - 07:07 PM

View PostQ-efx, on 16 August 2009 - 01:24 PM, said:

Difference in md5 crypting in php and mysql, is that php use a hash key from the "word"

so for example something like this:

md5('secretpass');

is actually : md5('secretpass', 'secret');

And mysql uses a different "auto" hash.

so using this:

$pass = md5(md5($this->password).Yii::app()->params["salt"]);
$this->password = $pass;

is kinda useless. I would recommend to use sha1. Cause of less conflicts of the hash.

If you want a better system, you could use xtea ( A "fast" encryption ) and combine it with sha1. Cause using double, ( It doesnt matter which method,sha1 or md5 "doubled" ) dont produce better encryption.

And the most security risk is not "the database". ( However, you should crypt the password ) It is user input. And stupid passwords :)

I have created a "small" password checker extension:

http://www.yiiframew...hecker-for-yii/

It returns a "password" level :)


I completely agree with you especially with the fact that the user is the weakest link - making weak passwords.

However the point of that double MD5 setup and salt is not to make the "hashing more secure" it's to ensure that what gets hashed is not an actual word but more a random value.

for example, the first MD5 on the password, if I chose my password as leafs67

then when the hashing happens in php, and takes a part of the word - dictionary attacks are effective against this.

However, the now hash of leafs67 added with a salt and then hashed again will generate a more random hash because it's based off a random word setup, which would be more effective in stopping dictionary attacks, because as far as I know, there are no listings of hash values in the dictionary as results from hashing (hence double hashing). But I could be wrong.

It's a little overkill for the security it gives but it's something simple enough that developers new at this stuff can understand and use it, and their site won't be completely open to abuse.
0

#18 User is offline   Q-efx 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 62
  • Joined: 12-April 09

Posted 17 August 2009 - 12:08 AM

Eh? I am not quite sure if I understand you right :)

But dictionary attacks comes through your websites. Not your database ( you should only allow one ip to access the db ) And as long users enter there data in "clear" forms. It doesn't matter how "good" your database data is encrypted.
----------------------------------------------------
United-Regions-OFP

A social development project  ( In development ;-) )
----------------------------------------------------
0

#19 User is offline   whoopass 

  • Advanced Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 119
  • Joined: 27-May 09
  • Location:Ajax, Ontario, Canada

Posted 17 August 2009 - 01:38 AM

View PostQ-efx, on 17 August 2009 - 12:08 AM, said:

Eh? I am not quite sure if I understand you right :)

But dictionary attacks comes through your websites. Not your database ( you should only allow one ip to access the db ) And as long users enter there data in "clear" forms. It doesn't matter how "good" your database data is encrypted.


I'm sorry about the length of the information (but it's condensed from about 6 or 7 articles of information). There's a lot to talk about in security. In fact, each key word used here about security could be talked about in it's own thread for a lengthly discussion.

At any rate, first off yes, dictionary attacks are performed through the site, that's why it's imperative that you protect your site from those situations. The first and foremost best protection? Making a long, random, strong password. It's a simple task that many user's hate doing (including myself) but it works and with the Yii framework you have the power to force the length of passwords easily, then using custom functions you can incorporate strength by characters used.

But it doesn't just stop there. There are two types of dictionary attacks, the pre-compiled ones, and then the on the go types. The on the go are shorter list that test many possibilities rapidly but usually just the common possibilities - they build specifically to a site, they are easier to deploy - but less likely to succeed. The pre-compiled ones are already built full listings of many possibilities and they usually apply and break instantaneously but they take a while to build up and load and this is only if their algorithms match those used on the server. If you're looking for more information I'd highly suggest wikipedia -> Dictionary Attacks and it's supporting articles.

That being said, based on the type of encryption / hashing you use, you have greater chances at protecting yourself. For example, hashing is a much more powerful security tool because it's a one way function. More over, it's designed to use heavier algorithms, thus being slower to compute and perform the algorithm. This ultimately means less number of possibilities for the attacker to guess in a given period of time.

Now by salting a hash, what you effectively do is create a different partial string to perform with the hash. This makes it so in terms of a dictionary attack, it's near next to impossible to deploy pre-compiled list effectively, and on the fly list would take next to forever because the algorithm would have to account for the salt change every time (since the salt would essentially make each hash unique in the system on top of the hash itself being unique).

But as time goes on, these methods of encryption and hashing will become vulnerable. The first stage is guessing passwords, then you step to dictionary attacks, after you have brute force attacks, after those the attacks become more efficient in deployment and results (precompiled list - rainbow tables). Not MD5 or SHA1 are safe in this regard, because they can be brute forced, and to some extent of their previous algorithms, even taken in more efficient means.

Now to get more technical, the brute force method has two factors, online and offline. A dictionary attack is like a form of brute force but not completely because it does not go through all possibilities. Online attacks are harder to manage because the attacker has to authenticate with the password. However if the attacker gains access to the hash, then they can perform an offline attack which works much faster and is more effective. So basically, make sure throughout your site, you don't pass the hash value of a password or anything you're hashing that's consistent throughout the site.

If you plan on using one time hashes, like a validation link for registration, or a drop out link for newsletters, whatever, use a different hashing / encryption scheme then you have throughout your site. If that data is not completely important to protect, a simple MD5 hash will do the trick, and be a waste of time to any attacker who attempts to gain access because once they do, what they gain is access to nothing and data of nothing. As such, for validation links and the such, its a good idea to use user names, emails, time, ip, whatever as the form of random string to hash as oppose to a user's password or other important credentials.

Now precomputation (pre-compiled listed) attacks are generally the most dangerous in terms of brute force. These are what are considered rainbow tables. Effectively, a system goes through every word in a dictionary and hashes it, different rainbow tables apply different hashing techniques and different dictionaries. Anytime a new method is discovered it is added to the list. Without effective usage of salts, there's no way to prevent this.

As for salts, the newer hashing algorithms apply them already, MD5 and bcrypt have 48 and 128bit salts. But adding a salt on top of that would only be adding to the cake. Salts make it so that when a password is hashed, it now has a unique string to the end of it and thus for every different user and every different password, a different salt is then created, making the hash itself even more random. This essentially prevents any possibility for an attacker to perform rainbow table attacks and making brute force unreasonable.

A salt that's over 20bits, can be effective on its own, combined with a lengthly password (26+ characters) it's virtually impossible (at todays standards) to break. But then once again you have to worry about the other areas to screw up, like making your hashing codes secure so they aren't transfered.

This brings in my effective point of double hashing, which you probably didn't get. Lets say we're transmitting data between us, and user C pulls a man in the middle attack to sniff packets. Maybe we were smart and used an encryption, like DES, to transfer our TCP and UDP packets. However bright that may be, User C is still able to come up with the public key to decode the messages. So now he has access to say the hash we passed through to authenticate.

If it's just one hash, then brute force, dictionary, rainbow tables whatever the case, it's highly possible User C will breach. However, a hash with a salt, is now 3 times the work because a different user name makes a different salt, a different password makes a different salt and a different hash makes a different salt. Tied in with the built in salting of hash algorithms now, that's key work load of math. If you hash that hash, you have an entirely different string that's resisted to rainbow tables (unless your algorithm is established - but with salts that's close to impossible) and brute force will have no effect because once it goes through every possiblity once, it only has the first hash, then it has to account for the 2nd hash, the salt and the salts within the hashes.

I'm not sure if that is clear enough to describe why a double hash would be effective but all that matters is understanding the key points. Newer technology is more secure. Proper usage of salts is highly effective. Keeping your site credentials from being displayed or transferred publicly is key.

For high end servers and sites, they deploy security tokens, which effectively shift / change password algorithms thus in order for any attack to work, they have to breach in the short time frame window before the shift occurs or else their work is useless. Examples of sites that use this, banks, government, anything with high end financial or personal data. Banks go so far as to have security tokens change in sync remotely with servers every 60 seconds. Good luck breaching that.
0

#20 User is offline   smclark89 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 119
  • Joined: 20-June 09
  • Location:NY, USA

Posted 17 August 2009 - 06:28 PM

I just had the terror of decrypting my password with 1 Google search and two clicks, and realized that it might be worth mentioning in here that there IS NOT ANY Yii::app()->params["salt"], you have to create it. Obvious, I know, but I fell for it.

That being said, after looking around a bit, it seems that it might be better to use the unique username for a salt, or in addition to the salt if you want to sleep REALLY well at night. It's not hard - anyplace you're going to be checking/encrypting the password, you're probably not going to be far from the username, IP, or some other unique-to-the-user value. This makes it just that much harder for anybody to get your passwords, since for them to be able to access large amounts of passwords they now have to look up the md5, look it up again, and figure out which is your random salt and which is the username (or other unique-to-user value) - all of which is going to make a computer's brain explode, hopefully :). Of course, then if they change their username, you have to change the password along with it.
0

Share this topic:


  • (3 Pages)
  • +
  • 1
  • 2
  • 3
  • You cannot start a new topic
  • You cannot reply to this topic

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users