Type Here to Get Search Results !

Making Sense: ASP.NET Security

Create a Website

For this article, we’ll be using an ASP.NET Website. The same techniques for an ASP.NET Website apply to an ASP.NET Application, but a discussion of the project types is out of scope for this article. To create the Website, open Visual Studio 2008 (VS 2008) and select File -> New -> Website, which shows the New Web Site window in Figure 1.
Figure 1. Creating a New Website
Creating a New Website
As shown in Figure 1, I based my Web site off the File System, am using C# as the language, and specified the Web site name asAspDotNetSecurity. Click the OK button to continue and VS 2008 will create a new solution, shown in Figure 2.
Figure 2. A New ASP.NET Website
A New ASP.NET Website
Figure 2 shows that all you have is a Default.aspx page, a web.config file, and an App_Data folder to start with. To keep this article as simple as possible, we won’t be using Master pages or any other ASP.NET features. However, any time you begin a real site, you’ll want to set up Master pages first so that your security controls appear where you want.
Note: It’s possible that your screen might not look like mine, from Figure 2, since my VS 2008 environment is set to Visual C# settings and yours might be set to something else. If you want your screen to look like mine, delete this project and then select Tools -> Import and Export Settings and step through the wizard to reset your settings to Visual C#. Then re-create your ASP.NET Website, as described in previous paragraphs.
Now that you have a Website, you’ll need a database to hold security information.

Database Configuration

The first thing you’ll need to do is create a database to hold your security information. The database must be SQL Server. Any version of SQL Server from 2000 and later will work. You could create the database via SQL Server tools or VS 2008. If you don’t have SQL Server, perhaps because you are using only the .NET Framework SDK or an express version of Visual Studio,  For this article, I’ll create a database for through VS 2008 (not an express edition).
To create the database, with VS 2008, open the Server Explorer by selecting View -> Server Explorer. Perform a right-click on Data Connections, in Server Explorer, and select Create New SQL Server Database, shown in Figure 3. Click the OK button to create the database.
Figure 3. Creating a new Database
Creating a New Database
Next, you’ll set up the proper security objects in your database. ASP.NET ships with a utility named aspnet_regsql.exe that will automatically set up your database with necessary security objects. This utility will install tables for other ASP.NET features, such as profiles and health management, but those subjects are out of scope for this article. You can find this utility at %windir%\Microsoft.NET\Framework\v2.0.50727. Running the aspnet_regsql.exe utility, you’ll see the window in Figure 4.
Note: You might think it’s strange to use utilities in the .NET Framework 2.0 folder if you’re working with a later version of ASP.NET. However, later versions such as ASP.NET v3.5 still use the .NET 2.0 CLR.
Figure 4. ASP.NET SQL Server Setup Wizard Welcome Page
SQL Server Setup Wizard Welcome Screen

As you can see in Figure 4, the Welcome page explains what the wizard does. Click Next to select a Start Up Option, shown in Figure 5.
Figure 5. ASP.NET SQL Server Setup Wizard Start Up Options
SQL Server Setup Startup Options
In the Start Up Options, Figure 5, select Configure SQL Server for application services and click Next. You’ll see the window for Server and Database selection in Figure 6.
Figure 6. ASP.NET SQL Server Setup Wizard Server and Database Selection
SQL Server Setup Server and Database Options
Select the same server and database as configured earlier, Figure 3, and click Next. You’ll see a summary page and can click the Next button to configure the database. The next window you’ll see is a confirmation that the database has been set up. You can click the Finish button to close the wizard. Your database now contains many new objects, Figure 7.
Figure 7. Database Objects Created by the ASP.NET SQL Server Setup Wizard
Database Objects Created by the ASP.NET SQL Server Wizard
You generally don’t need to know the contents and schema of the objects shown in Figure 7, but should be aware of their presence. You can see that tables and stored procedures have aspnet_ prefixes and views have vw_aspnet_ prefixes. You should never delete these objects from your database. Finally, add the following connection string for the new database to your web.config file, which will replace the default<connectionStrings /> entry:
  <connectionStrings>
    <clear/>
    <add name="LocalSqlServer" connectionString="Data Source=.;Initial Catalog=AspDotNetSecurity;Integrated Security=True;Pooling=False"/>
  </connectionStrings>
The reason I named the connection string LocalSqlServer is because that is the name of the default connection string from machine.config that points to a local SQL Express database. Further, all of the provider configurations (membership, roles, etc.) in machine.config refer toLocalSqlServer as their connection string. Adding the clear element and then the add element removes the machine.config connection string and replaces it with this application’s connection string in the scope of this application only. Therefore, the default provider definitions use the connection string for the database that I’ve configured for this Web site.
Tip: To prevent mistyping the connection string, you can right-click on the database in Server Explorer, select Properties from the context menu, and then copy-and-paste the Connection String property in the Properties window.
With a Website and database in place, you’re ready to begin adding security to your site. I’ll be using the ASP.NET Configuration Tool to set up security. You can start this tool by selecting Website -> ASP.NET Configuration and you’ll see the ASP.NET Web Site Administration Tool in Figure 8.
Figure 8. Web Site Administration Tool Main Screen
Web Site Administration Tool Main Screen
Your first task is to configure a provider. This will specify the database for storing security objects. Click the Provider Configuration link to set up the database, shown in Figure 9.
Figure 9. Specifying a Provider for Security Services
Specifying a Provider for Security Services
While you have the option of selecting a different provider for each ASP.NET feature (i.e. membership, roles, profiles, etc.), it is more common to select a single provider, which is the approach this article takes. Click the Select a single provider for all site management data link, and observe the next screen in Figure 10, showing the AspNetSqlProvider.
Figure 10. The Default ASP.NET Security Provider
The Default ASP.NET Security Provider
Next, click on the Security tab to set up an administrator, roles, and site security settings, showing the screen in Figure 11.
 Figure 11. Configuring Security
Configuring Security
The easiest way to configure security is by using the provided wizard, so click on Use the security Setup Wizard to configure security step by step. Click Next to bypass the Welcome screen and you’ll see the Select Access Method screen in Figure 12.
Figure 12. Selecting an Access Method
Selecting an Access Method
The access method defaults to From a local area network, which means windows authentication. What you really want is ASP.NET Forms authentication, so select From the internet. This assumes that the Web site will be deployed to the internet and accessed from any platform, including non-Windows platforms. Therefore, ASP.NET Forms authentication would be appropriate. Windows authentication would be a better choice if the Web site was on a network inside of a company where users could use their domain credentials to access the site.
After you select From the internet, click on Next to view the Advanced provider settings screen. We’ve already discussed provider settings, so click on Next again for the Roles screen, shown in Figure 13.
Figure 13. Enabling Roles
Enabling Roles
ASP.NET Roles are managed via cookies and checking the box on the Roles screen, Figure 13. In most implementations, you will want roles. i.e. Admin, Staff, etc. Click Next to create a new role, shown in Figure 14.
Figure 14. Adding a New Role
Adding a New Role
Something you’ll want to do when setting up security is to create a role for administrators. Type in Admin, Figure 14, and click the Add Rolebutton. Add any other roles you might want for the site. Click the Next button to create a user, shown in Figure 15.
Figure 15. Adding a New User
Adding a New User
In addition to an Administrator role, you’ll also need to create the user account for an administrator. The UserName and Password are self-explanatory. The default password configuration is a minimum of 7 characters and one 1 non-alphanumeric character. You’ll receive an error if the password doesn’t meet the complexity requirements. Later, I’ll show you how to configure passwords, via the web.config membership element, to alter complexity requirements. By default, user names and emails must be unique. The Security Question and Security Answer are used to challenge the user when they are changing passwords. User status is Active by default, but you can uncheck the Active User box to create the user without allowing them to log in. Click the Create User button to add the user. Click Next to configure site security, shown in Figure 16.
Note: One of the things you should notice about creating the user, Figure 15, is the minimal amount of information required. This is inadequate because most applications will have additional information about users, such as contact information. Later, I’ll show you how to resolve this problem.
Figure 16. Configuring Site Security
Configuring Site Security
The key to understanding site security policy is “First match wins”. ASP.NET will read configuration settings from the top down. If it finds a match, then it stops looking and uses the configuration that matches the user configuration. You can configure policy based on users or roles. In most cases, you will build policy based on roles because it allows you to add and remove any number of users to and from those roles without changing policy. We’ll take the role-based approach in this article.
To configure Web site security policy, select the AspDotNetSecurity folder, click on the Roles option and ensure that Admin is selected, click on the Allow permission, and then click the Add This Rule button. Next, you're going to add another rule, by selecting the All Users option, select the Deny permission, and then click the Add This Rule button. You can see the two rules in the list below the input controls, showing Allow Admin first and then Deny [all]. This gives access to anyone in the Admin role to the entire site, but prevents all other users from accessing the site. You can add more roles above the Deny All Users entry to permit other roles to use the site.
Note: Remember the first match wins rule because if the Deny [all] were at the top of the list, no one would be allowed to log in; this is a common gottcha for new ASP.NET developers.
Going back to the folder list, where you clicked on the AspDotNetSecurity folder, if you opened lower level folders in the site, you could also configure security policy for any of those folders also. Security policy applies to a specified folder and all other folders below it, unless the lower level folders have their own security policy, which would override the security policy of the parent folder. Click Next and then click Finish.
You have one more task to complete, associating the Admin user with the Admin role. Click on the Security tab, select Manage Users, clickEdit Roles for the Admin user, and check the Admin box. Close the ASP.NET Web Site Administration Tool. Your site security is now set up and you can begin adding controls.

Overview of Login Controls

ASP.NET ships with a set of controls for managing security on a Web site. You can see what is available via the Toolbox, which you can open by selecting View -> Toolbox and then expanding the Login tab. Since the Toolbox is context sensitive, you’ll need to have an *.aspx page open in the designer to see its contents; you can double-click on Default.aspx in the Solution Explorer to do this. As shown in Figure 17, there are several Login controls available.
Figure 17: Login Controls
Login Controls
Table 1 describes the controls you see in Figure 17.
Table 1. ASP.NET Login Controls
Control NamePurpose
LoginAllows user to log in with user name and password
LoginViewContains templates that can be configured to display based on logged in user’s roles
PasswordRecoveryAllows users to recover passwords
LoginStatusDisplays whether user is logged in or logged out
LoginNameDisplays logged in user’s name
CreateUserWizardAllows registration of a user in security system
ChangePasswordLets a user change their password
You’ll see each of these controls used in this article, plus coverage of workarounds that are similar to the practical scenarios you implement every day.

Creating a Login Page

As it stands right now, no one can use your site. This is because security policy is set up to deny all unknown users, except for those in the Admin role. However, there isn’t a way for the site to know if someone is in the Admin role because logins haven’t been set up yet. We’ll fix that now.
First, we’ll set up a page, representing site content, and then create a login page. We’ll use Default.aspx as content that you might want to protect on a site. To let you know you’re on the right page, type “This is the default content page.” into the form area of Default.aspx.
Next, add a page named Login.aspx to your site. It is important that you use this name for your page because it is the default name that ASP.NET uses whenever a user must be authenticated. Although there are advanced options, which you can set via web.config, naming your login page as Login.aspx is normal.
Drag-and-drop a Login control from the Toolbox to the Login.aspx page and click on Design view, shown in Figure 18.
Figure 18. The Login Page
The Login Page
Right-click on Default.aspx and select Set as Startup Page. This will ensure that when we run the application that it tries to open Default.aspx first. However, it can’t do this because the site security policy is set to deny all unknown users, which will cause ASP.NET to redirect to the login page. After we login, ASP.NET will redirect to Default.aspx because it keeps track of what your original destination was so you can go straight there after logging in. Run the application and log in to observe this behavior.
If that doesn’t work, you might have missed a step in setting everything up, so review each step to make sure you don’t miss anything. Hopefully, I’ve identified enough hazards for you to avoid and make it this far successfully.

Changing Passwords

You might like implementing this; Create a new Web page named ChangePassword.aspx. Drop a ChangePassword control onto the page. That’s it.
To try it out, run the application, log on, change the browser address from Default.aspx to ChangePassword.aspx and fill in the form. Close and reopen the browser window and log in with your new password.

Registering New Users

Now that I’ve given you a break by showing a couple easy tasks, let’s move back into more complex topics by discussing proper registration and management of users. If you recall, during security setup, creating a user offered a minimal set of user information. In real applications, this is insufficient because you need to keep track of other user data, such as address and other contact info. Therefore, you need a workaround for both allowing new users to register and/or managing existing users.
The ASP.NET Configuration Tool is insufficient for this task because it only updates login information that ASP.NET needs. If you have other user information, it will never be created or updated because the ASP.NET Configuration Tool has no knowledge of this information. You can use the ASP.NET Configuration Tool for general site configuration and roles, but you should not use it for user management because it doesn’t keep all the user data in sync. I needed to create the initial user to log in, but that isn’t ideal because now that user doesn’t have updated contact info.
Looking for a built-in solution, one might be tempted to look at ASP.NET Profiles, which allows you to store personalization data for each user. The problem with profiles is that the default database configuration is a proprietary format with special configuration for tables. This makes it convenient for strongly typing the data and accessing it via profile properties, but it doesn’t work well if you need to query the table, which would be onerous because of the proprietary format. There are other options to extend profile and use normal tables, but it seems like too many gyrations to do something that should be simple. My preferred solution is to create my own Users table. Add the Users table, shown below, to your database:
CREATE TABLE [dbo].[Users](
    [UserName] [nvarchar](256NOT NULL,
    [Address] [nvarchar](50NOT NULL,
    [City] [nvarchar](50NOT NULL,
    [State] [nvarchar](50NOT NULL,
    [PostalCode] [nvarchar](20NOT NULL,
    [Phone] [nvarchar](50NOT NULL,
 CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED 
(
    [UserName] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF
ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ONON [PRIMARY]
ON [PRIMARY]

The benefit of this approach is that I can create a one-to-one relationship between the ASP.NET aspnet_users table and my own table. This required a little bit of knowledge of the aspnet_users table, even though I made an earlier remark about not needing to know much about the ASP.NET tables; this is an exception to the rule. The primary key, UserName, has a one-to-one relationship with the UserName field in theaspnet_users table. You might want to change the primary key to the UserId, which is a GUID, but that’s up to you. I’ll use UserName in this article.
Additionally, you’ll notice that I didn’t add any constraints to the Users table. This would cause problems with the ASP.NET infrastructure . Besides, the data is already inconsistent because the Admin user resides in aspnet_users, but not in Users. You can resolve this now by adding a record where the UserName is set to Admin and then fill in the rest of the data as required. You’ll have to add the record manually because we haven’t created an input form for it yet, but that will be resolved soon.
The next problem to solve is how to use the existing CreateUserWizard control to save this data to both the ASP.NET tables and the User table. Fortunately, the CreateUserWizard control has the ability to add templates with extra information. To get started, add a new page to the site named Register.aspx and drag-and-drop a CreateUserWizard control onto Register.aspx. Rename the CreateUserWizard control to RegisterWiz. Go to Design view, click on the Action list, and click Add/Remove Wizard Steps. You’ll see the Wizard Step Collection Editor, shown in Figure 19.
Figure 19. Adding a Step to the CreateUserWizard
Adding a Step to the CreateUserWizard
You should change the title of the step to User Info, use the up and down arrows to place it as the first item in the list (Figure 19), and click OK when you’re done. Next, select User Info from the CreateUserWizard control’s Action List. Populate the User Info template with fields for the Users table, as shown in Figure 20.
Figure 20. The User Info Template
The User Info Template
Just like any other user control with templates, you can drag-and-drop controls into the template on the design surface. In this case, I used Label and TextBox controls in a table. For the address, city, state, postal code, and phone number; rename the TextBox controls totxtAddresstxtCitytxtStatetxtPostalCode, and txtPhone; respectively. Now that you know how to add controls to collect custom data, the next section will explain how to properly save this custom data to the database.

Saving Custom User Data

In most data entry scenarios, you simply grab the data and use your data access technology of choice to save the data in the database. However, it isn’t as simple in this case because you have ASP.NET user data with a one-to-one relationship with the Users table. For data consistency purposes, you’ll need to ensure that both of these tables are updated in the same operation.
Your first thought might be to perform the update via a transaction, but that isn’t possible because the CreateUserWizard control saves the ASP.NET side of the data automatically and you don’t have a hook into that part of the process. i.e. there is not an event you can subscribe to or a virtual method you can override to perform this operation yourself. While you can’t perform an atomic operation, a compensation strategy would work.
Examining the CreateUserWizard, there are three events associated with user creation: CreatingUserCreateUserError, and CreatedUser.CreatingUser occurs before the CreateUserWizard saves the ASP.NET data. CreateUserError occurs if the CreateUserWizard encounters an exception while trying to save. CreatedUser occurs after the CreateUserWizard has saved the ASP.NET data. If an error occurs and the CreateUserWizard invokes CreateUserError, the CreateUserWizard will not invoke CreatedUser.
Based upon the events of the CreateUserWizard and their behaviors, you can design a compensation strategy to ensure the consistency of data in both the ASP.NET and custom user data. Here, we have two conditions to address: ASP.NET update failure or custom Users data update failure. If one update fails, the other should not persist. The ASP.NET update failure case can be handled by the behavior of the CreateUserWizard events. Since errors during update will invoke the CreateUserError event, but not the CreatedUser event, we can put the Users table update code in the CreatedUser event handler and know that it won’t be executed if an error occurs with the ASP.NET update. In the case of an error with the Users table update, the ASP.NET update has already occurred. Therefore, whenever an error occurs during the Users table update, we must delete the user from the ASP.NET tables to maintain consistency. Listing 1 shows an implementation of theCreatedUser event handler that saves user data and compensates for potential errors during the update.
Listing 1. Implementing the CreatedUser Event for the CreateUserWizard
protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
{
var txtAddress = RegisterWiz.FindControl("txtAddress") as TextBox;
var txtCity = RegisterWiz.FindControl("txtCity") as TextBox;
var txtState = RegisterWiz.FindControl("txtState") as TextBox;
var txtPostalCode = RegisterWiz.FindControl("txtPostalCode") as TextBox;
var txtPhone = RegisterWiz.FindControl("txtPhone") as TextBox;
var user = new UserInfo
{
UserName = RegisterWiz.UserName,
Address = txtAddress.Text,
State = txtState.Text,
City = txtCity.Text,
Phone = txtPhone.Text
PostalCode = txtPostalCode.Text, };
var userMgr = new UserManager();
try
{
userMgr.InsertUser(user);
Roles.AddUserToRole(RegisterWiz.UserName, "Admin");
}
catch (Exception)
{
// compensating action to ensure data
consistency
Membership.DeleteUser(RegisterWiz.UserName);
throw;
// and/or log
// and/or communicate with user
// and/or some responsible handling
technique
}
}
There are a couple tasks being performed in Listing 1 to prepare the update, prior to error handling: getting a reference to custom data controls, instantiating an entity object with the proper info, and referencing a business object to interact with. The following snippet repeats a line from Listing 1 that demonstrates how to access custom controls in the CreateUserWizard, RegisterWiz:
var txtAddress = RegisterWiz.FindControl(
"txtAddress") as TextBox;
Like many other container controls in ASP.NET, the CreateUserWizard has a FindControl method that will return a reference to a child control with a specified name. In this case, the ID of the child control in the HTML is txtAddress, which is passed as a string parameter to FindControl. The return value of FindControl is Control, so we need to perform the conversion to TextBox, using the as operator. This results in a reference to the txtAddress control inside the User Info template of RegisterWiz. This example uses C# 3.0, and later, object initialization syntax to create an instance of UserInfo, a custom type passed into the business logic layer, repeated below:
var user = new UserInfo
{
UserName = RegisterWiz.UserName,
Address = txtAddress.Text,
State = txtState.Text,
City = txtCity.Text,
Phone = txtPhone.Text
PostalCode = txtPostalCode.Text,
};
It uses the UserName from the CreateUserWizard, which is important because UserName is the key of the Users table. All of the other native control values of CreateUserWizard are directly accessible via RegisterWiz instance properties. It’s only the custom controls that you add to a template where a call to FindControl is required. Looking at the downloadable code associated with this article, you’ll notice that I used the ADO.NET Entity Framework and LINQ to Entities for working with the Users table. Implementing this with LINQ was my preference, but you can use any data access technology that you’re comfortable with. The particular implementation is out of scope for this article, but hopefully the fact that I abstracted the implementation by placing it in a business object will make this part of the code more understandable. The code below, taken from Listing 1, demonstrates how I instantiate the business object that adds the new user info to the database:
        var userMgr = new UserManager();
The UserManager is a custom business object that I added to a library project and referenced from the current Website project. It exposes anInsertUser method that will perform the logic necessary to validate and save this object. The following snippet from Listing 1 shows how to call InsertUser and properly handle errors:
try
{
userMgr.InsertUser(user);
Roles.AddUserToRole(RegisterWiz.UserName, "Admin");
}
catch (Exception)
{
// compensating action to ensure data
consistency
Membership.DeleteUser(RegisterWiz.UserName);
throw;
// and/or log
// and/or communicate with user
// and/or some responsible handling
technique
}
It’s important that any exceptions raised from calling InsertUser are handled properly. Your own business situation and requirements dictate what the complete handling strategy should be, which is why I added all of the annoying comments reminding you to implement a proper handling strategy. Part of the handling strategy must be to delete the user from the ASP.NET tables, which is accomplished via the call toMembership.DeleteUser. Remember, by the time that the CreatedUser handler executes, ASP.NET has already saved the user in its tables. An exception during the call to InsertUser means that the new data has not been saved to the Users table, which would leave the database in an inconsistent state. Therefore, deleting the ASP.NET user data restores consistency.
Note: The discussion on exception handling strategy makes the assumption that Users data was not saved. Of course, there are exceptions to the rule. What If you were doing something in business logic that caused the exception after the value was saved? Again, the code you’ve written and application requirements dictate the exact exception handling strategy.
Notice that the code also assigns the user to the Admin role. If you recall, when setting up security policy for this site, we allowed only users in the Admin role to access pages. In practice, you would assign users to some default role and set security policy to allow those users to access only parts of the Web site that you allowed them to see. Additionally, I hard-coded Admin, but you might need this to be configurable via database or appSettings in web.config. Now, you can run the application.
Navigate to Register.aspx after logging in and fill in the form. If there aren’t any errors, you’ll be able to open the aspnet_users and Userstables and verify that the UserName you entered resides in both tables. To test the ASP.NET update failure scenario, you can try registering a new user with a duplicate UserName and verifying that the record was not added to either table. To test the Users table failure scenario, you can alter the InsertUser code and throw an exception before saving the data and then verify that the user is not part of either table.
Now you have a valid user in your system with custom data that is consistent with ASP.NET data. This gives you the best of both worlds where you can have additional user data, beyond what ASP.NET provides, and still retain the benefits of using the built-in ASP.NET security system.
Warning: With registration set up to write consistently to both ASP.NET tables and the Users table, you don’t want to use the ASP.NET Web Administration tool to add users. The ASP.NET Web Administration Tool has no knowledge of your Users table and would cause your data to be inconsistent if you used the ASP.NET Web Administration Tool to add users. A common symptom of this problem is when a user can log in, because ASP.NET is aware of the user, but application functionality isn’t working, because you don’t have custom data for the user.
Another site security feature is to help people who have an account, but have forgotten their password, discussed next.

Implementing Password Recovery

With so many sites with their own user IDs and passwords, a common occurrence is to forget a password. Just as common is the ability of sites to allow you to either retrieve or reset your password. This section will explain how you can implement password recovery in ASP.NET.
To get started, create a new Web page named ForgotPassword.aspx and drag-and-drop a PasswordRecovery control onto it. Next, make sure you’re in Design view and then click on the Administer Website link on the PasswordRecovery control’s action list. You’ll see the ASP.NET Web Administration Tool appear, just like Figure 8.
The PasswordRecovery control will send an email message to the email address for the user. To enable this behavior, you’ll need to configure an email server to relay the email through. You can do this by clicking on the Application tab and then click on the Configure SMTP e-mail settings link. At this point I would show you a screen shot, but I’m sure you don’t want all of the details for logging into my mail server. After you’ve added your own mail server credentials, click the Save button, click the OK button, and then you can close the ASP.NET Web Administration Tool screen.
Password Recovery is now set up, but you can’t use it yet. That’s because site security policy won’t allow anonymous users to access any page other than Login.aspx. Since the user doesn’t know their password, there is no way to get to the ForgotPassword.aspx page, but there is a way to configure exceptions like this. Add a location element to web.config, as shown in Listing 2.
Listing 2. Authorizing Access to Web Pages with the location Element
<?xml version="1.0"?>
<configuration>
  ...
  <location path="ForgotPassword.aspx">
    <system.web>
      <authorization>
        <allow users="*"/>
      </authorization>
    </system.web>
  </location>
</configuration>

Notice that the location element is a direct child of the configuration element. The path attribute specifies the page and the users attribute of the allow element specifies who can access the page, which is everyone, as indicated by the star.
Tip: Using the location element is a useful technique for allowing access to other parts of a Web site such as themes, resources, and other pages that you want the public to have access to. If you aren’t able to access a Web Service, adding a location element can help too. One indicator of the need for a location tag is if your themes or styles don’t appear on Login.aspx or another page that you’ve given the general public access to, which indicates a need to include your theme and/or styles folder in a location tag. Just use a folder name and all the contents below that folder will have the specified access.
Now, you can test password recovery by navigating from the login page to ForgotPassword.aspx. Enter a user name, answer the security question, and then look for the message in your email box. When setting up the user, you should have used an email address that was real and that you have access too. Otherwise, someone else might get spammed. Of course, it’s just as fine to use your boss’ email address too.

Modifying User Data

If you recall from earlier discussions, you should not use the ASP.NET Web Administration Tool for updating user information. It doesn’t have any features that allow you to update your custom user tables. Therefore, you must create your own Web page for performing inserts, updates, deletes, and viewing the user list. This Web page will be designed to use a custom object that ensures the consistency of user data.

A Bindable Business Object for Managing User Data

The technique you’ll learn in this article for managing the consistency of user data uses transactions. Both the update to the ASP.NET tables and the custom Users table will occur in the scope of a single transaction. That way, if an error occurs during either update, all changes roll back, leaving the database in the consistent state it was in before the transaction started. Listing 3 contains a business object that shows how to properly manage user data. To run the code, you need to add a reference to the System.Transactions assembly and add a using declaration to the file for the System.Transactions namespace.
Listing 3. A Business Object that Manages User Data via Transactions
using System;
using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using System.Web.Security;
using System.ComponentModel;
namespace SecurityLib
/// <summary>
{
/// manages users
/// </summary>
[DataObject]
public class UserManager
{
/// <summary>
/// reference to DAL
/// </summary>
private UserData m_data = new UserData();
/// <summary>
/// ensures a valid UserInfo object
/// </summary>
/// <param name="user">UserInfo to check</param>
private void ValidateUserInfo(UserInfo user)
{
if (user == null)
{
throw new ArgumentNullException(
"user",
"user must refer to a valid Users instance");
}
if (string.IsNullOrEmpty(user.UserName))
{
throw new ArgumentException(
"user must contain a UserName property with a valid value.",
"user.UserName");
}
}
/// <summary>
/// returns a list of all users from
/// both ASP.NET and Users tables
/// </summary>
/// <returns>list of UserInfo</returns>
[DataObjectMethod(DataObjectMethodType.Select)]
public List<UserInfo> GetUsers()
{
from user in m_data.GetUsers()
var allUsers =
join MembershipUser mbrUser in Membership.GetAllUsers()
on user.UserName equals mbrUser.UserName
select new UserInfo
{
UserName = mbrUser.UserName,
Password = string.Empty,
Email = mbrUser.Email,
Address = user.Address,
City = user.City,
PostalCode = user.PostalCode,
State = user.State,
return allUsers.ToList();
Phone = user.Phone };
}
/// <summary>
/// adds a new user to both
/// ASP.NET and Users tables
/// </summary>
/// <param name="user">UserInfo to add</param>
[DataObjectMethod(DataObjectMethodType.Insert)]
public void InsertUser(UserInfo user)
{
ValidateUserInfo(user);
using (var tx = new TransactionScope(
TransactionScopeOption.Required))
{
var userEntity = new Users
{
UserName = user.UserName,
Address = user.Address,
State = user.State,
City = user.City,
Phone = user.Phone
PostalCode = user.PostalCode, };
// add to the Users table
m_data.InsertUser(userEntity);
// add to ASP.NET tables
Membership.CreateUser(user.UserName, user.Password, user.Email);
// no exception - commit
tx.Complete();
} }
/// <summary>
/// updates specified user
/// </summary>
/// <param name="user">user to update</param>
[DataObjectMethod(DataObjectMethodType.Update)]
public void UpdateUser(UserInfo user)
{
ValidateUserInfo(user);
using (var tx = new TransactionScope(
TransactionScopeOption.Required))
{
var userEntity = new Users
{
UserName = user.UserName,
Address = user.Address,
State = user.State,
City = user.City,
Phone = user.Phone
PostalCode = user.PostalCode, };
// modify the Users table
m_data.UpdateUser(userEntity);
var mbrUser = Membership.GetUser(user.UserName);
mbrUser.Email = user.Email;
// modify ASP.NET tables
Membership.UpdateUser(mbrUser);
// no exception - commit
tx.Complete();
} }
/// <summary>
/// deletes specified user
/// </summary>
/// <param name="user">contains primary key, UserName</param>
[DataObjectMethod(DataObjectMethodType.Delete)]
public void DeleteUser(UserInfo user)
{
ValidateUserInfo(user);
using (var tx = new TransactionScope(
TransactionScopeOption.Required))
{
var userEntity = new Users
{
UserName = user.UserName
};
// delete from the Users table
m_data.DeleteUser(userEntity);
// delete from ASP.NET tables
Membership.DeleteUser(user.UserName);
// no exception - commit
tx.Complete();
} } }
}
The UserManager object in Listing 3 has validation and methods for working with UserInfo objects. It also contains an m_data field for referencing the Data Access Layer (DAL). As stated earlier, I used LINQ to entities to implement the DAL, but you can use any data access technology that will enlist in a transaction.
Notice how InsertUserUpdateUser, and DeleteUser in Listing 3 have using statements for TransactionScope objects. The using statement defines the boundaries of the transaction. The code sets TransactionScopeOption to Required, guaranteeing that the transaction runs either as part of another transaction or starts a new transaction. The important part of these methods is that they operate on both the ASP.NET user info and the custom user info. The code uses the ASP.NET membership API to update ASP.NET data and the DAL to update custom data at the same time. Both updates must occur atomically or not at all, which is where the transaction logic helps out.
If the transaction runs successfully, the tx.Complete statement will execute, committing the transaction. If an exception occurs, there is no way that the tx.Complete will be called, meaning that the transaction will not be committed. As you may already know, parameters to a usingstatement must be IDisposable, meaning that the using statement will call Dispose on the TransactionScope instance, tx. TheTransactionScope instance knows whether Complete has been called. If Dispose executes and the transaction has not been committed, thenDispose will ensure that the transaction rolls back, leaving the database in the consistent state it was in before the transaction.

Post a Comment

0 Comments
* Please Don't Spam Here. All the Comments are Reviewed by Admin.

Top Post Ad

Below Post Ad