CSLARoleProvider cookie not being created

CSLARoleProvider cookie not being created

Old forum URL: forums.lhotka.net/forums/t/6717.aspx


vbnetJeff posted on Wednesday, April 01, 2009

I'm having difficulty with the Roles cookie that is supposed to be created with my forms authentication in an ASP 3.5 application. I have set up my custom Principal and Identity objects and am persisting them into the CSLA.ApplicationContextUser in the global.asax. I have set the security on the main site with the following in Web.config:

  <authentication mode="Forms">

      <forms name=".FMCA" defaultUrl="~/Landing.aspx" timeout="60" slidingExpiration="true" loginUrl="~/Login.aspx" />

  </authentication>

<authorization>

      <deny users="?" />

 </authorization>

In the location path, I have tightened the authentication on certain pages to only allow "Admin" users to access those pages as follows:

<location path="UserSetup.aspx">

    <system.web>

      <authorization>

        <allow roles="Admin"/>

        <deny users="*"/>

      </authorization>

    </system.web>

</location>

I set the SiteMap to use security trimming and that part appears to be working correctly.  I have my roleManager object set up this way in web.config: 

  <roleManager defaultProvider="CSLARoleProvider" enabled="true" cacheRolesInCookie="true" createPersistentCookie="true" cookieName=".Roles" cookieTimeout="30" cookiePath="/" cookieRequireSSL="true" cookieSlidingExpiration="true" cookieProtection="All">

      <providers>

        <add name="CSLARoleProvider" type="CSLARoleProvider" applicationName="Members" writeExceptionsToEventLog="false"/>

      </providers>

</roleManager>

 

 And finally, I have this code in my custom RoleProvider object to get the correct roles for the authenticated, logged in user:

 Public Overrides Function GetRolesForUser(ByVal username As String) As String()

         Dim mRoles() As String = Nothing

          If Not System.Web.HttpContext.Current.User Is Nothing Then

            If System.Web.HttpContext.Current.User.Identity.IsAuthenticated Then

                Dim roleIdentity As FMCAIdentity = FMCAIdentity.ReLoadIdentity(username)

                mRoles = roleIdentity.GetRolesForUser.ToArray(GetType(String))

            End If

        Else

            Web.Security.FormsAuthentication.SignOut()

            FMCAPrincipal.Logout()

        End If 

        Return mRoles

    End Function

 

 Everything works fine, except that everytime the user calls a page that is listed in the <authorization> section of web config, the ‘GetRolesForUser’ sub is called, and the object makes another call to the database.  This information is supposed to be stored in a cookie after the 1st call, so that we are not continually making DB calls for the same information.  The only way I can get .NET to create the cookie is to manually type in a URL that the user is not authorized to view.  In this case, the ‘GetRolesForUser’ sub executes & the cookie is created. 

 

Has anyone run into this situation with the CSLARoleProvider cookie that is supposed to be created?  The forms cookie & session cookie are being created normally.

RockfordLhotka replied on Wednesday, April 01, 2009

Where did this CSLARoleProvider thing come from? It isn't part of normal CSLA .NET, so is it something you created for your app?

kdubious replied on Wednesday, April 01, 2009

I have the same issue. Primarily because I passed it along to Jeff.

We're trying to use web.config tags to manage role based authorization to resources (web pages). Session is not available at the time Authorization occurs, so we can't get to our BusinessPrincipal. This means we waste a dataabse call to grab the roles inside the RoleProvider.

.Net is supposed to cache the roles for us. BUT, it only does it after the first attempt to hit a page we do not have permission to access.

Maybe we are fighting a.NET issue.

However, can someone tell us the "CSLA Approved" approach to preventing access to pages based on membership in roles? We have thought up a few workarounds, but are concerned about peformance. Meaning, we don't want to wait until a page loads and waste all of that overhead just to redirect them to another page. (We want to hook the .NET authorization)

Thanks!

Kevin

RockfordLhotka replied on Wednesday, April 01, 2009

I don't think this is a CSLA issue. CSLA doesn't really care what you do, as
long as HttpContext.Current.User is a valid principal by the time your
page/business code is running. Everything prior to that point is an ASP.NET
issue.

Rocky

kdubious replied on Wednesday, April 01, 2009

Probably not.

But, how do you handle Authorization (ability to view a page) with CSLA? Do you let them navigate to every page, but not load the BO if they don't have permission.

Seems that it would be useful for CSLA to tie into .NET's authorization.

Kevin

RockfordLhotka replied on Wednesday, April 01, 2009

The trick is to put code behind global.asax to set up your
principal/identity object early in the postback processing, before ASP.NET
does its authz checking.

You don't need to encode the roles into a cookie - that's often inefficient
if you have a lot of roles (because you are sending that data back and forth
over a slow link on each request). Most web auth implementations reload the
user's data on each page request (from memory, cache or db) based on the
auth cookie (which contains just the username).

And this works fine, as long as you do the loading of the principal/identity
early enough in the process. This can be done in global.asax, or in an
HttpModule.

Rocky

JoeFallon1 replied on Thursday, April 02, 2009

kdubious:
how do you handle Authorization (ability to view a page) with CSLA? Do you let them navigate to every page, but not load the BO if they don't have permission.


That is exactly what I do.
At the top of each page is a check for IsInRole with a redirect to a message telling them they do not have authority to view that page.

(I actually have a finer grained call to HasPermission but the idea is the same.)

Joe

kdubious replied on Thursday, April 02, 2009

The ability to set Authorization tags for various locations in web.config is a very nice way to manage page access (for my purposes). Plus, it saves some processing on the server. Instead of letting the request go all the way to page load (or page init) and then trash that request and redirect to another page, Authorization happens very early on in the life cycle.

Web.sitemap security trimming help manage the menu (by removing pages that the user is not allowed to see), but it does not prevent access to those pages. Security trimming still works in our model, since it happens at the control creation and the session has put our principal into the content.

My Authorization issue seems to be this: using our own Principal removes the RolePrincipal that .NET uses in conjunction with the RoleProvider, so it can’t persist the Role Cookie. An Unauthenticated/Unauthorized request, or a request for a non-existent resource bypasses the code in Global where we set our principal, and the .NET cookie gets set.

Seems that the custom Principal in CSLA makes RoleProvider with "cacheRolesInCookie =true" a non-option based on the current implementation of the RoleProvider, FormsIdentity and RolePrincipal base classes.

Rocky makes a valid point that we'll have to test performance of the following: a.) using a custom cookie-based approach (ticket UserData perhaps) and taking the bandwidth hit b.) making an extra DB call on each request and taking that hit instead.

Always Trade-offs, right?

Kevin

JoeFallon1 replied on Friday, April 03, 2009

Trade-offs. Yes.

Most devs carry the RoleList inside their Principal/Identity classes. Any reason you do not want to do what almost everyone else does? Saves Db hits on each call to get the list of roles. Trade off is size of Principal object which gets serialized each hit.

Joe

kdubious replied on Friday, April 03, 2009

I do that, too (carry the RoleList inside my Identity class).

But consider the pipeline on the web server:

Authenticate the User (Forms Auth for me)
Authorize (Custom Role Provider would be ideal here)
Acquire Request State (This is where we swap the ASP.NET based Principal / Identity for our CSLA based Identity)
Process Request (See below)

If you defer Role Checking until the page loads, you have done a lot of work on the server already, and tied up a thread from the thread pool. The work includes wiring-up the Session variables for the user, possibly doing work with the viewstate (depends on just how late you Authorize), etc. This means you've tied up the thread that processes the Request, and under high usage, that's one more visitor who has to wait to get access to the app.

That's why ASP.NET authorizes early in the process. After all, if the Authorization fails, there's going to be a whole new request to process.

Rocky says:
The trick is to put code behind global.asax to set up your
principal/identity object early in the postback processing, before ASP.NET
does its authz checking.

But you can’t, since Authorization is happening before you have the Session which is where we keep the principal/identity.

I can make this work with custom Authorization code in Global, or custom Cookie code in the RoleProvider, but both would break the standard model offered by RoleProvider.

On the one hand, "who cares?" I mean we can make it work. On the other hand, I do prefer to do things the "proper" way if I can.

But, seems I'd need to have a change to .NET for this to work, so no breath-holding here.

Am I missing something?

Thanks everyone!

Kevin

mcfin replied on Monday, January 25, 2010

Hi Kevin,

I just realized that when we added role checking in web.config that it is too early(before AquireRequestState). So, we are doing an extra trip to the database.

Did you figure out a good way around this?

RockfordLhotka replied on Monday, January 25, 2010

In the end this is a classic state management issue. The web technologies were built to have Alzeimer's - to not remember anything from moment to moment, which is why web development is (imo) so darned frustrating.

You have a custom principal. That is state. You need to decide where to store that state when the server loses its mind. Your options (generally) are:

  1. Session
  2. database
  3. third party state store/cache product
  4. cookie
  5. hidden field

For things like a principal containing a bunch of roles, options 4 and 5 are out (why send lots of data over the slowest link?).

If you have a #3 (like memcached, velocity, etc) that might be an option. But let's face it, this is really just a variation on Session for the most part.

Session is out if you want to use all the ASP.NET features, because it is loaded too late. Which probably means number 3 is out for the same reason.

So that leaves you with only one option - the database.

Many (most?) high-volume apps use some highly cached database to solve this problem. So they do use a "database", but maybe one fronted by a good caching tool (again, memcached, velocity, etc).

mcfin replied on Monday, January 25, 2010

Thanks for the quick response Rocky.

When GetRolesForUser() is being called, the Web app knows the user id but doesn't have access to Session yet.

The user name is unique per application. So, I guess we can just cache the list of roles using the app name and user id as cache key, with a fairly short expiration. That would save at least some trips to the database.

Copyright (c) Marimer LLC