.gif)
Related Links
patterns and practices Index
Chris Schoon, Doug Rees, Edward Jezierski
Microsoft Corporation
December 2002
Summary
This guide provides guidelines for designing and coding application-managed authorization for single or multi-tier applications that are based on Microsoft® .NET. It focuses on common authorization tasks and scenarios, and it provides information that helps you choose the best approaches and techniques. This guide is intended for architects and developers.
This guide assumes that readers have a basic knowledge of topics such as Windows authentication and authorization, XML Web services, and .NET remoting. For more information about designing distributed .NET-based applications, see "Designing Applications and Services" in the MSDN® Library. For more information about designing security into distributed applications, see "Building Secure ASP.NET Applications" in the MSDN Library. For more general design guidance, see the .NET Architecture Center in Microsoft TechNet.
Download
Click here to download this guide in PDF format.
Contents
This guide includes the following sections:
Introduction
This guide describes how to perform authorization in .NET-based applications. It explains the term authorization and discusses several mechanisms for performing authorization. This guide also describes:
- Important concepts such as identities and principals.
- How to use role-based security to authorize a category of users who share the same security privileges.
- Significant differences between .NET and COM+ role-based security.
The specific authorization mechanism you adopt often depends on how you authenticate, or verify the identity of, users. This guide examines:
- The differences between Windows authentication and non-Windows authentication.
- How these authentication mechanisms affect authorization.
- How to flow identity information for authorization purposes to remote application tiers.
In a typical enterprise application, you need to perform different kinds of authorization at each tier in the application. To help you to identify the need for authorization in each tier and to choose appropriate authorization strategies in various scenarios, this guide describes typical authorization tasks in the user interface tier, the business tier, and the data tier. Figure 1 highlights some of the important authorization issues in each tier in an enterprise application.
.gif)
Figure 1. Performing authorization in each tier in an enterprise application
The .NET Framework Class Library provides a variety of interfaces and classes that help you perform authorization with .NET role-based security. This guide describes:
- Several techniques for checking whether a user belongs to a particular role.
- How to handle authorization errors.
- Specific authorization issues that arise in multithreaded .NET applications.
Much of the effort you invest in defining an authorization framework can be reused across multiple applications. This guide concludes by describing:
- How to define a reusable authorization framework.
- Guidelines on how to maximize the security and performance of such a framework.
Note This guide pertains to application managed authorization using features of the .NET Framework. The Authorization Manager API and Microsoft Management Console (MMC) snap-in available in the Windows® Server 2003 family of operating systems provide applications with a complete role-based access control framework. The Authorization Manager API, also known as AzMan, provides a simplified development model in which to manage flexible groups and business rules and to store authorization policies. For more information, see "
Authorization Interfaces" and "
Authorization Objects" in the MSDN Library.
Understanding Authorization
Authorization is confirmation that an authenticated principal—a user, a computer, a network device, or an assembly—has permission to perform an operation. Protection allows only appointed users to perform certain actions, and it prevents malicious acts.
This section of the guide describes:
- The protection that authorization provides.
- Basic authorization.
- The authorization capabilities of the .NET Framework.
Mitigating Security Threats
Authorization alone is not enough to secure an application; therefore, this guide briefly mentions the types of threats that face your application. Some of the common security threats are as follows; these threads are often referred to by the acronym STRIDE. They include:
- Spoofing identity—An unauthorized user impersonating a valid user of the application
- Tampering with data—An attacker illegally changing or destroying data
- Repudiability—The ability of a user to deny that he or she performed an action
- Information disclosure—Sensitive data released to users or to locations that should not have access to it
- Denial of service—Acts of sabotage that make applications unavailable to users
- Elevation of privilege—A user illegally gaining an unacceptably high level of access to the application
You can use such techniques as the following to address STRIDE threats:
- Authentication—Strong authentication helps mitigate identity spoofing. When a user logs on to Windows or starts an application, he or she enters information in the form of credentials, such as a user name and password. Windows uses a protocol such as NTLM or Kerberos to validate the user's credentials and to log the user onto the system. Applications often use the product of a system logon or implement custom authentication as a basis for authorization. For more information about authentication, see "Building Secure ASP.NET Applications" in the MSDN Library.
- Authorization—Use the authorization techniques described in this guide to prevent data tampering and information disclosure threats.
- Identity flow—Applications deployed across multiple computers sometimes need to pass information representing the identity of the authenticated user among the computers of the system. Identity flow is typical when authentication occurs on the first computer and other application logic resides on a separate computer. For more information about identity flow, see "Designing Identity Flow for Authorization" later in this guide.
- Auditing—A record of both authorized and unauthorized operations reduces repudiability. This guide does not describe auditing in detail. For more information about auditing, see "Building Secure ASP.NET Applications" in the MSDN Library.
For more information about STRIDE, see "Designing for Securability" in the MSDN Library.
Figure 2 shows a model for how to mitigate STRIDE security threats in a multi-tiered application.
.gif)
Figure 2. Model of a secure multi-tiered application
Figure 2 depicts a multiple physical tier deployment; however, many smaller applications are implemented on one physical tier, simplifying authentication, authorization, and identity flow. Figure 2 illustrates the following measures to mitigate security threats:
- Bandwidth throttling reduces denial of service (DoS) attacks. This prevents applications from becoming swamped by continuous and undesired requests from malicious applications or users.
- Encryption enables secure communication.
- Authentication prevents identity spoofing.
- Authentication verifies credentials against a data store.
- Identity flows between the application tiers (optional).
- Auditing reduces repudiability.
- Authorization prevents data tampering and exposure threats.
Selecting Authorization Mechanisms
You can use various authorization mechanisms to control the functionality of your applications so that they behave as intended and cannot be misused either accidentally or deliberately. These authorization mechanisms fall into one of the following categories:
- System Authorization—Windows protects resources such as files and stored procedures with access control lists (ACLs). ACLs specify which users are allowed to access securable resources.
- .NET Code Access Security Authorization—Code access security authorizes code to perform actions based on the code's origin. For example, code access security determines which assemblies can access other assemblies in a .NET-based application, based on evidence criteria.
- Application Authorization—The application code itself authorizes user actions.
Use a combination of these approaches to create a secure application, as shown in Figure 3.
.gif)
Figure 3. Selecting authorization mechanisms
System Authorization
System authorization is the process of setting resource permissions or ACLs for objects that the operating system controls, such as printers and files. System administrators maintain these settings. System authorization is a yes or no decision: A user is either authorized or not authorized to access the resource.
Examples of system authorization include:
- Authorization settings in Microsoft ASP.NET-based applications that limit access to URL files or paths specified in Web.config files.
- Permissions set in the Active Directory® directory service.
- NTFS file system access control entries.
- Message queuing permissions.
- Permissions granted within server products such as Microsoft SQL Server™. Such permissions might involve individual objects such as tables or views.
For more information about system level security and authorization, see "Building Secure ASP.NET Applications" in the MSDN Library.
System authorization applies constraints to individual objects; however, .NET Code Access Security Authorization is necessary to constrain the code.
.NET Code Access Security Authorization
The .NET common language runtime uses code access security to constrain executable code. Code access security works by granting permissions (known as permission sets) to application code based on evidence. This evidence can include the code's origin, its publisher; or other evidence, such as an assembly's strong name.
Permission sets let you control what the application can perform, such as deleting files or accessing the Internet. For example, you can limit the application to use only isolated storage or to control access to printing.
Code access security takes the evidence into account regardless of the user's identity. Even if a user with administrative privileges uses the application, the code access security permissions remain the same. For example, if the code comes from the Internet, limits—such as the capacity to delete files—apply regardless of who the user is.
Examples of code access security usage include:
- Preventing a downloaded component from performing dangerous actions by limiting its power and placing it in an isolated environment. Isolation helps prevent rogue code from compromising your system.
- Creating an isolated environment for hosted code running on a Web server or on an application server.
- Limiting the power of your components to prevent malicious code from misusing them.
Note Use Caspol.exe or the Microsoft .NET Framework Configuration management console to configure code access security.
For more information about code access security, see the.NET Framework Developer's Guide article "Code Access Security" in the MSDN Library.
Code access security helps secure your system by checking permissions for the code, but depending on the application, you might also need to use application authorization to check permissions for the user.
Application Authorization
Most applications implement different functionality or security permissions, depending on the user interacting with the system. Design application authorization to limit access to application resources or to implement business rules based on the user's role within the application.
The primary purpose of application authorization is to secure functionality and other intangible items, such as business logic. For this reason, application authorization is difficult to implement with current system-level technologies because these technologies tend to use settings, such as ACLs, on physical resources. For example, you might want to secure an operation that approves an expense claim from an employee, but there is no physical resource to secure; therefore, when you design application authorization, you should focus on high-level operations rather than on individual resources.
Application authorization provides an alternative approach to system authorization when system authorization mechanisms are too finely grained, or when they don't take into account application data or state. For example, system level security standards for XML Web services are still under development and are therefore still evolving. You do not need to wait until the standards are complete to add security to or create secure XML Web services. For XML Web services you build today, you can implement application authorization and use Secure Sockets Layer (SSL) or other combinations to secure calls to the service.
Examples of application authorization include:
- Checking whether a user has permissions to perform specific actions—for example, approving an expense claim—within the application.
- Checking whether a user has permission to access application resources—for example, retrieving sensitive columns from a database.
You will learn how to design and code these types of application authorization later in this guide.
Checking at the Gate
To prevent an operation from needlessly continuing to the point of failure, always authorize a user's request as soon as you can. Each point of authorization is called a gatekeeper. Examples of gatekeeping mechanisms include file and URL authorization in an ASP.NET gate. There may be several gatekeepers as the identity flows down through the tiers. Checking at the gate reduces the number of authorization checks necessary deeper in the system (past the entry point, or gate).
Objects that perform authorization checks deeper in the system have fewer requirements for logic that compensates for the authorization failure. The individual component is not responsible for dealing with authorization failure beyond raising an exception to inform the caller of the failure.
Using Roles for Authorization
The .NET Framework provides a role-based application authorization capability. A role is a category or set of users who share the same security privileges.
Using roles rather than specific user identities provides the following benefits:
- You don't have to change your application when changes occur, such as users being added, being promoted, or leaving their jobs.
- Maintaining permissions for roles is easier than for individual users. For example, working with 10 roles is easier and less time-consuming than working with 120 users.
- A user can be a member of more than one role, allowing flexibility in how you assign and test permissions.
Defining Roles Based on the Business Organization
Roles can represent the position the user holds in an organization; for example:
- Manager
- Employee
- ClaimApprovalDepartment
A benefit to this approach is that the information is generally retrieved from a store such as in Active Directory. Often these roles are useful in modeling actual business requirements.
Achieving Independence from Organizational Changes
You can also use roles to indicate the types of operations that a user performs for his or her job. Such roles allow you to link application functions to individual users; for example:
- CanApproveClaim
- CanAccessLab
- CanViewBenefits
This second approach is flexible because you can design the roles around application functionality rather than around organizational structures. It might be harder to maintain because of a lack of infrastructure in which to store these roles. An application needs a combination of approaches in most cases.
Performing Authorization Without Using Roles
Sometimes you must base authorization on who the user is rather than on the roles he or she plays within the application. For example, you might allow only a direct manager to approve an employee's expense claim. You can achieve this finer level of authorization by comparing the current user to the manager of the employee who filed the claim.
Using Role-Based Security in .NET-Based Applications
The .NET Framework provides a role-based security implementation in the System.Security.Principal namespace, which you use for application authorization. To work with application authorization in the .NET Framework, create IIdentity and IPrincipal objects to represent users. An IIdentity encapsulates an authenticated user. An IPrincipal is a combination of the identity of the user and any roles he or she has.
Figure 4 shows the relationship between IIdentity and IPrincipal objects.
.gif)
Figure 4. Relationship between IIdentity and IPrincipal objects
Notice the following points in Figure 4:
- An IIdentity object is an instance of a class that implements IIdentity. An IIdentity object represents a particular user.
- The IIdentity interface has the properties Name, IsAuthenticated, and AuthenticationType. Classes that implement IIdentity often contain additional private members specific to their purpose. For example, the WindowsIdentity class encapsulates the account token for the user on whose behalf the code is running.
- An IPrincipal object is an instance of a class that implements IPrincipal. The IPrincipal object is a combination of an IIdentity representing the user and any roles he or she has. This allows for a separation of authentication and authorization capabilities.
- The IPrincipal object is used to perform authorization with the IsInRole method and provides access to the IIdentity object through the Identity property.
Working with Identities
The .NET Framework provides four classes that implement the IIdentity interface:
- WindowsIdentity
- GenericIdentity
- PassportIdentity
- FormsIdentity
Each class enables you to work with different kinds of user identities. To access the current WindowsIdentity object for an application that uses Windows authentication, use the static GetCurrent method of the WindowsIdentity class, as the following code shows:
|
WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent();
|
You can also create custom identity classes by implementing the IIdentity interface in your own custom class. For more information about creating custom identities, see "Extending the Default Implementation" later in this guide. For more information about how to work with the default IIdentity implementations, see "Designing Authentication for Authorization" later in this guide.
Working with Principals
The .NET Framework provides the IPrincipal interface to link user roles and identities. All managed code that performs application authorization should use an object of a class that implements IPrincipal. For example, the WindowsPrincipal and GenericPrincipal classes provide inbuilt implementations of IPrincipal. Alternatively, you can create your own custom principal classes based on IPrincipal.
To make your coding more efficient, link the IPrincipal object to the current thread by using techniques described in the Designing Authentication for Authorization section later in this guide. Linking the IPrincipal object to the thread provides the current thread easy access to the IPrincipal object by using the static CurrentPrincipal property of the Thread object, as the following code shows:
|
WindowsPrincipal currentPrincipal = (WindowsPrincipal)
Thread.CurrentPrincipal;
|
You can then perform an authorization check by testing whether the user is a member of a particular role. You do so by using the IsInRole method of the IPrincipal interface, as the following code shows:
|
bool roleMemberFlag = Thread.CurrentPrincipal.IsInRole(
"CanApproveClaims" );
|
ASP.NET applications handle IPrincipal objects differently than do other .NET-based applications. ASP.NET creates the appearance of a session over the stateless HTTP protocol. As a part of this session, the IPrincipal object representing the user is available from the User property of the HttpContext object for all code executing the user's request. The common language runtime automatically updates Thread.CurrentPrincipal with the HttpContext.User value after the OnAuthenticate event of the Global.asax file.
ASP.NET applications often use the User property to perform authorization checks, as the following code shows:
|
bool roleMemberFlag = Context.User.IsInRole( "CanApproveClaims" );
|
Note Manually changing HttpContext.User automatically updates the Thread.CurrentPrincipal for all threads executing within the same HTTP context. However, changing the Thread.CurrentPrincipal does not affect the HttpContext.User property. It affects only the chosen thread for the remainder of the request.
For more information about creating your own IPrincipal types, see "Extending the Default Implementation" later in this guide. For more information about how to work with the default IPrincipal implementations, see "Designing Authentication for Authorization" later in this guide.
Granting Permissions to Work with IIdentity and IPrincipal Objects
The ability to work with IIdentity objects is a sensitive operation because user-related information is available. Allowing applications to change the current IPrincipal object should also be protected because application authorization capability is based on the current principal. The framework provides this protection by requiring that these operations have a code access security permission. Grant the SecurityPermissionAttribute.ControlPrincipal permission to applications that need to manipulate these objects by using Caspol.exe or the .NET Framework Configuration tool.
By default, all locally installed applications have this permission because they run under the Full Trust permission set.
Executing the following methods require the ControlPrincipal permission:
- AppDomain.SetThreadPrincipalPolicy()
- WindowsIdentity.GetCurrent()
- WindowsIdentity.Impersonate()
- Thread.CurrentPrincipal()
For more information about setting security permissions using CASPOL, see "Configuring Security Policy Using the Code Access Security Policy Tool (Caspol.exe)" in the MSDN Library.
Managing Authorization Differences Between Windows and the Common Language Runtime
The common language runtime has a separate security infrastructure on top of the Windows security infrastructure. A Windows thread has a token for a Windows authenticated user, whereas the runtime thread has an IPrincipal object to represent the user.
When developing your code, you must therefore consider at least two security contexts that represent a user. For example, consider an ASP.NET-based application using forms authentication: The ASP.NET process runs under a Windows service account (a user account created specifically for an application) named ASPNET by default. Imagine a user named Bill logs on to the Web site. The Thread.CurrentPrincipal property represents the Forms authenticated user, Bill. The common language runtime sees the thread running as Bill, so any managed code sees Bill as the principal. If the managed code requests access to a system resource such as a file, the unmanaged code sees the ASPNET Windows service account regardless of the Thread.CurrentPrincipal value. Figure 5 represents these relationships.
.gif)
Figure 5. The authorization relationship between common language runtime and operating system threads
Impersonation allows you to change which user account the operating system thread executes under in its interactions with Windows. You can impersonate a Windows identity by calling the WindowsIdentity.Impersonate method. This form of impersonation allows you to access local resources as a particular user. When you call the Impersonate method, you will be logged on as the user that the WindowsIdentity represents. You must have access to the user's credentials on the server and call the LogonUser API. However, creating the WindowsIdentity manually can be complex, so you should perform impersonation only when it is absolutely necessary.
The WindowsImpersonatationContext.Undo method reverts a user's identity back to the original identity after the application code impersonates another user. These functions are generally called in a matched pair as the following code shows:
|
// Impersonate a Windows identity
WindowsImpersonationContext impersonationCtx =
WindowsIdentity.Impersonate(userToken);
// Do something under the context of the impersonated user
// Revert to the user's real identity
impersonationCtx.Undo();
|
For more information about impersonating a Windows account, see "Impersonating and Reverting" in the MSDN Library.
Designing Authentication for Authorization
Authorization depends on authentication—that is, a user or process must be authenticated before it can be authorized to view or use protected resources. This section of the guide examines two authentication mechanisms and explains how each affects authorization:
- Performing authorization based on Windows authentication
- Performing authorization based on non-Windows authentication
Your choice of authentication mechanism is often influenced by factors unrelated to authorization, such as the availability of Windows user accounts and general environmental considerations, including the client's browser type. However, the authentication choice you make does affect the way you perform your authorization checks.
Performing Authorization Based on Windows Authentication
All applications execute under a Windows user account known as the Windows identity. During Windows authentication, you use this Windows user account or the role to which the Windows user belongs to perform your authorization checks. Windows chooses the user account based on credentials supplied by one of the following techniques:
- Using information supplied by the user when he or she logged on to the computer
- Using a service account that you created especially for the application by using configuration tools such as the Component Services management utility
The overall process for using authorization with Windows authentication is:
- Windows uses NTLM or Kerberos to validate the user's credentials. For more information about using Kerberos delegation, see "How To: Implement Kerberos Delegation for Windows 2000" in "Building Secure ASP.NET Applications."
- The successful logon produces a Windows access token, which a WindowsIdentity encapsulates for .NET applications.
- Usually, the runtime automatically sets the Thread.CurrentPrincipal property to a WindowsPrincipal object that contains the Windows notion of roles; however, in some situations you must set it yourself as described later in this section.
- Authorization can now take place using Thread.CurrentPrincipal.
- When you call application logic remotely, there are some situations in which you need to manually provide the identity to the callee. This issue is described in greater detail in the Implementing Manual Identity Flow section later in this guide.
Note Link the IPrincipal object to the thread, and then retrieve the principal or identity information from the Thread.CurrentPrincipal property. This allows you to maintain the role collection in memory and therefore reduce how often you need to look up this information.
Performing Authorization by Using Windows Roles
You can check to see whether a user is a member of specific Windows user groups (such as "<domain>\Users") by using a WindowsPrincipal object. The WindowsPrincipal object has an Identity property, which returns a WindowsIdentity object that describes the identity of the current user.
You can configure .NET-based applications hosted within ASP.NET to have automatic access to a WindowsPrincipal from the Thread.CurrentPrincipal property by enabling Windows authentication in Web.config. You can use this technique in ASP.NET Web applications, XML Web Services, and .NET remoting objects hosted within ASP.NET.
Other .NET-based applications, such as Windows services, console, and Windows Forms-based applications, require you to establish Thread.CurrentPrincipal using one of the following approaches:
- Calling SetPrincipalPolicy
- Setting CurrentPrincipal
Note Code that works with
IIdentity and
IPrincipal objects requires the
ControlPrincipal permission. Use code access security to grant this permission to the application. For more information, see "
Using Role-Based Security in .NET-Based Applications" earlier in this guide.
Calling SetPrincipalPolicy
The SetPrincipalPolicy method of the AppDomain object links specific types of IPrincipal objects to the application domain. Use the WindowsPrincipal value of the PrincipalPolicy enumeration to link the current WindowsPrincipal object to the application threads, as the following code shows:
|
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrinc
ipal);
|
The principal is then available for authorization checks. Call SetPrincipalPolicy at the beginning of your application's execution to ensure that the IPrincipal object is linked to the threads before you perform any authorization checks.
Do not use SetPrincipalPolicy within ASP.NET applications because ASP.NET authentication manages this value for you during the Application_AuthenticateRequest event handler defined in Global.asax.
Note SetPrincipalPolicy is effective only if no default principal exists for the application domain.
Setting CurrentPrincipal
To set the CurrentPrincipal property, you must first create a new WindowsPrincipal object by passing a WindowsIdentity object to the WindowsPrincipal constructor. You can then link the new WindowsPrincipal to the Thread.CurrentPrincipal property as the following code shows:
|
Thread.CurrentPrincipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
|
The IPrincipal object is then available for authorization checks.
For information about how to perform authorization checks with a WindowsPrincipal, see "Performing Authorization Checks" later in this guide.
Performing Authorization by Using Application-Defined Roles
To create authorization checks based on application-defined roles (such as CanApproveClaims) you must manually create a generic or custom IPrincipal object. You do not need to call the SetPrincipalPolicy method because its default setting is NoPrincipal, which is the correct setting for assigning your own principal.
Most of the time, create a GenericIdentity object based on the name of the authenticated user. (To get this name, use the Name property of the IIdentity interface.) This avoids any security issues involving the user's logon token—accessible using a WindowsIdentity object—when presenting the IIdentity object to other components. You then create a GenericPrincipal that links the IIdentity object to your list of application-defined roles.
The following code shows how to create GenericIdentity and GenericPrincipal objects when you use Windows authentication:
|
GenericIdentity identity = new
GenericIdentity(WindowsIdentity.GetCurrent().Name);
Thread.CurrentPrincipal = new GenericPrincipal(identity,
new string[] {"CanApproveClaims", "User"});
|
In most cases, retrieve the application-defined roles from a data store rather than hard-coding the roles. This allows you greater flexibility in assigning role membership. For an example of this approach, see "How to Build a GenericPrincipal Using SQL Server" in the Appendix.
For information about how to perform authorization checks with a GenericPrincipal, see "Performing Authorization Checks" later in this guide.
Performing Authorization Based on Non-Windows Authentication
You might not want to or be able to use Windows authentication for application authorization. This could be because the users do not have Windows user accounts or because technical constraints keep you from using Windows authentication to validate the user's credentials.
Non-Windows authentication is the process of verifying the user's credentials using a technology other than Windows. This type of authentication includes those provided to you by the .NET Framework, such as ASP.NET Forms authentication and those you create yourself.
The overall process for using authorization with non-Windows authentication is:
- The application gathers the user credentials.
- The application verifies the credentials, typically against a custom data store such as a SQL Server database.
- Initially, the Thread.CurrentPrincipal property is set to a GenericPrincipal object that does not contain any roles. You replace Thread.CurrentPrincipal with a new GenericPrincipal object, or with an object of a custom class that implements IPrincipal to provide application-defined roles.
- Authorization can now take place using Thread.CurrentPrincipal.
- When calling application logic remotely, you might need to manually provide the identity to the callee.
Common types of non-Windows authentication include:
- ASP.NET Forms authentication—ASP.NET Forms authentication automatically creates a FormsIdentity object that represents the validated identity. It contains a FormsAuthenticationTicket, which contains information about the user's authentication session.
The Forms authentication provider creates a GenericPrincipal based on the FormsIdentity object but with no role membership.
For information about how to use ASP.NET Forms authentication, see article Q301240, "HOW TO: Implement Forms-Based Authentication in Your ASP.NET Application by Using C# .NET," in the Microsoft Knowledge Base.
Forms authentication almost always uses credentials that are specific to your application. For an example of how to verify Windows credentials obtained using forms authentication, see article Q316748, "HOW TO: Authenticate Against the Active Directory by Using Forms Authentication and Visual C# .NET," in the Microsoft Knowledge Base.
- Passport authentication—ASP.NET applications can use .NET Passport to perform authentication. Passport authentication produces a PassportIdentity object to represent a user identity. The PassportIdentity object extends the basic IIdentity interface to include Passport profile information.
The Passport authentication provider creates a GenericPrincipal based on the PassportIdentity object but with no role membership.
For more information about Passport authentication, see "The Passport Authentication Provider" in the MSDN Library.
- ISAPI authentication solutions—An alternative approach is to implement a manual or custom authentication mechanism using HTTP headers such as the authentication mechanisms found in Microsoft Commerce Server. For more information about security in Microsoft Commerce Server, see "Commerce Server Security" in the MSDN Library.
The HTTP transport provides information (such as a cookie) for use in constructing a generic or custom IIdentity object and a generic or custom IPrincipal object.
For more information about the authentication techniques available in ASP.NET, see "Authentication in ASP.NET: .NET Security Guidance" in the MSDN Library.
All the previously listed types of non-Windows authentication involve GenericPrincipal objects or custom IPrincipal objects. For an example of how to link a GenericPrincipal to application defined roles, see "How to Build a GenericPrincipal Using SQL Server" in the Appendix.
Designing Identity Flow for Authorization
Authentication creates IIdentity and IPrincipal objects for authorization purposes and determines how you can programmatically pass the identity information to application logic deployed remotely on other computers. The propagation of an authenticated identity is known as identity flow. There are two ways to achieve identity flow:
- Automatic identity flow
- Manual identity flow
Using Automatic Identity Flow
The common language runtime automatically provides identity flow when all of the code that requires the identity executes in the same context. The caller and callee are in the same context when they share the same application domain. If the client code (referred to as the caller) and the component being called (referred to as the callee) are running within the same context, .NET automatically uses the same Thread.CurrentPrincipal object for both the caller and the callee. For cases in which the callee and caller execute on different threads, see "Performing Authorization with Multiple Threads" later in this chapter.
Examples of code executing within a single application domain include:
- An ASP.NET Web site that contains all of the required code within in-process components and has no remotely deployed logic.
- A standalone Windows Forms-based application with no remotely deployed logic.
Implementing Manual Identity Flow
Implement manual identity flow when authorization checks occur in a remote tier that cannot perform authentication because of technical constraints. An example of this situation is calling a remote component using .NET remoting over the TCP channel. For more information about using channels in .NET Remoting applications, see "Microsoft .NET Remoting: A Technical Overview" in the MSDN Library.
If you are implementing your own identity flow, pass the data in a manner that differs from the way you call your code (known as out-of-band). If your component interface involves calling functions and passing data using parameters, plan to send identity information some other way.
For example, if you are calling an XML Web Service using SOAP, don't send the identity information as part of the message body; put it in a custom SOAP header. This is not so much a security consideration as a code design issue. A separation like this enables extensibility, reuse, and aggregation of your code interfaces. This also allows you to change the way you send the identity information as technological standards evolve without changing your interface contract. The Web Services Security (WS-Security) specification demonstrates this.
Note that identity flow is different from passing credentials (which is effectively a reauthentication on the server). Alternatively, you can pass the credentials within the message itself—as long as you encrypt the credentials. However, the encryption would involve using a shared secret key or a public key system, in which case passing the credentials in this manner isn't much of an advantage.
Following are some best practices for implementing identity flow:
- Use the strongest authentication available within the functional requirements and within technological constraints.
- Some identity flow techniques involve storing and/or passing secret information that is critical for the security of the identity flow mechanism. Encrypt the secret information or use a secure channel to keep the information safe. The classes in the System.Security.Cryptography namespace might be helpful. Also, the "How To: Create a DPAPI Library" sample in Building Secure ASP.NET Applications may be helpful.
- Store secret information securely using a technology such as the Data Protection API (DPAPI). For information about DPAPI, see "Windows Data Protection" in the MSDN Library.
- Ensure that your implementation allows you to use auditing to the depth required. For information on auditing, see "Monitoring and Auditing for End Systems" in Microsoft TechNet.
Passing Identity Information in a Trusted Environment
In a trusted environment, the callee leaves the responsibility of authentication to the caller. Any information passed from the caller to the callee is considered to be safe and reliable—for example, when a Web tier is the caller and a component on an internal application farm is the callee.
Note This can be an insecure solution if it is possible for a rogue user to invoke code passing in another (and potentially fake) identity. To protect against these types of spoofing attacks, use technologies provided by the system layer to secure the communication channels with Secure Sockets Layer (SSL) or IP Security (IPSec), and use code access security to ensure that only appropriate code calls your functions.
There are two main ways to pass identity information between your application tiers in a trusted environment:
- Passing only the user name—The caller can pass only the user name to the callee. The callee can then query the authorization store to create a generic or custom IPrincipal object and attach it to the thread, as described earlier in this guide.
This approach is also used when you want to flow the identity to the database for auditing. For more information, see "Performing Authorization in the Data Tier" later in this guide.
This approach provides flexibility, because you are not forcing callers to pass a particular type of object or extra information, such as the application roles.
- Passing the roles (using an IPrincipal object)—After authenticating the user, the caller can pass the roles to the callee by serializing the generic or custom IPrincipal object. The callee can then deserialize the object and attach it to the thread, as described earlier in this guide. This provides a similar effect to what happens automatically in a single application domain.
Passing a custom IPrincipal object allows you to pass the roles and any extra information that a custom IPrincipal object could contain (such as user profile information), as well as the identity.
However, you cannot pass IPrincipal objects that encapsulate certain types of information. For example, you cannot pass a WindowsPrincipal or any other principal based on a WindowsIdentity object because the identity maintains a token that makes no sense outside of the original environment.
For more information about serializing an IPrincipal object, see "How to Enable Authorization in a .NET Remoting Component" in the Appendix.
Note Passing identity information is acceptable only if you are within a trusted environment using a secured communication channel, such as a corporate LAN protected from the outside by a firewall. Protect yourself from spoofs caused by intercepting or modifying network data on the wire.
Passing Identity Information in a Non-Trusted Environment
A non-trusted environment is one in which the caller does not trust the callee. In a non-trusted environment, the callee must validate the caller before performing authorization—for example, a Web application validating each request by a browser.
There are two common ways to pass identity information between your application tiers in a non-trusted environment:
- Passing signed data—Passing cryptographically signed data using public key infrastructure can be an effective means of identity flow. You typically use this approach with asynchronous communication because there is no session for authentication.
Passing signed data works by including a digital signature with the message. The digital signature is computed using the private key of the sender and the contents of the message. To verify, the callee obtains the public key of the sender through a key management system and uses this key to determine that the sender used the corresponding private key when signing the message.
The .NET Framework provides the SignedXml class as an implementation of the XMLDSIG standard. This standard doesn't address issues such as key management or the trusting of keys. A receiver can make no assumptions about the trustworthiness of the keys used in this process. This is the responsibility of your application code. For details about the XMLDSIG standard, see "XML-Signature Syntax and Processing" on the W3C Web site.
For sample code showing how to use the SignedXml class, see the CAPICOM SDK; CAPICOM is a Microsoft ActiveX® control that provides a COM interface to Microsoft CryptoAPI. This sample code illustrates the full round trip of using a certificate authority, signing, and verifying an XMLDSIG signature. You can find the sample code in the Platform SDK Redistributable: CAPICOM 2.0 in the samples\c_sharp\xmldsig directory. For more information about CAPICOM, see the "CAPICOM Reference" in the MSDN library.
- Passing a token—Passing a token involves passing a piece of information that the callee verifies to determine who the user is.
Passing a token requires a shared authentication mechanism or a way to determine whether the token is valid. Passing a token might include passing a Kerberos ticket between components. Another example is a session cookie for an e-commerce Web site that an Internet Information Services (IIS) application creates upon authentication. The cookie passes back to the Web server each time the browser makes a call. This is typical of ISAPI solutions.
Now that you understand how authorization works with the various authentication mechanisms and how to pass authorization data across tiers, you need to see how authorization works in each of the three tiers in an enterprise application.
Performing Authorization in an Enterprise Application
Most of today's applications benefit from a multi-tier design because multiple tiers provide scalability, flexibility, and performance enhancements. Although the purpose of authorization is essentially the same within each of the application tiers—that is, to control user access to system functionality and data—you design and implement authorization differently across the tiers. The three tiers in an enterprise application are the:
- User interface tier
- Business tier
- Data tier
Performing Authorization in the User Interface Tier
This section describes how to perform authorization in the user interface tier. The following topics are included later in the guide: Performing Authorization in the Business Tier and Performing Authorization in the Data Tier.
Performing application authorization in the user interface tier helps to ensure that only authorized users can view or alter data or perform business functions that are restricted to specific jobs (for example, salary-related activities). Authorizing users in the user interface tier is the first opportunity you have to restrict access to processes that require authorization in other tiers within the system.
Create access checks in the user interface tier when you need to:
- Enable and disable or show and hide controls based on the user's role membership. For more information, see "Altering the Controls on a Form" later in this section.
- Change the flow from one form (or page) to the next, based on the user's role membership. For more information, see "Altering the Page Flow" later in this section.
Follow these best practices when you create authorization code in the user interface tier:
- Configure system authorization to control entry to the user interface whenever possible. For example, configure the Web.config file in an ASP.NET application to grant access to the entry page to only authorized users. For information about ASP.NET URL authorization, see "Building Secure ASP.NET Applications" in the MSDN Library.
- Defend against the user accessing the user interface elements at an incorrect stage in the process or task by authorizing the user when each form or page loads. For example, a user may need to fill out multiple Web pages in an ASP.NET application before an operation can be completed. Allowing the user to type a URL directly into the browser may allow the user to access pages he or she is not entitled to view at a certain stage. Therefore, you should perform application authorization checks when each page loads.
- Do not base the security of your system exclusively on application authorization within the user interface. Perform additional access checks at the business or data tier because you might call these components in several ways or from different applications.
- If the user interface limits the data the user can view, don't read unnecessary data from the data source; this prevents any potential leak of the restricted data as it travels around the network.
Note All code samples in the following topics show Web Forms for the examples. You can modify the code to work in Windows Forms by changing
User.IsInRole to
Thread.CurrentPrincipal.IsInRole. For more information about the
IsInRole method and performing authorization checks, see "
Performing Authorization Checks" later in this guide.
Altering the Controls on a Form
In both Windows forms and ASP.NET applications, you may need to alter the way that you display controls in the user interface based on authorization. These changes include changing the visibility of some controls or disabling them.
For example, in an employee expense claims application, members of the CanApproveClaims role need a button in the form for approving claims. All users not in this role do not see the Approve button. The following code demonstrates this approach:
|
if(User.IsInRole( "CanApproveClaims" ))
cmdApprove.Visible = true;
else
cmdApprove.Visible = false;
|
Such simplistic approaches can result in long "if-then-else" chains when you create authorization code for complicated user interfaces or for interfaces with many role customizations.
Use the following techniques to lessen the complexity of displaying controls based on authorization checks:
- Consolidate the logic—Place the logic for altering controls inside a private method that you call from within the form's Load event. Therefore, you have only one location to maintain the form authorization logic related to displaying controls.
The following code shows how you can apply this approach to a Web form:
|
private void Page_Load(object sender, System.EventArgs e)
{
ConfigureControls();
}
private void ConfigureControls()
{
// Configure the form controls
}
|
- Use a repeater control where possible—In a situation in which a control corresponds to multiple lines of data, use a repeater control so that you can modify all the controls settings at one time.
For example, if you create a Web form that uses a DataGrid, you can use a templated column in the form of a check box to allow authorized users to approve multiple claims at the same time.
The following code shows how you can use this technique to display an Approve column in a DataGrid.
|
private void ConfigureControls()
{
// Modify the visibility of the templated check box column
// based on the role of the user
if (User.IsInRole( "CanApproveClaims" ))
ClaimGrid.Columns[3].Visible = true;
else
ClaimGrid.Columns[3].Visible = false;
}
|
- Use a Model View Controller pattern—The Model View Controller pattern helps you separate the logic that controls the behavior of the user interface elements from the events fired by those controls. In the claims example, a form may have a list box of employees and a Show Claims button. You can enforce a clear separation of logic by putting all the code that should execute when ShowClaims is pressed in another function (called a controller function), or even in another class. The list box acts as a view on the employee data, and the Show Claims button handles user actions (such as a click) and calls the related controller function. The controller function is a method on your Web or Windows form that contains the logic to change the state of the business data and user interface.
Place specific portions of your authorization code that change control state after the form is loaded in these controller functions rather than in the event handler.
- Use separate forms for each role—Create separate forms for each role if your number of roles is low and each role requires a significantly different version of a form. Test for role membership in each form and redirect to the correct form. This may add significant test effort—using the Model View Controller pattern as described earlier may help you factor out and reuse your presentation code.
Altering the Page Flow
Sometimes—such as in a typical wizard user interface—you want the user to complete multiple forms (or pages) to perform a business process, depending on his or her role membership. This approach can change the page order or flow.
In the claims example, a GeneralManager member can view and approve claims from any employee in any department; however, a StoreManager member can view claims from only employees in his or her department. To allow the GeneralManager member to choose the department, create an extra step to retrieve this information before displaying the employee selection form.
The following code shows one way to create this type of page flow. The code uses the ASP.NET Server.Transfer method to transfer control to a different page. The calls to Server.Transfer are wrapped within a private method named DisplayNextPage:
|
private void btnView_Click(object sender, System.EventArgs e)
{
DisplayNextPage();
}
private void DisplayNextPage()
{
if (User.IsInRole( "GeneralManager" ))
Server.Transfer( "ChooseDepartment.aspx" );
else if (User.IsInRole( "StoreManager" ))
Server.Transfer( "ChooseEmployee.aspx" );
}
|
Performing Authorization in the Business Tier
Business processes range from fairly simple business logic to larger, long-running workflows consisting of many trusted and nontrusted partners. Your business tier code must ensure that all client requests meet the authorization and logic rules that the organization prescribes.
Create access checks in the business tier when you need to:
- Authorize an operation that performs a business process.
- Authorize a call from an external, nontrusted source before calling an internal business component. To do so could involve establishing an IPrincipal object for use by your business process in authorization. Use a service interface to manipulate the identity as it flows into your process, authorize a call to an internal business component, or both.
- Authorize calls to other parts of your distributed system or external services that are not part of your application. To do so could involve manipulating the identity flow out from your component. Use a service agent to make sure the current user can perform the external call and/or manipulate the identity as it flows out of your process.
Follow these best practices when creating authorization code in the business tier:
Using Role-Based Security in .NET vs. COM+
COM+ is best known for providing your applications with enterprise features such as transactional components, asynchronous activation, and object pooling. If you require any of these features in your .NET enterprise applications, you need to use COM+ rather than .NET role-based security. The COM+ role-based security system and the .NET framework role-based security do not interoperate. Therefore, you must decide early in the design which components will reside in COM+ and which will be .NET types; it will be hard to change the authorization code later.
For an example of how to target both the .NET role-based security system and the COM+ role-based security system, see "Unify the Role-Based Security Models for Enterprise and Application Domains with .NET" in MSDN Magazine.
In addition to role-based security, COM+ has built-in capability to flow the Windows user context and information about previous callers.
Note: Mixing COM+ and .NET role-based security in your components is not a safe practice. Windows switches between managed and unmanaged code when executing managed COM+ components, which makes principal information unreliable.
For code samples of COM+ role-based security, see "How to Use System.EnterpriseServices COM+ Role-Based Security" in the Appendix. For more information about using COM+ within .NET-based applications, see "Understanding Enterprise Services (COM+) in .NET" in the MSDN Library.
Performing Authorization in a Business Logic Component
Authorization checks are often required in the components that contain the business logic. You can call these components directly from the code in the user interface tier by using .NET remoting or by using service interfaces that encapsulate the business components, such as XML Web services.
The authorization logic that you create frequently blends in with and becomes part of your component business logic. For information about how to separate business logic and authorization logic, see "Separating Business Logic and Authorization Logic" later in this guide. For information about how to create your authorization checks, see "Creating Authorization Code with .NET Role-Based Security" later in this guide.
Performing Authorization in Entities
An entity is a representation of data that may also include some form of behavior. You can use numerous kinds of entity such as DataSets, XML strings, XML Document Object Models (XML DOMs), and custom entity classes in your application, depending on the physical and logical design constraints of the application. The application both produces and consumes these entities.
Entities frequently travel between application tiers, including complete round trips in which the data is retrieved from the data source, sent to the user interface, and returned to the data source for updating.
You can create classes to represent entities that can perform authorization checks. For example, only users belonging to certain roles can create and initialize an entity object.
However, in most cases, you should not perform application authorization in entities because:
- Pure data implementations such as DataSets, XML strings, and XML DOMs provide no inbuilt way to perform application authorization checks.
- Entities—including entities provided by a third party—can be highly mobile. You cannot be sure that client applications will be able to use the entity authorization features.
Performing Authorization in a Service Interface
Service interfaces are façades that expose internal business logic to the outside world. Service interfaces provide an entry point to a business process, and they give you the flexibility to change the method signature of the internal process without affecting the outside caller.
Service interfaces can provide entry for nontrusted parties or for parties that require authentication and authorization before they allow access to the internal business component.
In the expense claim example, a service interface allows one claims processing component to be accessible to nontrusted clients by using an XML Web service and for trusted clients by using either .NET remoting or Distributed COM (DCOM). Service interfaces may also be custom Message Queuing (MSMQ) listeners.
For sample code that demonstrates authorization in an XML Web Service, see "How to Perform Authorization in an XML Web Service" in the Appendix.
Performing Authorization in a Service Agent
A service agent is a bridge between business logic and an external service. XML Web Services and MSMQ message queues are two examples of external service agents.
You can use service agents to control access to external services by authorizing attempts to call the services from within your business tier. Service agents act as a proxy to an external service to allow you to perform validations, authorization, data formatting, and other tasks when your code calls the external service. Using a service agent also allows you to change which external service is being called and its signature, without affecting your business logic. You can write code in authorization service agents to prevent unauthorized users from calling external services.
Note This type of authorization is separate from authorization that occurs in the external service. If you maintain the external service, perform the authorization check within the callee anyway, to ensure that the system is secure.
In the following service agent code, a user cannot pass a claimXML message to the ApproveClaim XML Web service method if he or she does not belong to the CanApproveClaims role.
|
public ClaimResponse ApproveClaim(string claimXML)
{
if (Thread.CurrentPrincipal.IsInRole( "CanApproveClaims" ))
{
WebService.Claim claimService = new WebService.Claim();
return claimService.ApproveClaim(claimXML);
}
else
throw new SecurityException(
"You are not authorized to approve claims.");
}
|
For more information about dealing with exceptions in authorization code, see "Handling Authorization Errors" later in this guide.
If the external service requires a specific user identity for authorization, create code within the service agent to pass an acceptable IIdentity object. Doing so changes the identity flow out of the component. For more information about manipulating identity flow, see "Designing Identity Flow for Authorization" earlier in this guide.
Performing Authorization in the Data Tier
The data tier is the last line of defense against illegitimate requests from the user interface, business components, or attackers attempting to access the data directly. From an authorization perspective, the data tier ensures that only approved users can access and modify data regardless of how they attempt to access it.
Create access checks in the data tier when you need to perform the following actions:
- Prevent sensitive data from propagating outside the data tier.
- Prevent unauthorized modification of existing data or insertion of new data.
- Limit access to data by using stored procedures or views rather than modifying direct access permissions to the data tables.
To perform authorization in the data tier, you can use several techniques and objects, depending on your security requirements. You can perform authorization in the following data tier components:
- Data Access Logic Components—Data Access Logic Components are responsible for retrieving data from the database, saving data back to the database, and processing any logic required to achieve these data operations. All application data travels to and from the database through the Data Access Logic Components to the rest of the application.
- Stored Procedures—Stored procedures can perform authorization logic within the database and retrieve and update the data.
- Database Security Features—Use security features built into SQL Server to secure database objects such as tables, columns, views, and stored procedures.
For more information about working with Data Access Logic Components and entities, see "Designing Data Tier Components and Passing Data Through Tiers" in the MSDN Library.
The following questions will help you choose your authorization strategy in the data tier:
- How complex is the authorization logic?
- Simple—Perform the authorization check in a stored procedure.
- Complex—Perform the authorization check in the Data Access Logic Components, and call different stored procedures.
- How complex is the stored procedure?
- How many stored procedures involve authorization checks?
- A small percentage—Create multiple versions of stored procedures that involve authorization checks. Create naming standards that obviously link the stored procedures, such as for GetClaimsMGR and GetClaimsEMP. This approach can be detrimental to application design as your application grows bigger, so use it with caution.
- A large percentage—Create individual stored procedures that retrieve different data depending on who the connected user is. Use service accounts that represent application roles to connect to the database, allowing the stored procedure to perform the authorization check. This approach reduces maintenance by minimizing the number of stored procedures.
Follow these best practices when creating authorization code in the data tier:
- Connect to the database with few service accounts to maximize the performance benefit of connection pooling. Connection pooling allows applications to reuse connections when the connection information (such as the user details) matches an existing connection. Therefore, do not create connections as individual users.
For more information about connection management, see "Managing Database Connections" in the ".NET Data Access Architecture Guide" in the MSDN Library.
- Protect database objects with GRANT and DENY permissions.
Denying direct access to the data tables blocks users who connect to the database by using applications such as query analyzers, which do not enforce business logic on data updates or queries. Limit access to stored procedures and views to only allowed user/service accounts.
- Always use stored procedures when using application authorization techniques that involve WHERE clauses. Doing so helps prevent the SQL injection attack, in which attackers append additional (usually malicious) SQL statements to legitimate WHERE clauses. For more information about SQL injection attacks, see "Building Secure ASP.NET Applications" in the MSDN Library.
Performing Authorization in Data Access Logic Components
Data Access Logic Components are the last components that expose functionality before accessing your database. As such, you can use them to ensure only authorized users can access or modify data.
Perform authorization in Data Access Logic Components when:
- Multiple applications use the same Data Access Logic Components.
- You need to protect access to powerful functions exposed by the Data Access Logic Components or the data store.
- You need to restrict user access to sensitive data.
Examples of using role-based security within Data Access Logic Components include filtering data before it propagates to other application tiers and controlling which stored procedures your code executes.
For more detailed information about how to perform authorization checks, see "Performing Authorization Checks" later in this guide.
Filtering Data in the Data Access Logic Components
To ensure you return only suitable information to other application tiers, selectively remove sensitive data in your Data Access Logic Component code.
The following code sample removes the ConfidentialNotes column from the data when users do not belong to the Manager role.
|
public DataSet GetClaimData()
{
DataSet claimData;
// Database code to retrieve DataSet
//…if (!Thread.CurrentPrincipal.IsInRole( "Manager" ))
claimData.tables[0].Columns.Remove["ConfidentialNotes"];
return claimData;
}
|
Retrieving the unused column from the database is a waste of resources, especially if multiple columns are unused. Instead use stored procedures to select only the necessary data to increase database engine performance.
Calling Different Stored Procedures from the Data Access Logic Component
You can create multiple stored procedures that return the correct data for only one role. Your Data Access Logic Component code then performs an authorization check to determine which stored procedure to use based on the user's roles.
For example, you can create a version of a stored procedure for Managers and another for Employees that differ only by the data retrieved. The Data Access Logic Component code checks the user's role membership and calls the correct stored procedure.
The following code shows how the Data Access Logic Component method chooses which stored procedure to call based on the user's role membership. This code uses a helper class to avoid showing a lot of ADO.NET code. The Microsoft Application Blocks for .NET provides the SqlHelper class:
|
public DataSet GetClaimData(Int32 id)
{
DataSet dsClaimData;
if (Thread.CurrentPrincipal.IsInRole( "Manager" ))
dsClaimData= SqlHelper.ExecuteDataset(CONN_STRING,
CommandType.StoredProcedure, "GetClaimsMGR",
new SqlParameter("@ID", id));
else if (Thread.CurrentPrincipal.IsInRole( "Employee" ))
dsClaimData= SqlHelper.ExecuteDataset(CONN_STRING,
CommandType.StoredProcedure, "GetClaimsEMP",
new SqlParameter("@ID", id));
return dsClaimData;
}
|
Performing Authorization in the Stored Procedures
You can perform authorization within stored procedures by using two main approaches:
- Passing a role flag—Perform the authorization check in the Data Access Logic Component and pass an authorization result flag to the stored procedure. Doing so allows the stored procedure to filter the data based on the authorization flag, as the following code shows.
|
CREATE PROC GetClaimDetails
(
@ClaimID int,
@IsUserManager bit
)
AS
IF @IsUserManager = 0
SELECT ClaimDate,
ClaimAmount,
ClaimDescription
FROM Claims
WHERE
ClaimID = @ClaimID
ELSE
SELECT ClaimDate,
ClaimAmount,
ClaimDescription,
ApprovedFlag,
ConfidentialNotes
FROM Claims
WHERE
ClaimID = @ClaimID
|
- Passing the user identity or other role information—The Data Access Logic Component passes the user identity to the stored procedure as a parameter. The stored procedure then uses the parameter to look up the user's role membership and to select the data, as the following code shows.
|
CREATE PROC GetClaimDetails
(
@ClaimID int,
@UserIdentity varchar(20)
)
AS
IF NOT EXISTS(SELECT UserId FROM UserRoles
WHERE UserId = @UserIdentity AND Role = 'Manager')
SELECT ClaimDate,
ClaimAmount,
ClaimDescription
FROM Claims
WHERE
ClaimID = @ClaimID
ELSE
SELECT ClaimDate,
ClaimAmount,
ClaimDescription,
ApprovedFlag,
ConfidentialNotes
FROM Claims
WHERE
ClaimID = @ClaimID
|
Performing Authorization in the Database
Use the database security features as the fo