Part 3 - Improving your forms authentication code and database access
I mentioned the use of forms authentication a few times in the previous parts of this FAQ series. In this post I'll cover some best practices to improve your forms authentication even further. Beside of that, I'll cover database best practices as well.
Tip 1: Encrypt but make sure it's encrypted the right way
As I mentioned earlier, stored passwords in the database you're using for the forms authentication on your site should be encrypted. The best way is to apply irreversible encryption by means of a hash (so there is no danger that an attacker can steal the key that was used to do symmetric encryption for example, so that all passwords can be retrieved). However, hashed values can be well-known (for example a hash of the password "password" will always be the same when MD5 is applied to encrypt it). To improve security further, you can take advantage of a technique called "salt". The idea is pretty simple, instead of encrypting the password itself, encrypt a derived string by creation some random "salt" and concatenating this to the password string. There are several possible solutions:
- The first one is pretty easy: just use a well-known "secret" in your application that defines the salt. For example, always concatenate "SomeJunkData" before the password and then hash it (and store that value in the database). When doing a login, put the salt on the password string again and compare the hashes. The main problem however is where to store this secret again (if an attacker can change the salt string that lives on the web server, new passwords will be stored incorrectly and nobody will be able to log in again since the hashed values will be different. Thus, this approach is not recommended!
- Every user has his salt string which is stored in the database. This is the best approach and requires an additional field in the database for each user that contains the salt. To create a salt value, use a random generator and create a salt string. The rest of the process is the same as described above, concatenate the salt and the password and hash it. However, there are some things to keep in mind:
- System.Random is a random generator but this one is not sufficient to get "encryption random values". The better random generator is the System.Security.Cryptography.RNGCryptoServiceProvider class that takes advantage of system entropy to create a really random value. Once you get the random bytes, use encoding to put the byte array into a string value (e.g. base-64 using Convert.ToBase64String(byte)).
- Authentication now requires to get an additional value from the database containing the salt. Some further tips on database access will be covered further.
Tip 2: Active Directory authentication using forms
If you've ever connected to an Exchange 2003 OWA website that has forms-authentication enabled, this is going on behind the scenes. Actually, you're presented with a web page that allows you to login to your mailbox using your AD credentials (which is a nice way to login if you're outside the corporate network, if SSL is used of course!). As far as I know, there are two ways to do this:
Tip 3: Secure database handling
A few tips:
- The first thing to do to secure your database is of course putting the connection string in a secure place (as explained earlier in the context of the aspnet_setreg.exe tool).
- Avoid SQL Injection attacks by avoiding the usage of string concatenation to build your SQL statements. An example is this: "SELECT * FROM Users WHERE ID=" + id contains a problem. When the id string contains for example: "1;DROP ..." (replace ... with some keyword stuff and a table name for example), the malicious code will be executed. Furthermore, by taking advantage of the "--" commenting characters for SQL Server some parts of a SQL string can be ignored. An example is this: "SELECT * FROM Users WHERE ID=" + id + " AND ..." (replace ... with some other stuff). When id now contains for example "1 --", the rest of the condition will be ignored. To avoid these kind of problems (remember "all input is suspect") use a SqlCommand with parameters.
- Use stored procedures wherever you can. This is worth the work to avoid security risks and to make performance better. Again, use a SqlCommand with parameters.
- SQL Injections attacks should not occur but if they (still) occur, make sure the risks are low. What I mean is this: if 90% of the website only reads data from the database, there's no need to have write-rights enabled for the account used. Don't ever use sa as the account to login to the database server! Don't use a edit-enabled account if it's not needed. It's better to have two different connection strings, one for admins and one for generic data readers, than having one string causing a higher risk. However, keep in mind to store the connection strings securely.
- Make sure you're using the validateRequest attribute on the @Page directive to check against other kinds of suspicious input from users (script injection).
Tip 4: Use SSL whenever possible
When using forms authentication (or basic authentication as well), do everything you can to use SSL to encrypt all traffic between the server and the client when communicating to do the login. You can do this by specifying requireSSL="true" on the <forms> tag in your web.config file. Keep in mind this only requires users to use HTTPS to log in (not for the rest of the site). If you want the whole site to be SSL-requiring, you should alter the configuration on the level of IIS. The installation of the SSL/TLS certificate is an IIS thing as well.
Tip 5: How to work with connections in ADO.NET?
Keep it as easy as possible and don't reinvent the wheel again. Microsoft is clear on this point: open up a connection once you need one and close it asap once you don't need it anymore. Don't keep connections alive (combined with the SqlCommand this can have an unexpected behavior since, depending on the used properties, the connection can be closed when the command completes). Of course, put the "connection close" code in the finally block of a large try...catch structure to avoid that connections keep live in case of an exception. Typical code looks as follows:
SqlConnection conn = new SqlConnection(dsn);
//use the db
catch (Exception ex)
or you can use the "using" C# feature as well since SqlConnection is IDisposable to make sure the connection is always closed:
using(SqlConnection conn = new SqlConnection(dsn))
Tip 6: What about connection pooling?
Connection pooling is a handy ADO.NET feature that you best start to use to control the number of connections on a SQL Server. The trick is pretty easy: a pool is defined through the DSN (data source name) string. The only thing you should take care of is that the DSN string should be the same throughout the whole application to ensure that the same pool is used (this is done when you're retrieving the DSN string from the web.config file, see earlier for encrypting this). An example is this:
string dsn = "server=localhost;uid=someUser;pwd=S0mePwd!;database=Northwind;Min Pool Size=5;Max Pool Size=10";
This will create a pool of minimum 5 connections and a maximum of 10 concurrent connections to the database server. Using the following dsn as well will create a second pool (!!!):
string dsn = "server=localhost; uid=someUser;pwd=S0mePwd!;database=Northwind;Min Pool Size=5;Max Pool Size=10"; //one additional space somewhere, can you find it?
A handy trick is the usage of the performance monitor on the database server to watch the number of connections that is made when the first request to the app occurs (there will be 5 connections at least).
Tip 7: Impersonation on certain code levels in an application only
Sometimes it's not needed that the whole web application runs in an impersonated context to do its work. This is the most common usage of impersonation and is as easy as putting an <impersonation> tag on the right place of the web.config file (see in an earlier post). However, you can do impersonation in the code as well. First of all, retrieve the WindowsIdentity of the current user:
WindowsIdentity id = (User.Identity as WindowsIdentity); //retrieved from the current HttpContext User object
Then, create an impersonation context for the by calling the Impersonate method on the id object:
WindowsImpersonationContext cntx = id.Impersonate();
All the following code will run under the context of the logged in user now. If you're done, call the Undo method on the context:
Typically, this code is placed in a finally block again to ensure the impersonation stops always.Del.icio.us
| Digg It