Firebird Membership/Role/Profile Example Continued

This article will discuss in moderate detail the example Firebird Membership/Role/Profile code. If your having problems getting your MemberShip provider connected to your Firebird database then checkout the first part of this article here Firebird MemberShip/Role/Profile Example.

The first part of this article, which discusses setting up the membership database with Firebird, is really the hardest part. Once you have that working the example code is really very simple. I'll go over some of what may be less obvious in the code to help answer some questions people may have. If anyone posts further questions in the future I'll add more detail to address those questions.

1. Forms authentication, Roles and Directories

    I decided to use Form level security and break up the form security by directory. This is a really simple way to handle page level security without having to add anything specific in each web page you develop. Essentially you drop a Web.config file in each directory of your website application and specify which roles are required to access any page in that directory. Afterwards any page in the corresponding directory will automatically inherit the security rules from the closest related Web.config.

    Example application directory structure:

    ~\
      Guest\
      User\
      Manager\
      Admin\
         

    Example application Web.Config file structure:

    ~\
      <em>Web.config</em> -Forms authentication configuration.
      Guest\
        <em>Web.config</em> -Accepted Roles(Guest,User,Manager,Admin).
      User\
        <em>Web.config</em> -Accepted Roles(User,Manager,Admin).
      Manager\
        <em>Web.config</em> -Accepted Roles(Manager,Admin).
      Admin\
        <em>Web.config</em> -Accepted Roles(Admin).
         

    Lets take a look at the syntax of the <authorization> tag in each Web.Config file.

    <system.web>
      <authorization>
        <deny users="?"/>
        <allow roles="Manager"/>
        <allow roles="Admin"/>
        <deny users="*"/>
      </authorization>
    </system.web>
         

    • <deny users="?"/>: Deny all anonymous users.
    • <allow roles="Manager"/>: Allow users with Manager role
    • <allow roles="Admin"/>: Allow users with Admin role
    • <deny users="*"/>: Deny ALL users.

    Its important to understand that the authorization rules are handled in the order they appear in the file. Once a rule is processed as true or a match then the checks stop and further rules are ignored.

    Example:

    • User is anonymous: The first check (deny users="?") will succeed returning true for denial of access. No further checks will be done and access will denied.
    • User has a login but no roles: The first check will fail, the 2nd (allow roles="Manager") and 3rd (allow roles="Admin") checks will also fail. The final check (deny users="*") catches all users so it will succeed and again access will be denied.
    • User is a Manager: The first check will fail. The 2nd will succeed and no further checks will be done. User will have full access to web pages in the corresponding directory assuming no additional security exists.
    • User is an Admin: The first and 2nd check will fail. The 3rd check will succeed and as in the last case full access is given to web pages in the corresponding directory.

2. Overview of Web Pages in the example

    The following web pages are part of the example code:

    • Default.aspx: Main page containing user information, login status and links to the Guest/User/Manager/Admin pages.
    • Login.aspx: Forms authentication login page. Any attempt to access another page without logging in will direct you here. Requests username and password.
    • NewUser.aspx: The login page has a link to create a new user if one doesn't exist. That link directs the user here. Basic user information plus some additional profile information must be entered to create a new user. Roles are not automatically granted so you must be a user that was part of the roles script provided in the example or add roles to the user manually afterwards.
    • GuestPage.aspx: A page that requires "Guest" role access.
    • User Page.aspx: A page that requires "User" role access.
    • ManagerPage.aspx: A page that requires "Manager" role access.
    • AdminPage.aspx: A page that requires "Admin" role access.

    The Guest/User/Manager/Admin pages contain no code, just a label with text telling the user they made it to the page. Essentially it just lets you know you had the correct role access to view that page.

3. Guest/User/Manager/Admin Pages

    The Guest/User/Manager/Admin pages contain no code, just a label with text telling the user they made it to the page. Essentially it just lets you know you had the correct role access to view that page.

4. Login Page

    Again this screen also contains no actual code. I just used the default Login control class. The only change made on the control was setting the CreateUserUrl property to ~/NewUser.aspx. This causes the "Create User Account" link to become visible so the user can navigate to the NewUser page to create their account.

    The Login control contains a property for selecting your MemberShipProvider but since we cleared the default providers and added our own in as the default (see Firebird MemberShip/Role/Profile Example.), we don't need to provide one.

5. Default Page

    The default page contains minimal code. There are links for each of the role pages as well as controls for displaying login status. A small block of code on the LoginView1 control displays the roles to which the current user has access, as well as the additional profile information.

    Two classes are introduced in this code: System.Web.Security.Roles class, and the _Default.Profile property.

    System.Web.Security.Roles:

      This class provides access to role information obtained in the ROLES and USERSINROLES tables. You can view as well as modify information in these tables using this class. In the example we use the function Roles.GetRolesForUser() to get a string array of all the roles for the current user.

    _Default.Profile:

      This property which exists on the page class provides access to the standard profile information as well the additional profile fields stored in the PROFILES table. This table contains a denormalized view of all special profile fields added in the Web.config. In the case of our example I added Age and Country as 2 additional user information fields. Check the primary Web.config file under the <profile> <properties> tags for the definition of these new fields. The NewUser.aspx page contains the code necessary to populate these fields.

6. NewUser Page

    Finally we will take a look at the NewUser page. While there is still very little code on this page its the most code of any page in this little example. For the most part the page just consists of a single CreateUserWizard control. If you look at the source for the NewUser.aspx you'll notice an extra WizardStep was added. This was needed to handle the new profile fields Age and Country. If you are comfortable working in the HTML you can manually add this page in by simply adding a <asp:WizardStep> tag inside the WizardSteps tag. You can have as many <asp:WizardStep> tags as you want. Each tag provides a new page which supports all normal HTML and asp extended controls within it.

    If you prefer to work visually select the CreateUserWizard control and look for the WizardSteps collection property. Click on the button in the property field to get the following:

    WizardStep Collection Editor

    As you can see I added the "Additional Info" step. You can add your steps visually here. Once your steps are added you will want to customize them with your fields. Close the WizardStep Collection Editor and right-click on the CreateUserWizard control. From the right-click menu select "Show Smart Tag" and you will get the following:

    CreateUserWizard Tasks

    Notice the steps dropdown list contains all the pages in your wizard. Select the page you want to work on from the list and the view of the NewWizard.aspx in the editor will display that page. Now you can drop down any HTML or asp extended control you want for gathering additional information.

    Now lets look at the code we need to make use of the extra information we collected. If you review the NewUser.aspx.cs file you'll see the following code:

    protected void CreateUserWizard1_ActiveStepChanged(object sender, EventArgs e) {
      if (CreateUserWizard1.ActiveStep == CreateUserWizard1.CompleteStep) {
        // Create an empty Profile
        ProfileCommon prof =
          (ProfileCommon)ProfileCommon.Create(CreateUserWizard1.UserName, true);

        WizardStep step = (WizardStep)CreateUserWizard1.WizardSteps[1];
        // Save the additional profile info and save
        prof.Country = ((TextBox)step.FindControl("txtCountry")).Text;
        prof.Age = Int32.Parse(((TextBox)step.FindControl("txtAge")).Text);
        prof.Save();
      }
    }
       

    This function is bound to the ActiveStepChange event on the CreateUserWizard1 control so it will be called each time the user causes the wizard to move to a new page. In this case we only want to do something if the wizard moves to the last page because then we know the user entered something in the "Additional Info" page (assuming some validation is done). Thus we check the ActiveStep to make sure its the CompleteStep which is the last step in our case.

    The first thing we need to do is create a new profile for the current user. The profile comes from the ProfileCommon class and the CreateUserWizard1 control has the UserName information. The special Age and Country fields we added in the Web.Config are automatically part of the profile class. All we need to do now is get access to the controls we added to our custom WizardStep. We can grab the WizardStep from the CreateUserWizard1 controls collection and then use the FindControl function on the step to gain access to the txtAge and txtCountry textboxes that were added. Finally just call the ProfileCommon.Save() method and the fields are saved to the database for the current user.

Its possible to handle security is a variety of ways including various levels of manual methods versus completely depending on the built in objects. In the end configuring the default providers to work with the standard Login and CreateUserWizard controls can be somewhat confusing, however once this is done it's generally a lot less effort to use those standard controls. Having said this I would still assume that most large scale web applications use a lot of custom security and will likely move to using more proprietary methods and controls to meet their needs.