I am developing a system that uses custom routing to load pages. I needed a quick way to secure the pages so I didn’t have to put the same
The "old" way
If Not Page.User.Identity.IsAuthenticated Then
Response.Redirect("~/Account/Login?ReturnUrl=" & Server.UrlEncode(Request.Url.ToString())
End If
You get the idea.
Anyway, I remember how beautifully WebAPI and MVC handle this by using the “AuthorizeAttribute” attribute. So, I decided to implement this in my page router.
The "new" way
First, you’ll want to enable custom routing in your application as outlined here. Notice that the article I linked to has a solution already outlined for security. I decided not to go that route as I like this solution better.
I have a custom class for handling the IRouteHandler
interface implementation required by the custom routing solution.
Within that handler, I do various things like check the virtual paths to make sure that they are valid. Most custom routes get converted to a custom base page that has a public property on it for use with my system. I instantiate that class, set the custom property, and then return that page as the IHttpHandler
required by the GetHttpHandler
method of the IRouteHandler
interface.
Here is the definition of the route in my RouteConfig.vb
file:
'Content editor route '
routes.Add("Edit Content", New Route _
( _
"EditContent/{contentControl}", New CMSPageRouteHandler("~/Pages/CMSContentEditor.aspx") _
))
Here is the class definition of a page with the Authorize attribute on it:
<Authorize(Roles:="Editor")> _
Public Class CMSContentEditor Inherits System.Web.UI.Page
...
End Class
Only users in the Editor role will be able to access this page. Let’s see why.
To start off with, we need to see if our page is decorated with the Authorize
attribute. I created a function that does this. I pass in the requestContext
that was handed off to me in the GetHttpHandler()
method (make sure you Import the System.Web.Http
namespace):
Protected Function IsPageAuthorized(ByVal page As Page, ByVal requestContext As System.Web.Routing.RequestContext)
Dim authorized As Boolean = requestContext.HttpContext.User.Identity.IsAuthenticated
' Check to see if this page is decorated with the "Authorize" attribute '
Dim authAttribute As AuthorizeAttribute = Nothing
Dim ptype As Type = page.GetType()
For Each attr In ptype.GetCustomAttributes(True)
If TypeOf attr Is AuthorizeAttribute Then
authAttribute = attr
Exit For
End If
Next
If authAttribute Is Nothing Then
' there was no Authorize attribute on this page, so go ahead and let them in '
authorized = True
Else
' see if this page is authorized '
If Not String.IsNullOrEmpty(authAttribute.Users) Then
' reset the authorized variable since we have Users listed '
authorized = False
If authAttribute.Users.ToLower().Split(New Char() {","}, StringSplitOptions.RemoveEmptyEntries).Contains(requestContext.HttpContext.User.Identity.Name.ToLower()) Then
authorized = True
GoTo EndOfAuthCheck
End If
End If
If Not String.IsNullOrEmpty(authAttribute.Roles) Then
' reset the authorized variable since we have roles '
authorized = False
' split the roles into an array '
Dim roles() As String = authAttribute.Roles.Split(New Char() {","}, StringSplitOptions.RemoveEmptyEntries)
If roles.Count > 0 Then
For Each role In roles
If requestContext.HttpContext.User.IsInRole(role) Then
authorized = True
GoTo EndOfAuthCheck
End If
Next
End If
End If
End If
EndOfAuthCheck:
Return authorized
End Function
First, we set the authorized variable to equal whether or not the user is authenticated. This will save us time later when none of the other checks fail.
After we loop through the attributes on the page, the authAttribute
will either be null or it will be equal to the instance of the AuthorizeAttribute
that is on the page. If we check it and it’s null, then we know the page is authorized, so we go ahead and set the authorized variable to true and continue on. However, if the authAttribute
has a value, then we need to check the properties on the attribute to see if the page is authorized.
User list check
First, we look to see if the User’s name is included in the array of Users (we get it by splitting the Users property of the attribute by commas). I went ahead and converted the Users property to lower case for comparison sake, just in case.
If we find the User’s name in the Users property, then we simply set the authorized variable to true and continue on. I used a GoTo
(I know) to short circuit and save the work of the next step:
Role checks
Next, we split the Roles into an array, then loop through each one, seeing if the user is in that role. If so, we set the authorized variable to true and continue on.
Authorized or not?
Once all of this has taken place, I return the authorized variable to the caller.
Handling the authorization response
In the calling method, here’s what I do:
If IsPageAuthorized(redirectPage, requestContext) Then
Return redirectPage
Else
RedirectToLoginPage(requestContext)
End If
Here is the RedirectToLogin()
method. I add a referrer to the ReturnUrl
so I know where the originating request came from. Otherwise, it gets lost when the Login page redirects to the desired page as the Login is now the referrer.
Protected Sub RedirectToLoginPage(ByVal requestContext As System.Web.Routing.RequestContext)
' redirect to the login page, but send the current referrer as a QueryString on the return page so it can be accessed '
Dim referrer As String = String.Empty
If Not requestContext.HttpContext.Request.UrlReferrer Is Nothing Then
referrer = "/?referrer=" & requestContext.HttpContext.Request.UrlReferrer.AbsolutePath
End If
requestContext.HttpContext.Response.Redirect("~/Account/Login?ReturnUrl=" & requestContext.HttpContext.Server.UrlEncode(requestContext.HttpContext.Request.Url.AbsolutePath & referrer))
End Sub
Then, in my custom page, I simply check the value of the referrer like so:
If Not Request.UrlReferrer.AbsolutePath.ToLower().Contains("login") Then
Session(SESSION_KEY_REFERRER) = Request.UrlReferrer.ToString()
Else
Session(SESSION_KEY_REFERRER) = Request.QueryString("referrer")
End If
I can’t use ViewState because of issues I’ve run into with custom routing and ViewState with my pages, so I’ve had to resort to using the Session.
As you can see, adding the Authorize
attribute to a custom page is easy when you’re doing custom routing. Of course, if custom routing isn’t for you, then this solution won’t work, but I thought I’d share it for those of you who do use custom routes with dynamic pages.