Application security comes in many flavors; the two that most concern Web applications are authentication and authorization. Authentication is the most frequently discussed security topic. By definition, when you authenticate an item of any kind, you verify that the item is what it claims to be. For example, a Web application might need to authenticate the application's users and make sure that the users are who they say they are. To authenticate a user, you typically ask the user for a username and password.
In Web application security, authorization is second in importance only to authentication. When you authorize an action, you grant a capability to someone or something. For example, after you authenticate a user, you might want to ensure that the user can access the application's resources before the user needs those resources. Now, Web application security becomes more interesting. By nature, authorization is more granular than authentication. During a session, you usually authenticate a particular user only once, but you might authorize a user more than once and in several different places within the application (e.g., in NTFS and in SQL Server).
How you provide authentication and authorization services in Web applications depends on several things. You can use Windows authentication to perform the authentication services for you, then use Windows authorization services to handle authorization. For example, after a user logs on with a valid account, you can use that account to authorize the user in NTFS, COM+, SQL Server, and other areas.
If users access your application through a firewall, you might need to use Basic authentication, which provides a valid Windows user account for the user. Basic authentication is especially handy for SQL Server, in which you can use Windows Integrated Security to authenticate user accounts that Windows 2000 (or Windows NT) has already authenticated. You can also map user account groups to SQL Server roles, providing both an authentication and authorization scheme. (For more information about security for components, see the sidebar "Component Security Terms.")
If your application uses a non-Windows account for authentication, you can't use that account for authorization. For example, if you have an extranet that has thousands of users and your IT department doesn't want to put those users in a domain or Active Directory (AD), you won't be able to use Windows accounts for the extranet users. You might decide to use your own login scheme, perhaps by creating a database (or other repository) of usernames and passwords. Then, you would validate your users against that login database or repository.
However, if you choose such an independent login scheme, you need to provide your own authorization services also. This approach works well if, in your code, you provide authorization to specific parts of the application. When you must authorize user access to specific OS features such as files, a database, an application server, or components that run in COM+ (aka Component Services), this approach becomes more difficult.
Security and Components
Today, developers are building more and more Web applications that use components to perform part of the work. My own informal research shows a sharp upswing in the number of developers who have used components in their applications over the past 2 years. Microsoft's research indicates much the same thing. Most component development involves creating components that process business rules and other components that handle data access.
This increasing use of components leads to the questions: Why, when, and how should you add security to any of these components? If you use Microsoft technologies, you have only two basic options in component-level security. First, if your application uses Windows account authentication (i.e., Basic or Windows Integrated security in Microsoft IIS) and your components run in COM+, you can use COM+ security features. Second, if your application has its own security scheme and relies to some extent on Win2K or NT user accounts, you can't use only COM+ security.
So, if your application runs in COM+, you can use either COM+ security or Windows security (e.g., NTFS) to control authentication and authorization within your components. This option eliminates a lot of the work required to establish security because you can rely on COM+ to take care of security for you. However, if your application doesn't use Windows authentication and doesn't run its components in COM+, you must handle security in another way. Let's look first at using COM+ security.
Using COM+ Security
Understanding Web application security is more complex than just reading the COM+ security features, but let's see how those features apply to Web applications. Win2K or NT authentication services let you authenticate a component's user and obtain the user's credentials from Win2K; COM+ can handle the rest of component security.
Role-based security lets you define roles in COM+, then assign user accounts to those roles. Roles can control authorization at different levels in a COM+ application or component. After you define a role, you can use the Component Services explorer to assign the role to components at the class or method level. Then, you can check roles in two ways. First, declarative role-checking requires that you use Component Services to assign role-checking to a particular level. If a user tries to access a method and the user's account belongs to one of that method's roles, the call succeeds. If the user's account isn't in one of the method's—or its parent's—roles, the call fails. Second, application program security lets you check security against the COM+ roles in your code. If you choose this approach, you can control exactly what a user can do at any granular level.
You can't assume that COM+ solves all your security needs, however. For example, COM+ applications can interact with IIS in two ways, as Figure 1 shows. A COM+ library application runs inside the IIS process space, and a COM+ server application runs outside the IIS process space in its own process. As a developer, you might not know whether your component runs under a server or a library COM+ application. An administrator can change the setting for the COM+ application that contains your component at the flip of a switch. Then, your component could run differently because the COM+ application's behavior might change.
COM+ Server Applications
A COM+ application that runs as a server process is said to run "out of process." Let's take a look at a COM+ server application. To illustrate how COM+ security works, I created the component that Listing 1 shows. The test application in Listing 2 demonstrates how COM+ security works in a simple environment. You need to get security to work in a simple test application before you try to implement security in a complex application.
To administer COM+ applications, select Start, Program, Administrative Tools, Component Services. Next, expand Computers, My Computer, and COM+ Applications. Then, create a new COM+ application. Select COM+ Applications, right-click COM+ Applications, and select New, Application. Click Next on the Application Install Wizard, and click Create an empty application. Enter a name for your application. The default application type is Server, although you can change the type to Library here if you want. When you're finished, click Next. On the next page, set Identity to a particular account. You select an account by clicking the User button, then entering (or selecting) a username. Enter the user account's password in the Password and Confirm password fields. Then, click Next, Finish. Now that you have a new COM+ application, you can experiment with security options.
The first step in security for components running in COM+ is to control the account under which each component runs. When you created a COM+ application, you entered a user account for the Identity property. COM+ uses this account to run your application. For example, when a user accesses an application that uses a component in this COM+ application, the application runs under the Identity of that COM+ application's account. In other words, the OS thinks the Identity account is the user running the COM+ application. You need to assign to that application an account that has the correct permissions. I usually create a separate account for each COM+ application so that I can assign explicit permissions to that account. For example, if an application contains only components that access a database, those components don't need to run under an account that has open file-system permissions. So by controlling the account that your COM+ applications use, you inherently lock in one security level for all of that application's components. After all, if a component can't do anything bad, users can't do something bad with it.
At this point, your COM+ application can host components, and those components will run under the Identity account you selected. Now, let's add a component to the COM+ application by dragging the component (using Windows Explorer) into the application's Components folder. For this test, you can use my sample application COMPlusSecurity.dll (for download instructions, see "More on the Web," page 39). This DLL includes the class that Listing 1 shows.
Thus far, you can check two aspects of security with the CheckIt method: First, if you call oSecurityCallCtx.IsCallerInRole("admin") and security isn't enabled, CheckIt returns True. This behavior is the same as the behavior of Microsoft Transaction Services (MTS), from which many readers might be upgrading. You would expect CheckIt to return False for all roles if security is off, but CheckIt doesn't return False because I don't think it's implemented correctly in either COM+ or MTS. Second, to check role membership properly, you need to call both IsSecurityEnabled and IsCallerInRole, as the pseudocode in Listing 3 shows. The pseudocode first checks to see whether security is enabled, then performs the role check. If either check returns False, the pseudocode calls the PerformErrorHandling function. Packaging both of the role membership checks into one function that takes a role as a parameter simplifies the process. The CheckRole function that Listing 4 shows implements this dual check. You can execute this method, for example, in the following If...Then statement:
If CheckRole("admin") Then
Before working with roles, you must turn on access checking for the COM+ application to enable role checking in the application. Right-click the application name in Component Services, select Properties, then select the Security tab. Next, select Enforce access checks for this application. If you rerun the test application, it now fails because Component Services is checking the user's security and denying access to the component. To use this application's components, you must have at least one role that has users.
Creating and adding roles works the same way in COM+ applications as in MTS. To add a role to the Roles folder, expand the COM+ application in Component Services. Next, click the Roles folder, right-click the folder, then select New, Role. Next, enter the role's name, then click OK. Now you can add users to the role. Expand the appropriate role, then click the Users folder. Next, right-click the Users folder, then select New, User. In the Select users dialog box, select the users or groups that you want to assign to that role. The users should appear in the appropriate roles, as Figure 3 shows. No one can use a COM+ application's components until that application has assigned roles. If a user tries to access the application, that user receives a Permission denied error.
To add roles to a component, right-click each class in the Components folder, then select Properties. Click the Security tab, select the roles that can use the class, then click OK to update the changes. You can use the CheckRole function to determine whether a user is in a specific role. You can also check the roles within your code to add fine-grained security, restrict management functions to management users, and so forth.
COM+ Library Applications
A COM+ application that runs as a library process is said to run "in process." Many, if not most, Web applications that use COM+ run applications as library applications. When an application runs as a library application, it runs in the calling process. The security context for an application comes from the calling process, not the COM+ application.
To enable role-checking in a library application, you must set the application's security to component-level security. Open the application's Properties dialog box, click the Security tab, then in the Security level section, click Perform access checks at the process and component level. The only way to pass the security context to the COM+ application—and, thus, the only way your code can access role status—is to select this button.
If you want to check users with COM+ security, the users must log in with a valid Windows account. If you set up Basic authentication, you can use the following code in your ASP script to force users to log in:
<% If Request("LOGON_USER") = "" Then Response.Status = "401 Unauthorized" End If %>
I used the Visual Basic (VB) application in Listing 2 for testing because it's easy to use and doesn't use IIS, SQL Server, or other technologies. This VB application lets you test the component—and even some security settings—before moving on to data-driven Web applications. After you load the component into the application, open WinAppSecurityChecker.vbp in VB. On the Project menu, select References to ensure that the COMPlusSecurity component is registered.
Now, press F5 to run the test application. Figure 2, page 37, shows what the interface looked like after I clicked the Check Enabled button. The returned text shows that security is on. If you configure your COM+ application as I did and run the test application, clicking Check Enabled initially returns False. So far, you haven't enabled security for the COM+ application, and the security system isn't picking up the caller's identity. After you click the Check Enabled button, the form executes the CheckIt method that callout A in Listing 2 shows. Then, the test form passes the string "enabled" to the CheckIt function, and the CheckIt function tests to determine whether security is enabled. Clicking the Check Role button causes the CheckIt function to test the component's current user and determine whether that user belongs to the Admin role. Clicking the Check Role button also determines whether security is enabled.
The code in the class that Listing 1 shows uses COM+ for all its work. The class's VB project has a reference to the COM+ Services Type library, which contains security objects. The code segment at callout A in Listing 1 dimensions the oSecurityCallCtx variable as type SecurityCallContext. The code segment at callout B in Listing 1 calls the GetSecurityCallContext method to set the oSecurityCallCtx. Callout C in Listing 1 performs the actual work. When the code passes "role" to the CheckIt method, as callout B in Listing 2 shows, CheckIt determines whether that user belongs to the Admin role:
When the code passes "enabled" to the CheckIt method, CheckIt uses the following function to determine whether this application has COM+ security enabled:
Controlling Security with Custom Code
Suppose your components aren't running in COM+, your server is behind a firewall, or you don't use Windows security. You can still control security in two ways. First, you can—and should—assign an identity to the COM+ application. This identity can limit system-resource access to the component's user account, thereby controlling what the component can access. Second, you can create a granular security scheme for your components.
Controlling your own security requires major planning. You must decide whether you want to simply authenticate users or also control access to component features. Or perhaps you want to ensure that only your application code calls the methods in the components. Each approach controls security differently and with varying degrees of difficulty. I suggest setting up a database with a table structure, as Figure 4, page 39, shows. When a user logs in, check the username and password against the Login table to authenticate the user. Then, look up the user's ID in the LoginRole table to find the user's roles. The LoginRole table provides the role ID and names. Finally, retain the user's roles in your application and check them as you'd check COM+ roles.
The main problems with controlling your own security are twofold. First, you must create all the code to authenticate and authorize users—not a trivial task. Second, you must create the management tools to update users and roles—again, not trivial.
Trading Overhead for Custom Security
Whenever you apply security to an application, you have to make trade-offs. Custom security requires a good bit of coding overhead and lots of administrative overhead. Custom solutions usually require more overhead than packaged solutions, and you can't rely on the "plumbing" that COM+ provides because you're not using it. However, COM+ adds its own security overhead: All application users who use COM+ components must have valid Windows accounts and must log in correctly. But, because COM+ provides a valuable security system that ties into the OS, it has many advantages over custom solutions.
You also pay a performance price for security management. When you turn on security in COM+, COM+ manages your security information. But, security management adds a lot of overhead, which can adversely affect performance, especially on a heavily loaded server. The bottom line for component security is to analyze your requirements carefully, then plan your security system to meet those requirements. You don't want to use more or less security than you require.