Examining ASP.NET's Membership, Roles, and Profile - Part 12
By Scott Mitchell
A Multipart Series on ASP.NET's Membership, Roles, and Profile
This article is one in a series of articles on ASP.NET's membership, roles, and profile functionality.
Part 1 - learn about how the membership features
make providing user accounts on your website a breeze. This article covers the basics of membership, including why it is needed,
along with a look at the SqlMembershipProvider and the security Web controls.
Part 2 - master how to create roles and
assign users to roles. This article shows how to setup roles, using role-based authorization, and displaying output on
a page depending upon the visitor's roles.
Part 3 - see how to add the membership-related
schemas to an existing database using the ASP.NET SQL Server Registration Tool (aspnet_regsql.exe).
Part 4 - improve the login experience by
showing more informative messages for users who log on with invalid credentials; also, see how to keep a log of invalid
login attempts.
Part 5 - learn how to customize the Login control.
Adjust its appearance using properties and templates; customize the authentication logic to include a CAPTCHA.
Part 6 - capture additional user-specific
information using the Profile system. Learn about the built-in SqlProfileProvider.
Part 7 - the Membership, Roles, and Profile systems
are all build using the provider model, which allows
for their implementations to be highly customized. Learn how to create a custom Profile provider that persists user-specific
settings to XML files.
Part 8 - learn how to use the Microsoft Access-based
providers for the Membership, Roles, and Profile systems. With these providers, you can use an Access database instead of
SQL Server.
Part 9 - when working with Membership, you have the
option of using .NET's APIs or working directly with the specified provider. This article examines the pros and cons of
both approaches and examines the SqlMembershipProvider in more detail.
Part 10 - the Membership system includes features
that automatically tally the number of users logged onto the site. This article examines and enhances these features.
Part 11 - many websites require new users to verify their
email address before their account is activated. Learn how to implement such behavior using the CreateUserWizard control.
Part 12 - learn how to apply user- and role-based
authorization rules to methods and classes.
Part 13 - see how to create a login screen
that allows Admin users to log in as another user in the user database.
Part 14 - learn how to create a page that permits
users to update their security question and answer.
Part 15 - the Membership API does not provide a means to change
a user's username. But such functionality is possible by going directly to the user store, as this article illustrates.
Part 16 - the Membership system includes the necessary components
for enforcing expiring passwords. This installment shows how to implement such a policy.
Part 17 - see how to display important, unread announcements to
users when they sign into the website.
Introduction
Several of the earlier installments in this article series examined how to apply authorization rules in order to prohibit particular users, roles,
or classes of users from accessing particular resources. For instance, Part 2
showed how to define URL-based authorization rules in web.config for roles. With just a bit of XML markup, it is possible to block particular users
or roles from visiting certain web pages. Just installments also looked at using the LoginView control, which displays different markup based on whether
the user is authenticated or not (and can also be used to display different markup based on the currently logged in user's role).
There are also programmatic techniques you can use to determine the identity of the currently logged on user and what roles she belongs to.
The URL-based authorization, LoginView control, and programmatic techniques can be used in tandem to ensure that a user does not visit a page or
perform some operation if she is not authorized. But what if you forget to implement one of these safeguards? For example, imagine that you have a web
page that includes a button that, when clicked, perform some task that is only intended for administrators. You could put this button in a LoginView
control or you could use programmatic techniques to ensure that only users in the appropriate role (say, Admin) saw the button. But what if
sometime later you, or another developer, removed this check by accident? The net result would be that any user visiting the page could perform the
administrator-only operation! Whoops!
To reduce the likelihood of such security mishaps, the .NET Framework includes capabilities for declaratively asserting permissions (via attributes) on
methods and classes. In a nutshell, you can add such attributes to ASP.NET pages, their code-behind classes, and your business logic and data access layers.
With these attributes in place, your visitors will be barred from performing unauthorized actions, regardless of whether there are any security holes in the
user interface. Read on to learn more!
Using the PrincipalPermissionAttribute Class to Declaratively Define Authorization Rules
The .NET Framework includes a class named PrincipalPermissionAttribute
that enables developers to apply principal permissions to code using declarative syntax. This attribute can decorate either classes or methods
and its properties spell out what users or roles can access the class or method it decorates. The PrincipalPermissionAttribute class has
the following important properties:
Action - defines the authorization check that is to be performed. There are a number of different Action options.
The one you'll use most often is Demand, which requires that all callers in the call stack have the permission specified by the other properties.
Authenticated - a Boolean value that indicates whether the caller must be authenticated or not.
Name - the name of the caller. An ASP.NET web application this is typically the username of the currently logged in user.
Role - the name of the role the caller must belong to.
The Action property is mandatory, but the other ones can be used in various combinations. For example, to declare that a particular method
can only be accessed by users in a particular role, you would use the following syntax:
// C#
[PrincipalPermission(SecurityAction.Demand, Role="roleName")]
public void SomeMethod(...)
{
...
}
' VB
<PrincipalPermission(SecurityAction.Demand, Role:="roleName")> _
Public Sub SomeMethod(...)
...
End Sub
Note: the PrincipalPermissionAttribute class is located in the System.Security.Permissions namespace. You may need to
import this namespace into the class files or ASP.net pages that use the PrincipalPermissionAttribute class.
You can also combine the Authenticated, Name, and Role properties in a single attribute declaration.
Doing so requires that the caller have all security permissions outlined. For example, to require that the user have the name Scott and along
to the role Admin, use the following:
' VB
<PrincipalPermission(SecurityAction.Demand, Name:="Scott", Role:="Admin")> _
Public Sub SomeMethod(...)
...
End Sub
To allow access for a caller that satisfies one or more conditions use multiple PrincipalPermissionAttributes. For instance,
the following declarations grant access to the method if the caller is user Scott or is in the role Admin:
' VB
<PrincipalPermission(SecurityAction.Demand, Name:="Scott")> _
<PrincipalPermission(SecurityAction.Demand, Role:="Admin")> _
Public Sub SomeMethod(...)
...
End Sub
Declaratively Applying Authorization Rules to Methods
The code available for download at the end of this article includes a simple business logic layer implemented as a single class named UserAPI.vb in the
App_Code folder. This class has three dummy methods:
SomeAdminAction - a method meant only to be called by users in the Admin role
SomeAuthenticatedAction - a method meant only to be called by authenticated users
SomeActionForScottOrSam - a method meant only to be called by users Scott or Sam
To help enforce these authorization rules it is good practice to add PrincipalPermissionAttributes to each method.
The following code implements these attributes and ensures that the methods can only be executed by those users or roles that have permission.
Imports System.Security.Permissions
Public Class UserAPI
<PrincipalPermission(SecurityAction.Demand, Role:="Admin")> _
Public Sub SomeAdminAction()
' ...
End Sub
<PrincipalPermission(SecurityAction.Demand, Authenticated:=True)> _
Public Sub SomeAuthenticatedAction()
' ...
End Sub
<PrincipalPermission(SecurityAction.Demand, Name:="Scott")> _
<PrincipalPermission(SecurityAction.Demand, Name:="Sam")> _
Public Sub SomeActionForScottOrSam()
' ...
End Sub
End Class
These attributes can also be added to methods and event handlers in the code-behind classes of ASP.NET web pages.
Declaratively Applying Authorization Rules to Classes
In addition to protecting particular methods, you can use PrincipalPermissionAttribute to protect an entire class.
Simply add the attribute immediately above the class declaration. The demo for download at the end of this article uses this functionality
to prohibit anonymous users from visiting the WhoIsOnline.aspx page. This is accomplished by adding a PrincipalPermissionAttribute
attribute to the class declaration in the page's code-behind file:
Imports System.Security.Permissions
<PrincipalPermission(SecurityAction.Demand, Authenticated:=True)> _
Partial Class WhoIsOnline
Inherits BasePage
...
End Class
What Happens When a Declarative Security Violation Occurs?
If an unauthorized caller executes a method or evokes a class that is decorated with a PrincipalPermissionAttribute attribute, a
SecurityException exception is thrown.
If there is no error handling the exception will bubble up to the ASP.NET runtime and will be handled there. Depending on how your website is configured
this may display the "yellow screen of death" (see the following screenshot) or a custom error page.
This exception can be gracefully handled in a number of ways. You can surround the call to the protected methods and classes with a Try...Catch block,
performing some action in the event of a SecurityException. The following code snippet and screenshot are from the code available for download
at the end of this article, and show a simple Try...Catch block implementation. Specifically, if a SecurityException is raised
when calling SomeActionForScottOrSam a message is displayed in a Label control (Message).
Try
Dim api As New UserAPI()
api.SomeActionForScottOrSam()
Catch sException As System.Security.SecurityException
Message.Text = "You attempted to perform an operation that you lack authorization for."
Catch ex As Exception
Throw ex
End Try
the website available for download from the end of this article has three user accounts: Scott, Jisun, and Sam. If Jisun logs on to the site,
visits the page ~/UsersOnly/Default.aspx, and clicks the button "Perform some operation that only Scott and Sam can do" the above
code is executed. The SomeActionForScottOrSam method includes two PrincipalPermissionAttribute attributes that permit only
users Scott and Sam. Consequently, when Jisun calls this method a SecurityException is thrown. The appropriate Catch block executes, displaying
the message shown in the screenshot below.
Another option is to automatically redirect users to a particular web page in the event of a security exception. This can be accomplished by creating an event handler
for the application's Error event in Global.asax. The Error event is raised whenever an unhandled exception bubbles up to the ASP.NET
runtime. Here we can determine what type of exception was raised and take action based on that knowledge. The following event handler redirects the user
to a page named Unauthorized.aspx whenever an unhandled SecurityException percolates up to the ASP.NET runtime.
Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
Dim err As Exception = Server.GetLastError()
If TypeOf err Is System.Security.SecurityException Then
Response.Redirect("~/Unauthorized.aspx?page=" & Server.UrlEncode(Request.RawUrl))
End If
End Sub
With this event handler in place in the event of an unauthorized access the caller is automatically shown the following screen.
Use the PrincipalPermissionAttribute Attribute as a Last Resort
Keep in mind that the purpose of the PrincipalPermissionAttribute attribute is to serve as a final authorization check.
You should still use your URL-based authorization rules in web.config along with user interface elements, such as the
Login and LoginView controls. The PrincipalPermissionAttribute attribute is designed to protect your business logic from
unauthorized callers should there be a security hole or oversight in the presentation layer.
If you download the demo at the end of this article you will no doubt notice the lack of preemptive checks in the presentation layer.
For instance, there are buttons to perform actions that are only available to authenticated users, yet these buttons are displayed to both
authenticated and anonymous users. Likewise, there are buttons to perform actions are only available to users in the Admin role, yet these buttons
are shown to all, regardless of their role. This design was done purposefully so as to illustrate the behavior of the PrincipalPermissionAttribute
attributes when an unauthorized user is encountered.
Conclusion
A well-designed website automatically prohibits users from visiting pages they are not authorized to see and hides user interface elements that perform actions
that the currently logged on user cannot perform. However, it is easy to overlook a particular user interface element and accidentally show it to all
users even though it performs an action that's reserved for a few. It's good practice to decorate your methods and classes with appropriate
PrincipalPermissionAttribute attributes. These attributes provide a declarative means for defining what callers can access what classes and methods,
and serves as a final check in case there are security holes or oversights in the presentation layer.
A Multipart Series on ASP.NET's Membership, Roles, and Profile
This article is one in a series of articles on ASP.NET's membership, roles, and profile functionality.
Part 1 - learn about how the membership features
make providing user accounts on your website a breeze. This article covers the basics of membership, including why it is needed,
along with a look at the SqlMembershipProvider and the security Web controls.
Part 2 - master how to create roles and
assign users to roles. This article shows how to setup roles, using role-based authorization, and displaying output on
a page depending upon the visitor's roles.
Part 3 - see how to add the membership-related
schemas to an existing database using the ASP.NET SQL Server Registration Tool (aspnet_regsql.exe).
Part 4 - improve the login experience by
showing more informative messages for users who log on with invalid credentials; also, see how to keep a log of invalid
login attempts.
Part 5 - learn how to customize the Login control.
Adjust its appearance using properties and templates; customize the authentication logic to include a CAPTCHA.
Part 6 - capture additional user-specific
information using the Profile system. Learn about the built-in SqlProfileProvider.
Part 7 - the Membership, Roles, and Profile systems
are all build using the provider model, which allows
for their implementations to be highly customized. Learn how to create a custom Profile provider that persists user-specific
settings to XML files.
Part 8 - learn how to use the Microsoft Access-based
providers for the Membership, Roles, and Profile systems. With these providers, you can use an Access database instead of
SQL Server.
Part 9 - when working with Membership, you have the
option of using .NET's APIs or working directly with the specified provider. This article examines the pros and cons of
both approaches and examines the SqlMembershipProvider in more detail.
Part 10 - the Membership system includes features
that automatically tally the number of users logged onto the site. This article examines and enhances these features.
Part 11 - many websites require new users to verify their
email address before their account is activated. Learn how to implement such behavior using the CreateUserWizard control.
Part 12 - learn how to apply user- and role-based
authorization rules to methods and classes.
Part 13 - see how to create a login screen
that allows Admin users to log in as another user in the user database.
Part 14 - learn how to create a page that permits
users to update their security question and answer.
Part 15 - the Membership API does not provide a means to change
a user's username. But such functionality is possible by going directly to the user store, as this article illustrates.
Part 16 - the Membership system includes the necessary components
for enforcing expiring passwords. This installment shows how to implement such a policy.
Part 17 - see how to display important, unread announcements to
users when they sign into the website.